diff options
Diffstat (limited to 'Tools/gtk/make-dist.py')
-rwxr-xr-x | Tools/gtk/make-dist.py | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/Tools/gtk/make-dist.py b/Tools/gtk/make-dist.py new file mode 100755 index 000000000..4f0c4d589 --- /dev/null +++ b/Tools/gtk/make-dist.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python +# Copyright (C) 2014 Igalia S.L. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +from __future__ import print_function +from contextlib import closing + +import argparse +import errno +import multiprocessing +import os +import re +import shutil +import subprocess +import tarfile + + +def enum(**enums): + return type('Enum', (), enums) + + +class Rule(object): + Result = enum(INCLUDE=1, EXCLUDE=2, NO_MATCH=3) + + def __init__(self, type, pattern): + self.type = type + self.original_pattern = pattern + self.pattern = re.compile(pattern) + + def test(self, file): + if not(self.pattern.search(file)): + return Rule.Result.NO_MATCH + return self.type + + +class Ruleset(object): + _global_rules = None + + def __init__(self): + # By default, accept all files. + self.rules = [Rule(Rule.Result.INCLUDE, '.*')] + + @classmethod + def global_rules(cls): + if not cls._global_rules: + cls._global_rules = Ruleset() + return cls._global_rules + + @classmethod + def add_global_rule(cls, rule): + cls.global_rules().add_rule(rule) + + def add_rule(self, rule): + self.rules.append(rule) + + def passes(self, file): + allowed = False + for rule in self.rules: + result = rule.test(file) + if result == Rule.Result.NO_MATCH: + continue + allowed = Rule.Result.INCLUDE == result + return allowed + + +class File(object): + def __init__(self, source_root, tarball_root): + self.source_root = source_root + self.tarball_root = tarball_root + + def should_skip_file(self, path): + # Do not skip files explicitly added from the manifest. + return False + + def get_files(self): + yield (self.source_root, self.tarball_root) + + +class Directory(object): + def __init__(self, source_root, tarball_root): + self.source_root = source_root + self.tarball_root = tarball_root + self.rules = Ruleset() + + self.files_in_version_control = self.list_files_in_version_control() + + def add_rule(self, rule): + self.rules.add_rule(rule) + + def get_tarball_path(self, filename): + return filename.replace(self.source_root, self.tarball_root, 1) + + def list_files_in_version_control(self): + # FIXME: Only git is supported for now. + p = subprocess.Popen(['git', 'ls-tree', '-r', '--name-only', 'HEAD', self.source_root], stdout=subprocess.PIPE) + out = p.communicate()[0] + if not out: + return [] + return out.rstrip('\n').split('\n') + + def should_skip_file(self, path): + return path not in self.files_in_version_control + + def get_files(self): + for root, dirs, files in os.walk(self.source_root): + + def passes_all_rules(entry): + return Ruleset.global_rules().passes(entry) and self.rules.passes(entry) + + to_keep = filter(passes_all_rules, dirs) + del dirs[:] + dirs.extend(to_keep) + + for file in files: + file = os.path.join(root, file) + if not passes_all_rules(file): + continue + yield (file, self.get_tarball_path(file)) + + +class Manifest(object): + def __init__(self, manifest_filename, source_root, build_root, tarball_root='/'): + self.current_directory = None + self.directories = [] + self.tarball_root = tarball_root + self.source_root = source_root + self.build_root = build_root + + # Normalize the tarball root so that it starts and ends with a slash. + if not self.tarball_root.endswith('/'): + self.tarball_root = self.tarball_root + '/' + if not self.tarball_root.startswith('/'): + self.tarball_root = '/' + self.tarball_root + + with open(manifest_filename, 'r') as file: + for line in file.readlines(): + self.process_line(line) + + def add_rule(self, rule): + if self.current_directory is not None: + self.current_directory.add_rule(rule) + else: + Ruleset.add_global_rule(rule) + + def add_directory(self, directory): + self.current_directory = directory + self.directories.append(directory) + + def get_full_source_path(self, source_path): + if not os.path.exists(source_path): + source_path = os.path.join(self.source_root, source_path) + if not os.path.exists(source_path): + raise Exception('Could not find directory %s' % source_path) + return source_path + + def get_full_tarball_path(self, path): + return self.tarball_root + path + + def get_source_and_tarball_paths_from_parts(self, parts): + full_source_path = self.get_full_source_path(parts[1]) + if len(parts) > 2: + full_tarball_path = self.get_full_tarball_path(parts[2]) + else: + full_tarball_path = self.get_full_tarball_path(parts[1]) + return (full_source_path, full_tarball_path) + + def process_line(self, line): + parts = line.split() + if not parts: + return + if parts[0].startswith("#"): + return + + if parts[0] == "directory" and len(parts) > 1: + self.add_directory(Directory(*self.get_source_and_tarball_paths_from_parts(parts))) + elif parts[0] == "file" and len(parts) > 1: + self.add_directory(File(*self.get_source_and_tarball_paths_from_parts(parts))) + elif parts[0] == "exclude" and len(parts) > 1: + self.add_rule(Rule(Rule.Result.EXCLUDE, parts[1])) + elif parts[0] == "include" and len(parts) > 1: + self.add_rule(Rule(Rule.Result.INCLUDE, parts[1])) + else: + raise Exception('Line does not begin with a correct rule:\n\t' + line) + + def should_skip_file(self, directory, filename): + # Only allow files that are not in version control when they are explicitly included in the manifest from the build dir. + if filename.startswith(self.build_root): + return False + + return directory.should_skip_file(filename) + + def get_files(self): + for directory in self.directories: + for file_tuple in directory.get_files(): + if self.should_skip_file(directory, file_tuple[0]): + continue + yield file_tuple + + def create_tarfile(self, output): + count = 0 + for file_tuple in self.get_files(): + count = count + 1 + + with closing(tarfile.open(output, 'w')) as tarball: + for i, (file_path, tarball_path) in enumerate(self.get_files(), start=1): + print('Tarring file {0} of {1}'.format(i, count).ljust(40), end='\r') + tarball.add(file_path, tarball_path) + print("Wrote {0}".format(output).ljust(40)) + + +class Distcheck(object): + BUILD_DIRECTORY_NAME = "_build" + INSTALL_DIRECTORY_NAME = "_install" + + def __init__(self, source_root, build_root): + self.source_root = source_root + self.build_root = build_root + + def extract_tarball(self, tarball_path): + with closing(tarfile.open(tarball_path, 'r')) as tarball: + tarball.extractall(self.build_root) + + def configure(self, dist_dir, build_dir, install_dir, port): + def create_dir(directory, directory_type): + try: + os.mkdir(directory) + except OSError, e: + if e.errno != errno.EEXIST or not os.path.isdir(directory): + raise Exception("Could not create %s dir at %s: %s" % (directory_type, directory, str(e))) + + create_dir(build_dir, "build") + create_dir(install_dir, "install") + + command = ['cmake', '-DPORT=%s' % port, '-DCMAKE_INSTALL_PREFIX=%s' % install_dir, '-DCMAKE_BUILD_TYPE=Release', dist_dir] + subprocess.check_call(command, cwd=build_dir) + + def build(self, build_dir): + command = ['make'] + make_args = os.getenv('MAKE_ARGS') + if make_args: + command.extend(make_args.split(' ')) + else: + command.append('-j%d' % multiprocessing.cpu_count()) + subprocess.check_call(command, cwd=build_dir) + + def install(self, build_dir): + subprocess.check_call(['make', 'install'], cwd=build_dir) + + def clean(self, dist_dir): + shutil.rmtree(dist_dir) + + def check(self, tarball, port): + tarball_name, ext = os.path.splitext(os.path.basename(tarball)) + dist_dir = os.path.join(self.build_root, tarball_name) + build_dir = os.path.join(dist_dir, self.BUILD_DIRECTORY_NAME) + install_dir = os.path.join(dist_dir, self.INSTALL_DIRECTORY_NAME) + + self.extract_tarball(tarball) + self.configure(dist_dir, build_dir, install_dir, port) + self.build(build_dir) + self.install(build_dir) + self.clean(dist_dir) + +if __name__ == "__main__": + class FilePathAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, os.path.abspath(values)) + + def ensure_version_if_possible(arguments): + if arguments.version is not None: + return + + pkgconfig_file = os.path.join(arguments.build_dir, "Source/WebKit2/webkit2gtk-4.0.pc") + if os.path.isfile(pkgconfig_file): + p = subprocess.Popen(['pkg-config', '--modversion', pkgconfig_file], stdout=subprocess.PIPE) + version = p.communicate()[0] + if version: + arguments.version = version.rstrip('\n') + + + def get_tarball_root_and_output_filename_from_arguments(arguments): + tarball_root = arguments.tarball_name + if arguments.version is not None: + tarball_root += '-' + arguments.version + + output_filename = os.path.join(arguments.build_dir, tarball_root + ".tar") + return tarball_root, output_filename + + parser = argparse.ArgumentParser(description='Build a distribution bundle.') + parser.add_argument('-c', '--check', action='store_true', + help='Check the tarball') + parser.add_argument('-s', '--source-dir', type=str, action=FilePathAction, default=os.getcwd(), + help='The top-level directory of the source distribution. ' + \ + 'Directory for relative paths. Defaults to current directory.') + parser.add_argument('--version', type=str, default=None, + help='The version of the tarball to generate') + parser.add_argument('-b', '--build-dir', type=str, action=FilePathAction, default=os.getcwd(), + help='The top-level path of directory of the build root. ' + \ + 'By default is the current directory.') + parser.add_argument('-t', '--tarball-name', type=str, default='webkitgtk', + help='Base name of tarball. Defaults to "webkitgtk".') + parser.add_argument('-p', '--port', type=str, default='GTK', + help='Port to be built in tarball check. Defaults to "GTK".') + parser.add_argument('manifest_filename', metavar="manifest", type=str, action=FilePathAction, help='The path to the manifest file.') + + arguments = parser.parse_args() + + # Paths in the manifest are relative to the source directory, and this script assumes that + # current working directory is the source directory, so change the current working directory + # to be the source directory. + os.chdir(arguments.source_dir) + + ensure_version_if_possible(arguments) + tarball_root, output_filename = get_tarball_root_and_output_filename_from_arguments(arguments) + + manifest = Manifest(arguments.manifest_filename, arguments.source_dir, arguments.build_dir, tarball_root) + manifest.create_tarfile(output_filename) + + if arguments.check: + Distcheck(arguments.source_dir, arguments.build_dir).check(output_filename, arguments.port) |