346 lines
13 KiB
Python
Executable File
346 lines
13 KiB
Python
Executable File
#!/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 = list(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 as 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', '-DENABLE_GTKDOC=ON', '-DENABLE_MINIBROWSER=ON', 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 check_symbols(self, build_dir):
|
|
check_bss = os.path.join(self.source_root, 'Tools', 'Scripts', 'check-for-global-bss-symbols-in-webkitgtk-libs')
|
|
libjsc = os.path.join(build_dir, 'lib', 'libjavascriptcoregtk-4.1.so')
|
|
libwk = os.path.join(build_dir, 'lib', 'libwebkit2gtk-4.1.so')
|
|
subprocess.check_call([check_bss, libjsc, libwk])
|
|
|
|
check_version_script = os.path.join(self.source_root, 'Tools', 'Scripts', 'check-for-invalid-symbols-in-version-script')
|
|
version_script = os.path.join(os.path.dirname(build_dir), 'Source', 'WebKit', 'webkitglib-symbols.map')
|
|
subprocess.check_call([check_version_script, version_script, libwk])
|
|
|
|
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)
|
|
if port == 'GTK':
|
|
self.check_symbols(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/WebKit/webkit2gtk-4.1.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)
|