[GTK] [CMake] Add a "make dist" target
[WebKit-https.git] / Tools / gtk / make-dist.py
1 #!/usr/bin/env python
2 # Copyright (C) 2014 Igalia S.L.
3 #
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2 of the License, or (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17
18 from __future__ import print_function
19 from contextlib import closing
20
21 import argparse
22 import os
23 import re
24 import sys
25 import tarfile
26
27
28 def enum(**enums):
29     return type('Enum', (), enums)
30
31
32 class Rule(object):
33     Result = enum(INCLUDE=1, EXCLUDE=2, NO_MATCH=3)
34
35     def __init__(self, type, pattern):
36         self.type = type
37         self.original_pattern = pattern
38         self.pattern = re.compile(pattern)
39
40     def test(self, file):
41         if not(self.pattern.search(file)):
42             return Rule.Result.NO_MATCH
43         return self.type
44
45
46 class Ruleset(object):
47     _global_rules = None
48
49     def __init__(self):
50         # By default, accept all files.
51         self.rules = [Rule(Rule.Result.INCLUDE, '.*')]
52
53     @classmethod
54     def global_rules(cls):
55         if not cls._global_rules:
56             cls._global_rules = Ruleset()
57         return cls._global_rules
58
59     @classmethod
60     def add_global_rule(cls, rule):
61         cls.global_rules().add_rule(rule)
62
63     def add_rule(self, rule):
64         self.rules.append(rule)
65
66     def passes(self, file):
67         allowed = False
68         for rule in self.rules:
69             result = rule.test(file)
70             if result == Rule.Result.NO_MATCH:
71                 continue
72             allowed = Rule.Result.INCLUDE == result
73         return allowed
74
75
76 class File(object):
77     def __init__(self, source_root, tarball_root):
78         self.source_root = source_root
79         self.tarball_root = tarball_root
80
81     def get_files(self):
82         yield (self.source_root, self.tarball_root)
83
84
85 class Directory(object):
86     def __init__(self, source_root, tarball_root):
87         self.source_root = source_root
88         self.tarball_root = tarball_root
89         self.rules = Ruleset()
90
91     def add_rule(self, rule):
92         self.rules.add_rule(rule)
93
94     def get_tarball_path(self, filename):
95         return filename.replace(self.source_root, self.tarball_root, 1)
96
97     def get_files(self):
98         for root, dirs, files in os.walk(self.source_root):
99
100             def passes_all_rules(entry):
101                 return Ruleset.global_rules().passes(entry) and self.rules.passes(entry)
102
103             to_keep = filter(passes_all_rules, dirs)
104             del dirs[:]
105             dirs.extend(to_keep)
106
107             for file in files:
108                 file = os.path.join(root, file)
109                 if not passes_all_rules(file):
110                     continue
111                 yield (file, self.get_tarball_path(file))
112
113
114 class Manifest(object):
115     def __init__(self, manifest_filename, source_root, build_root, tarball_root='/'):
116         self.current_directory = None
117         self.directories = []
118         self.tarball_root = tarball_root
119         self.source_root = os.path.abspath(source_root)
120         self.build_root = os.path.abspath(build_root)
121
122         # Normalize the tarball root so that it starts and ends with a slash.
123         if self.tarball_root.endswith('/'):
124             self.tarball_root = self.tarball_root + '/'
125         if self.tarball_root.startswith('/'):
126             self.tarball_root = '/' + self.tarball_root
127
128         with open(manifest_filename, 'r') as file:
129             for line in file.readlines():
130                 self.process_line(line)
131
132     def add_rule(self, rule):
133         if self.current_directory is not None:
134             self.current_directory.add_rule(rule)
135         else:
136             Ruleset.add_global_rule(rule)
137
138     def add_directory(self, directory):
139         self.current_directory = directory
140         self.directories.append(directory)
141
142     def resolve_variables(self, string, strip=False):
143         if strip:
144             return string.replace('$source', '').replace('$build', '')
145
146         string = string.replace('$source', self.source_root)
147         if self.build_root:
148             string = string.replace('$build', self.build_root)
149         elif string.find('$build') != -1:
150             raise Exception('Manifest has $build but build root not given.')
151         return string
152
153     def get_full_source_path(self, source_path):
154         full_source_path = self.resolve_variables(source_path)
155         if not os.path.exists(full_source_path):
156             full_source_path = os.path.join(self.source_root, source_path)
157         if not os.path.exists(full_source_path):
158             raise Exception('Could not find directory %s' % full_source_path)
159         return full_source_path
160
161     def get_full_tarball_path(self, path):
162         path = self.resolve_variables(path, strip=True)
163         return self.tarball_root + path
164
165     def get_source_and_tarball_paths_from_parts(self, parts):
166         full_source_path = self.get_full_source_path(parts[1])
167         if len(parts) > 2:
168             full_tarball_path = self.get_full_tarball_path(parts[2])
169         else:
170             full_tarball_path = self.get_full_tarball_path(parts[1])
171         return (full_source_path, full_tarball_path)
172
173     def process_line(self, line):
174         parts = line.split()
175         if not parts:
176             return
177         if parts[0].startswith("#"):
178             return
179
180         if parts[0] == "directory" and len(parts) > 1:
181             self.add_directory(Directory(*self.get_source_and_tarball_paths_from_parts(parts)))
182         elif parts[0] == "file" and len(parts) > 1:
183             self.add_directory(File(*self.get_source_and_tarball_paths_from_parts(parts)))
184         elif parts[0] == "exclude" and len(parts) > 1:
185             self.add_rule(Rule(Rule.Result.EXCLUDE, self.resolve_variables(parts[1])))
186         elif parts[0] == "include" and len(parts) > 1:
187             self.add_rule(Rule(Rule.Result.INCLUDE, self.resolve_variables(parts[1])))
188
189     def get_files(self):
190         for directory in self.directories:
191             for file_tuple in directory.get_files():
192                 yield file_tuple
193
194     def create_tarfile(self, output):
195         count = 0
196         for file_tuple in self.get_files():
197             count = count + 1
198
199         with closing(tarfile.open(output, 'w')) as tarball:
200             for i, (file_path, tarball_path) in enumerate(self.get_files(), start=1):
201                 print('Tarring file {0} of {1}'.format(i, count).ljust(40), end='\r')
202                 tarball.add(file_path, tarball_path)
203         print("Wrote {0}".format(output).ljust(40))
204
205
206 if __name__ == "__main__":
207     parser = argparse.ArgumentParser(description='Build a distribution bundle.')
208     parser.add_argument('-s', '--source-directory', type=str, default=os.getcwd(),
209                         help='The top-level directory of the source distribution. ' + \
210                               'Directory for relative paths. Defaults to current directory.')
211     parser.add_argument('--tarball-root', type=str, default='/',
212                         help='The top-level path of the tarball. By default files are added to the root of the tarball.')
213     parser.add_argument('-b', '--build-directory', type=str, default=None,
214                         help='The top-level path of directory of the build root. ' + \
215                               'By default there is no build root.')
216     parser.add_argument('-o', type=str, default='out.tar', dest="output_filename",
217                         help='The tarfile to produce. By default this is "out.tar"')
218     parser.add_argument('manifest_filename', metavar="manifest", type=str, help='The path to the manifest file.')
219
220     arguments = parser.parse_args()
221
222     manifest = Manifest(arguments.manifest_filename, arguments.source_directory, arguments.build_directory, arguments.tarball_root)
223     manifest.create_tarfile(arguments.output_filename)