Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | from mozpack.packager.formats import ( |
michael@0 | 6 | FlatFormatter, |
michael@0 | 7 | JarFormatter, |
michael@0 | 8 | OmniJarFormatter, |
michael@0 | 9 | ) |
michael@0 | 10 | from mozpack.packager import ( |
michael@0 | 11 | preprocess_manifest, |
michael@0 | 12 | preprocess, |
michael@0 | 13 | Component, |
michael@0 | 14 | SimpleManifestSink, |
michael@0 | 15 | ) |
michael@0 | 16 | from mozpack.files import ( |
michael@0 | 17 | GeneratedFile, |
michael@0 | 18 | FileFinder, |
michael@0 | 19 | File, |
michael@0 | 20 | ) |
michael@0 | 21 | from mozpack.copier import ( |
michael@0 | 22 | FileCopier, |
michael@0 | 23 | Jarrer, |
michael@0 | 24 | ) |
michael@0 | 25 | from mozpack.errors import errors |
michael@0 | 26 | from mozpack.unify import UnifiedBuildFinder |
michael@0 | 27 | import mozpack.path |
michael@0 | 28 | import buildconfig |
michael@0 | 29 | from argparse import ArgumentParser |
michael@0 | 30 | from createprecomplete import generate_precomplete |
michael@0 | 31 | import os |
michael@0 | 32 | from StringIO import StringIO |
michael@0 | 33 | import subprocess |
michael@0 | 34 | import platform |
michael@0 | 35 | |
michael@0 | 36 | # List of libraries to shlibsign. |
michael@0 | 37 | SIGN_LIBS = [ |
michael@0 | 38 | 'softokn3', |
michael@0 | 39 | 'nssdbm3', |
michael@0 | 40 | 'freebl3', |
michael@0 | 41 | 'freebl_32fpu_3', |
michael@0 | 42 | 'freebl_32int_3', |
michael@0 | 43 | 'freebl_32int64_3', |
michael@0 | 44 | 'freebl_64fpu_3', |
michael@0 | 45 | 'freebl_64int_3', |
michael@0 | 46 | ] |
michael@0 | 47 | |
michael@0 | 48 | |
michael@0 | 49 | class ToolLauncher(object): |
michael@0 | 50 | ''' |
michael@0 | 51 | Helper to execute tools like xpcshell with the appropriate environment. |
michael@0 | 52 | launcher = ToolLauncher() |
michael@0 | 53 | launcher.tooldir = '/path/to/tools' |
michael@0 | 54 | launcher.launch(['xpcshell', '-e', 'foo.js']) |
michael@0 | 55 | ''' |
michael@0 | 56 | def __init__(self): |
michael@0 | 57 | self.tooldir = None |
michael@0 | 58 | |
michael@0 | 59 | def launch(self, cmd, extra_linker_path=None, extra_env={}): |
michael@0 | 60 | ''' |
michael@0 | 61 | Launch the given command, passed as a list. The first item in the |
michael@0 | 62 | command list is the program name, without a path and without a suffix. |
michael@0 | 63 | These are determined from the tooldir member and the BIN_SUFFIX value. |
michael@0 | 64 | An extra_linker_path may be passed to give an additional directory |
michael@0 | 65 | to add to the search paths for the dynamic linker. |
michael@0 | 66 | An extra_env dict may be passed to give additional environment |
michael@0 | 67 | variables to export when running the command. |
michael@0 | 68 | ''' |
michael@0 | 69 | assert self.tooldir |
michael@0 | 70 | cmd[0] = os.path.join(self.tooldir, 'bin', |
michael@0 | 71 | cmd[0] + buildconfig.substs['BIN_SUFFIX']) |
michael@0 | 72 | if not extra_linker_path: |
michael@0 | 73 | extra_linker_path = os.path.join(self.tooldir, 'bin') |
michael@0 | 74 | env = dict(os.environ) |
michael@0 | 75 | for p in ['LD_LIBRARY_PATH', 'DYLD_LIBRARY_PATH']: |
michael@0 | 76 | if p in env: |
michael@0 | 77 | env[p] = extra_linker_path + ':' + env[p] |
michael@0 | 78 | else: |
michael@0 | 79 | env[p] = extra_linker_path |
michael@0 | 80 | for e in extra_env: |
michael@0 | 81 | env[e] = extra_env[e] |
michael@0 | 82 | |
michael@0 | 83 | # Work around a bug in Python 2.7.2 and lower where unicode types in |
michael@0 | 84 | # environment variables aren't handled by subprocess. |
michael@0 | 85 | for k, v in env.items(): |
michael@0 | 86 | if isinstance(v, unicode): |
michael@0 | 87 | env[k] = v.encode('utf-8') |
michael@0 | 88 | |
michael@0 | 89 | print >>errors.out, 'Executing', ' '.join(cmd) |
michael@0 | 90 | errors.out.flush() |
michael@0 | 91 | return subprocess.call(cmd, env=env) |
michael@0 | 92 | |
michael@0 | 93 | def can_launch(self): |
michael@0 | 94 | return self.tooldir is not None |
michael@0 | 95 | |
michael@0 | 96 | launcher = ToolLauncher() |
michael@0 | 97 | |
michael@0 | 98 | |
michael@0 | 99 | class LibSignFile(File): |
michael@0 | 100 | ''' |
michael@0 | 101 | File class for shlibsign signatures. |
michael@0 | 102 | ''' |
michael@0 | 103 | def copy(self, dest, skip_if_older=True): |
michael@0 | 104 | assert isinstance(dest, basestring) |
michael@0 | 105 | # os.path.getmtime returns a result in seconds with precision up to the |
michael@0 | 106 | # microsecond. But microsecond is too precise because shutil.copystat |
michael@0 | 107 | # only copies milliseconds, and seconds is not enough precision. |
michael@0 | 108 | if os.path.exists(dest) and skip_if_older and \ |
michael@0 | 109 | int(os.path.getmtime(self.path) * 1000) <= \ |
michael@0 | 110 | int(os.path.getmtime(dest) * 1000): |
michael@0 | 111 | return False |
michael@0 | 112 | if launcher.launch(['shlibsign', '-v', '-o', dest, '-i', self.path]): |
michael@0 | 113 | errors.fatal('Error while signing %s' % self.path) |
michael@0 | 114 | |
michael@0 | 115 | |
michael@0 | 116 | def precompile_cache(formatter, source_path, gre_path, app_path): |
michael@0 | 117 | ''' |
michael@0 | 118 | Create startup cache for the given application directory, using the |
michael@0 | 119 | given GRE path. |
michael@0 | 120 | - formatter is a Formatter instance where to add the startup cache. |
michael@0 | 121 | - source_path is the base path of the package. |
michael@0 | 122 | - gre_path is the GRE path, relative to source_path. |
michael@0 | 123 | - app_path is the application path, relative to source_path. |
michael@0 | 124 | Startup cache for all resources under resource://app/ are generated, |
michael@0 | 125 | except when gre_path == app_path, in which case it's under |
michael@0 | 126 | resource://gre/. |
michael@0 | 127 | ''' |
michael@0 | 128 | from tempfile import mkstemp |
michael@0 | 129 | source_path = os.path.abspath(source_path) |
michael@0 | 130 | if app_path != gre_path: |
michael@0 | 131 | resource = 'app' |
michael@0 | 132 | else: |
michael@0 | 133 | resource = 'gre' |
michael@0 | 134 | app_path = os.path.join(source_path, app_path) |
michael@0 | 135 | gre_path = os.path.join(source_path, gre_path) |
michael@0 | 136 | |
michael@0 | 137 | fd, cache = mkstemp('.zip') |
michael@0 | 138 | os.close(fd) |
michael@0 | 139 | os.remove(cache) |
michael@0 | 140 | |
michael@0 | 141 | # For VC12, make sure we can find the right bitness of pgort120.dll |
michael@0 | 142 | env = os.environ.copy() |
michael@0 | 143 | if 'VS120COMNTOOLS' in env and not buildconfig.substs['HAVE_64BIT_OS']: |
michael@0 | 144 | vc12dir = os.path.abspath(os.path.join(env['VS120COMNTOOLS'], |
michael@0 | 145 | '../../VC/bin')) |
michael@0 | 146 | if os.path.exists(vc12dir): |
michael@0 | 147 | env['PATH'] = vc12dir + ';' + env['PATH'] |
michael@0 | 148 | |
michael@0 | 149 | try: |
michael@0 | 150 | if launcher.launch(['xpcshell', '-g', gre_path, '-a', app_path, |
michael@0 | 151 | '-f', os.path.join(os.path.dirname(__file__), |
michael@0 | 152 | 'precompile_cache.js'), |
michael@0 | 153 | '-e', 'precompile_startupcache("resource://%s/");' |
michael@0 | 154 | % resource], |
michael@0 | 155 | extra_linker_path=gre_path, |
michael@0 | 156 | extra_env={'MOZ_STARTUP_CACHE': cache, |
michael@0 | 157 | 'PATH': env['PATH']}): |
michael@0 | 158 | errors.fatal('Error while running startup cache precompilation') |
michael@0 | 159 | return |
michael@0 | 160 | from mozpack.mozjar import JarReader |
michael@0 | 161 | jar = JarReader(cache) |
michael@0 | 162 | resource = '/resource/%s/' % resource |
michael@0 | 163 | for f in jar: |
michael@0 | 164 | if resource in f.filename: |
michael@0 | 165 | path = f.filename[f.filename.index(resource) + len(resource):] |
michael@0 | 166 | if formatter.contains(path): |
michael@0 | 167 | formatter.add(f.filename, GeneratedFile(f.read())) |
michael@0 | 168 | jar.close() |
michael@0 | 169 | finally: |
michael@0 | 170 | if os.path.exists(cache): |
michael@0 | 171 | os.remove(cache) |
michael@0 | 172 | |
michael@0 | 173 | |
michael@0 | 174 | class RemovedFiles(GeneratedFile): |
michael@0 | 175 | ''' |
michael@0 | 176 | File class for removed-files. Is used as a preprocessor parser. |
michael@0 | 177 | ''' |
michael@0 | 178 | def __init__(self, copier): |
michael@0 | 179 | self.copier = copier |
michael@0 | 180 | GeneratedFile.__init__(self, '') |
michael@0 | 181 | |
michael@0 | 182 | def handle_line(self, str): |
michael@0 | 183 | f = str.strip() |
michael@0 | 184 | if self.copier.contains(f): |
michael@0 | 185 | errors.error('Removal of packaged file(s): %s' % f) |
michael@0 | 186 | self.content += f + '\n' |
michael@0 | 187 | |
michael@0 | 188 | |
michael@0 | 189 | def split_define(define): |
michael@0 | 190 | ''' |
michael@0 | 191 | Give a VAR[=VAL] string, returns a (VAR, VAL) tuple, where VAL defaults to |
michael@0 | 192 | 1. Numeric VALs are returned as ints. |
michael@0 | 193 | ''' |
michael@0 | 194 | if '=' in define: |
michael@0 | 195 | name, value = define.split('=', 1) |
michael@0 | 196 | try: |
michael@0 | 197 | value = int(value) |
michael@0 | 198 | except ValueError: |
michael@0 | 199 | pass |
michael@0 | 200 | return (name, value) |
michael@0 | 201 | return (define, 1) |
michael@0 | 202 | |
michael@0 | 203 | |
michael@0 | 204 | class NoPkgFilesRemover(object): |
michael@0 | 205 | ''' |
michael@0 | 206 | Formatter wrapper to handle NO_PKG_FILES. |
michael@0 | 207 | ''' |
michael@0 | 208 | def __init__(self, formatter, has_manifest): |
michael@0 | 209 | assert 'NO_PKG_FILES' in os.environ |
michael@0 | 210 | self._formatter = formatter |
michael@0 | 211 | self._files = os.environ['NO_PKG_FILES'].split() |
michael@0 | 212 | if has_manifest: |
michael@0 | 213 | self._error = errors.error |
michael@0 | 214 | self._msg = 'NO_PKG_FILES contains file listed in manifest: %s' |
michael@0 | 215 | else: |
michael@0 | 216 | self._error = errors.warn |
michael@0 | 217 | self._msg = 'Skipping %s' |
michael@0 | 218 | |
michael@0 | 219 | def add_base(self, base): |
michael@0 | 220 | self._formatter.add_base(base) |
michael@0 | 221 | |
michael@0 | 222 | def add(self, path, content): |
michael@0 | 223 | if not any(mozpack.path.match(path, spec) for spec in self._files): |
michael@0 | 224 | self._formatter.add(path, content) |
michael@0 | 225 | else: |
michael@0 | 226 | self._error(self._msg % path) |
michael@0 | 227 | |
michael@0 | 228 | def add_manifest(self, entry): |
michael@0 | 229 | self._formatter.add_manifest(entry) |
michael@0 | 230 | |
michael@0 | 231 | def add_interfaces(self, path, content): |
michael@0 | 232 | self._formatter.add_interfaces(path, content) |
michael@0 | 233 | |
michael@0 | 234 | def contains(self, path): |
michael@0 | 235 | return self._formatter.contains(path) |
michael@0 | 236 | |
michael@0 | 237 | |
michael@0 | 238 | def main(): |
michael@0 | 239 | parser = ArgumentParser() |
michael@0 | 240 | parser.add_argument('-D', dest='defines', action='append', |
michael@0 | 241 | metavar="VAR[=VAL]", help='Define a variable') |
michael@0 | 242 | parser.add_argument('--format', default='omni', |
michael@0 | 243 | help='Choose the chrome format for packaging ' + |
michael@0 | 244 | '(omni, jar or flat ; default: %(default)s)') |
michael@0 | 245 | parser.add_argument('--removals', default=None, |
michael@0 | 246 | help='removed-files source file') |
michael@0 | 247 | parser.add_argument('--ignore-errors', action='store_true', default=False, |
michael@0 | 248 | help='Transform errors into warnings.') |
michael@0 | 249 | parser.add_argument('--minify', action='store_true', default=False, |
michael@0 | 250 | help='Make some files more compact while packaging') |
michael@0 | 251 | parser.add_argument('--minify-js', action='store_true', |
michael@0 | 252 | help='Minify JavaScript files while packaging.') |
michael@0 | 253 | parser.add_argument('--js-binary', |
michael@0 | 254 | help='Path to js binary. This is used to verify ' |
michael@0 | 255 | 'minified JavaScript. If this is not defined, ' |
michael@0 | 256 | 'minification verification will not be performed.') |
michael@0 | 257 | parser.add_argument('--jarlog', default='', help='File containing jar ' + |
michael@0 | 258 | 'access logs') |
michael@0 | 259 | parser.add_argument('--optimizejars', action='store_true', default=False, |
michael@0 | 260 | help='Enable jar optimizations') |
michael@0 | 261 | parser.add_argument('--unify', default='', |
michael@0 | 262 | help='Base directory of another build to unify with') |
michael@0 | 263 | parser.add_argument('manifest', default=None, nargs='?', |
michael@0 | 264 | help='Manifest file name') |
michael@0 | 265 | parser.add_argument('source', help='Source directory') |
michael@0 | 266 | parser.add_argument('destination', help='Destination directory') |
michael@0 | 267 | parser.add_argument('--non-resource', nargs='+', metavar='PATTERN', |
michael@0 | 268 | default=[], |
michael@0 | 269 | help='Extra files not to be considered as resources') |
michael@0 | 270 | args = parser.parse_args() |
michael@0 | 271 | |
michael@0 | 272 | defines = dict(buildconfig.defines) |
michael@0 | 273 | if args.ignore_errors: |
michael@0 | 274 | errors.ignore_errors() |
michael@0 | 275 | |
michael@0 | 276 | if args.defines: |
michael@0 | 277 | for name, value in [split_define(d) for d in args.defines]: |
michael@0 | 278 | defines[name] = value |
michael@0 | 279 | |
michael@0 | 280 | copier = FileCopier() |
michael@0 | 281 | if args.format == 'flat': |
michael@0 | 282 | formatter = FlatFormatter(copier) |
michael@0 | 283 | elif args.format == 'jar': |
michael@0 | 284 | formatter = JarFormatter(copier, optimize=args.optimizejars) |
michael@0 | 285 | elif args.format == 'omni': |
michael@0 | 286 | formatter = OmniJarFormatter(copier, |
michael@0 | 287 | buildconfig.substs['OMNIJAR_NAME'], |
michael@0 | 288 | optimize=args.optimizejars, |
michael@0 | 289 | non_resources=args.non_resource) |
michael@0 | 290 | else: |
michael@0 | 291 | errors.fatal('Unknown format: %s' % args.format) |
michael@0 | 292 | |
michael@0 | 293 | # Adjust defines according to the requested format. |
michael@0 | 294 | if isinstance(formatter, OmniJarFormatter): |
michael@0 | 295 | defines['MOZ_OMNIJAR'] = 1 |
michael@0 | 296 | elif 'MOZ_OMNIJAR' in defines: |
michael@0 | 297 | del defines['MOZ_OMNIJAR'] |
michael@0 | 298 | |
michael@0 | 299 | binpath = '' |
michael@0 | 300 | if 'BINPATH' in defines: |
michael@0 | 301 | binpath = SimpleManifestSink.normalize_path(defines['BINPATH']) |
michael@0 | 302 | while binpath.startswith('/'): |
michael@0 | 303 | binpath = binpath[1:] |
michael@0 | 304 | |
michael@0 | 305 | if args.unify: |
michael@0 | 306 | def is_native(path): |
michael@0 | 307 | path = os.path.abspath(path) |
michael@0 | 308 | return platform.machine() in mozpack.path.split(path) |
michael@0 | 309 | |
michael@0 | 310 | # Invert args.unify and args.source if args.unify points to the |
michael@0 | 311 | # native architecture. |
michael@0 | 312 | args.source, args.unify = sorted([args.source, args.unify], |
michael@0 | 313 | key=is_native, reverse=True) |
michael@0 | 314 | if is_native(args.source): |
michael@0 | 315 | launcher.tooldir = args.source |
michael@0 | 316 | elif not buildconfig.substs['CROSS_COMPILE']: |
michael@0 | 317 | launcher.tooldir = buildconfig.substs['LIBXUL_DIST'] |
michael@0 | 318 | |
michael@0 | 319 | with errors.accumulate(): |
michael@0 | 320 | finder_args = dict( |
michael@0 | 321 | minify=args.minify, |
michael@0 | 322 | minify_js=args.minify_js, |
michael@0 | 323 | ) |
michael@0 | 324 | if args.js_binary: |
michael@0 | 325 | finder_args['minify_js_verify_command'] = [ |
michael@0 | 326 | args.js_binary, |
michael@0 | 327 | os.path.join(os.path.abspath(os.path.dirname(__file__)), |
michael@0 | 328 | 'js-compare-ast.js') |
michael@0 | 329 | ] |
michael@0 | 330 | if args.unify: |
michael@0 | 331 | finder = UnifiedBuildFinder(FileFinder(args.source), |
michael@0 | 332 | FileFinder(args.unify), |
michael@0 | 333 | **finder_args) |
michael@0 | 334 | else: |
michael@0 | 335 | finder = FileFinder(args.source, **finder_args) |
michael@0 | 336 | if 'NO_PKG_FILES' in os.environ: |
michael@0 | 337 | sinkformatter = NoPkgFilesRemover(formatter, |
michael@0 | 338 | args.manifest is not None) |
michael@0 | 339 | else: |
michael@0 | 340 | sinkformatter = formatter |
michael@0 | 341 | sink = SimpleManifestSink(finder, sinkformatter) |
michael@0 | 342 | if args.manifest: |
michael@0 | 343 | preprocess_manifest(sink, args.manifest, defines) |
michael@0 | 344 | else: |
michael@0 | 345 | sink.add(Component(''), 'bin/*') |
michael@0 | 346 | sink.close(args.manifest is not None) |
michael@0 | 347 | |
michael@0 | 348 | if args.removals: |
michael@0 | 349 | lines = [l.lstrip() for l in open(args.removals).readlines()] |
michael@0 | 350 | removals_in = StringIO(''.join(lines)) |
michael@0 | 351 | removals_in.name = args.removals |
michael@0 | 352 | removals = RemovedFiles(copier) |
michael@0 | 353 | preprocess(removals_in, removals, defines) |
michael@0 | 354 | copier.add(mozpack.path.join(binpath, 'removed-files'), removals) |
michael@0 | 355 | |
michael@0 | 356 | # shlibsign libraries |
michael@0 | 357 | if launcher.can_launch(): |
michael@0 | 358 | for lib in SIGN_LIBS: |
michael@0 | 359 | libbase = mozpack.path.join(binpath, '%s%s') \ |
michael@0 | 360 | % (buildconfig.substs['DLL_PREFIX'], lib) |
michael@0 | 361 | libname = '%s%s' % (libbase, buildconfig.substs['DLL_SUFFIX']) |
michael@0 | 362 | if copier.contains(libname): |
michael@0 | 363 | copier.add(libbase + '.chk', |
michael@0 | 364 | LibSignFile(os.path.join(args.destination, |
michael@0 | 365 | libname))) |
michael@0 | 366 | |
michael@0 | 367 | # Setup preloading |
michael@0 | 368 | if args.jarlog and os.path.exists(args.jarlog): |
michael@0 | 369 | from mozpack.mozjar import JarLog |
michael@0 | 370 | log = JarLog(args.jarlog) |
michael@0 | 371 | for p, f in copier: |
michael@0 | 372 | if not isinstance(f, Jarrer): |
michael@0 | 373 | continue |
michael@0 | 374 | key = JarLog.canonicalize(os.path.join(args.destination, p)) |
michael@0 | 375 | if key in log: |
michael@0 | 376 | f.preload(log[key]) |
michael@0 | 377 | |
michael@0 | 378 | # Fill startup cache |
michael@0 | 379 | if isinstance(formatter, OmniJarFormatter) and launcher.can_launch(): |
michael@0 | 380 | if buildconfig.substs['LIBXUL_SDK']: |
michael@0 | 381 | gre_path = mozpack.path.join(buildconfig.substs['LIBXUL_DIST'], |
michael@0 | 382 | 'bin') |
michael@0 | 383 | else: |
michael@0 | 384 | gre_path = None |
michael@0 | 385 | for base in sorted([[p for p in [mozpack.path.join('bin', b), b] |
michael@0 | 386 | if os.path.exists(os.path.join(args.source, p))][0] |
michael@0 | 387 | for b in sink.packager.get_bases()]): |
michael@0 | 388 | if not gre_path: |
michael@0 | 389 | gre_path = base |
michael@0 | 390 | base_path = sink.normalize_path(base) |
michael@0 | 391 | if base_path in formatter.omnijars: |
michael@0 | 392 | precompile_cache(formatter.omnijars[base_path], |
michael@0 | 393 | args.source, gre_path, base) |
michael@0 | 394 | |
michael@0 | 395 | copier.copy(args.destination) |
michael@0 | 396 | generate_precomplete(os.path.normpath(os.path.join(args.destination, |
michael@0 | 397 | binpath))) |
michael@0 | 398 | |
michael@0 | 399 | |
michael@0 | 400 | if __name__ == '__main__': |
michael@0 | 401 | main() |