1#!/usr/bin/env python3 -B 2# This Source Code Form is subject to the terms of the Mozilla Public 3# License, v. 2.0. If a copy of the MPL was not distributed with this file, 4# You can obtain one at http://mozilla.org/MPL/2.0/. 5 6import argparse 7import enum 8import logging 9import os 10import shutil 11import stat 12import subprocess 13import sys 14from pathlib import Path 15 16logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO) 17 18 19def find_command(names): 20 """Search for command in `names`, and returns the first one that exists. 21 """ 22 23 for name in names: 24 path = shutil.which(name) 25 if path is not None: 26 return name 27 28 return None 29 30 31def assert_command(env_var, name): 32 """Assert that the command is not empty 33 The command name comes from either environment variable or find_command. 34 """ 35 if not name: 36 logging.error('{} command not found'.format(env_var)) 37 sys.exit(1) 38 39 40def parse_version(topsrc_dir): 41 """Parse milestone.txt and return the entire milestone and major version. 42 """ 43 milestone_file = topsrc_dir / 'config' / 'milestone.txt' 44 if not milestone_file.is_file(): 45 return ('', '', '') 46 47 with milestone_file.open('r') as f: 48 for line in f: 49 line = line.strip() 50 if not line: 51 continue 52 if line.startswith('#'): 53 continue 54 55 v = line.split('.') 56 return tuple((v + ['', ''])[:3]) 57 58 return ('', '', '') 59 60 61tmp_dir = Path('/tmp') 62 63tar = os.environ.get('TAR', find_command(['tar'])) 64assert_command('TAR', tar) 65 66rsync = os.environ.get('RSYNC', find_command(['rsync'])) 67assert_command('RSYNC', rsync) 68 69autoconf = os.environ.get('AUTOCONF', find_command([ 70 'autoconf-2.13', 71 'autoconf2.13', 72 'autoconf213', 73])) 74assert_command('AUTOCONF', autoconf) 75 76src_dir = Path(os.environ.get('SRC_DIR', Path(__file__).parent.absolute())) 77mozjs_name = os.environ.get('MOZJS_NAME', 'mozjs') 78staging_dir = Path(os.environ.get('STAGING', tmp_dir / 'mozjs-src-pkg')) 79dist_dir = Path(os.environ.get('DIST', tmp_dir)) 80topsrc_dir = src_dir.parent.parent.absolute() 81 82parsed_major_version, parsed_minor_version, parsed_patch_version = parse_version(topsrc_dir) 83 84major_version = os.environ.get('MOZJS_MAJOR_VERSION', parsed_major_version) 85minor_version = os.environ.get('MOZJS_MINOR_VERSION', parsed_minor_version) 86patch_version = os.environ.get('MOZJS_PATCH_VERSION', parsed_patch_version) 87alpha = os.environ.get('MOZJS_ALPHA', '') 88 89version = '{}-{}.{}.{}'.format(mozjs_name, 90 major_version, 91 minor_version, 92 patch_version or alpha or '0') 93target_dir = staging_dir / version 94package_name = '{}.tar.bz2'.format(version) 95package_file = dist_dir / package_name 96tar_opts = ['-jcf'] 97 98# Given there might be some external program that reads the following output, 99# use raw `print`, instead of logging. 100print('Environment:') 101print(' TAR = {}'.format(tar)) 102print(' RSYNC = {}'.format(rsync)) 103print(' AUTOCONF = {}'.format(autoconf)) 104print(' STAGING = {}'.format(staging_dir)) 105print(' DIST = {}'.format(dist_dir)) 106print(' SRC_DIR = {}'.format(src_dir)) 107print(' MOZJS_NAME = {}'.format(mozjs_name)) 108print(' MOZJS_MAJOR_VERSION = {}'.format(major_version)) 109print(' MOZJS_MINOR_VERSION = {}'.format(minor_version)) 110print(' MOZJS_PATCH_VERSION = {}'.format(patch_version)) 111print(' MOZJS_ALPHA = {}'.format(alpha)) 112print('') 113 114rsync_filter_list = """ 115# Top-level config and build files 116 117+ /configure.py 118+ /LICENSE 119+ /Makefile.in 120+ /moz.build 121+ /moz.configure 122+ /test.mozbuild 123 124# Additional libraries (optionally) used by SpiderMonkey 125 126+ /mfbt/** 127+ /nsprpub/** 128 129- /intl/icu/source/data 130- /intl/icu/source/test 131- /intl/icu/source/tools 132+ /intl/icu/** 133 134+ /memory/build/** 135+ /memory/moz.build 136+ /memory/mozalloc/** 137 138+ /modules/fdlibm/** 139+ /modules/zlib/** 140 141+ /mozglue/baseprofiler/** 142+ /mozglue/build/** 143+ /mozglue/misc/** 144+ /mozglue/moz.build 145+ /mozglue/static/** 146 147+ /tools/fuzzing/moz.build 148+ /tools/fuzzing/interface/** 149+ /tools/fuzzing/registry/** 150+ /tools/fuzzing/libfuzzer/** 151 152# Build system and dependencies 153 154+ /Cargo.lock 155+ /build/** 156+ /config/** 157+ /python/** 158 159+ /.cargo/config.in 160 161- /third_party/python/gyp 162+ /third_party/python/** 163+ /third_party/rust/** 164 165+ /layout/tools/reftest/reftest/** 166 167+ /testing/mozbase/** 168+ /testing/web-platform/tests/streams/** 169 170+ /toolkit/crashreporter/tools/symbolstore.py 171+ /toolkit/mozapps/installer/package-name.mk 172 173# SpiderMonkey itself 174 175+ /js/src/** 176+ /js/app.mozbuild 177+ /js/*.configure 178+ /js/examples/** 179+ /js/public/** 180+ /js/rust/** 181 182+ */ 183- /** 184""" 185 186INSTALL_CONTENT = """\ 187Full build documentation for SpiderMonkey is hosted on MDN: 188 https://developer.mozilla.org/en-US/docs/SpiderMonkey/Build_Documentation 189 190Note that the libraries produced by the build system include symbols, 191causing the binaries to be extremely large. It is highly suggested that `strip` 192be run over the binaries before deploying them. 193 194Building with default options may be performed as follows: 195 cd js/src 196 mkdir obj 197 cd obj 198 ../configure 199 make # or mozmake on Windows 200""" 201 202README_CONTENT = """\ 203This directory contains SpiderMonkey {major_version}. 204 205This release is based on a revision of Mozilla {major_version}: 206 https://hg.mozilla.org/releases/ 207The changes in the patches/ directory were applied. 208 209MDN hosts the latest SpiderMonkey {major_version} release notes: 210 https://developer.mozilla.org/en-US/docs/SpiderMonkey/{major_version} 211""".format(major_version=major_version) 212 213 214def is_mozjs_cargo_member(line): 215 """Checks if the line in workspace.members is mozjs-related 216 """ 217 218 return '"js/' in line 219 220 221def is_mozjs_crates_io_local_patch(line): 222 """Checks if the line in patch.crates-io is mozjs-related 223 """ 224 225 return 'path = "js' in line 226 227 228def clean(): 229 """Remove temporary directory and package file. 230 """ 231 logging.info('Cleaning {} and {} ...'.format(package_file, target_dir)) 232 package_file.unlink() 233 shutil.rmtree(str(target_dir)) 234 235 236def assert_clean(): 237 """Assert that target directory does not contain generated files. 238 """ 239 makefile_file = target_dir / 'js' / 'src' / 'Makefile' 240 if makefile_file.exists(): 241 logging.error('found js/src/Makefile. Please clean before packaging.') 242 sys.exit(1) 243 244 245def create_target_dir(): 246 if target_dir.exists(): 247 logging.warning('dist tree {} already exists!'.format(target_dir)) 248 else: 249 target_dir.mkdir(parents=True) 250 251 252def sync_files(): 253 # Output of the command should directly go to stdout/stderr. 254 p = subprocess.Popen([str(rsync), 255 '--delete-excluded', 256 '--prune-empty-dirs', 257 '--quiet', 258 '--recursive', 259 '{}/'.format(topsrc_dir), 260 '{}/'.format(target_dir), 261 '--filter=. -'], 262 stdin=subprocess.PIPE) 263 264 p.communicate(rsync_filter_list.encode()) 265 266 if p.returncode != 0: 267 sys.exit(p.returncode) 268 269 270def copy_cargo_toml(): 271 cargo_toml_file = topsrc_dir / 'Cargo.toml' 272 target_cargo_toml_file = target_dir / 'Cargo.toml' 273 274 with cargo_toml_file.open('r') as f: 275 class State(enum.Enum): 276 BEFORE_MEMBER = 1 277 INSIDE_MEMBER = 2 278 AFTER_MEMBER = 3 279 INSIDE_PATCH = 4 280 AFTER_PATCH = 5 281 282 content = '' 283 state = State.BEFORE_MEMBER 284 for line in f: 285 if state == State.BEFORE_MEMBER: 286 if line.strip() == 'members = [': 287 state = State.INSIDE_MEMBER 288 elif state == State.INSIDE_MEMBER: 289 if line.strip() == ']': 290 state = State.AFTER_MEMBER 291 elif not is_mozjs_cargo_member(line): 292 continue 293 elif state == State.AFTER_MEMBER: 294 if line.strip() == '[patch.crates-io]': 295 state = State.INSIDE_PATCH 296 elif state == State.INSIDE_PATCH: 297 if line.startswith('['): 298 state = State.AFTER_PATCH 299 if 'path = ' in line: 300 if not is_mozjs_crates_io_local_patch(line): 301 continue 302 303 content += line 304 305 with target_cargo_toml_file.open('w') as f: 306 f.write(content) 307 308 309def generate_configure(): 310 """Generate configure files to avoid build dependency on autoconf-2.13 311 """ 312 313 src_configure_in_file = topsrc_dir / 'js' / 'src' / 'configure.in' 314 src_old_configure_in_file = topsrc_dir / 'js' / 'src' / 'old-configure.in' 315 dest_configure_file = target_dir / 'js' / 'src' / 'configure' 316 dest_old_configure_file = target_dir / 'js' / 'src' / 'old-configure' 317 318 shutil.copy2(str(src_configure_in_file), str(dest_configure_file), 319 follow_symlinks=False) 320 st = dest_configure_file.stat() 321 dest_configure_file.chmod( 322 st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) 323 324 js_src_dir = topsrc_dir / 'js' / 'src' 325 326 with dest_old_configure_file.open('w') as f: 327 subprocess.run([str(autoconf), 328 '--localdir={}'.format(js_src_dir), 329 str(src_old_configure_in_file)], 330 stdout=f, 331 check=True) 332 333 334def copy_install(): 335 """Copy or create INSTALL. 336 """ 337 338 staging_install_file = staging_dir / 'INSTALL' 339 target_install_file = target_dir / 'INSTALL' 340 341 if staging_install_file.exists(): 342 shutil.copy2(str(staging_install_file), str(target_install_file)) 343 else: 344 with target_install_file.open('w') as f: 345 f.write(INSTALL_CONTENT) 346 347 348def copy_readme(): 349 """Copy or create README. 350 """ 351 352 staging_readme_file = staging_dir / 'README' 353 target_readme_file = target_dir / 'README' 354 355 if staging_readme_file.exists(): 356 shutil.copy2(str(staging_readme_file), str(target_readme_file)) 357 else: 358 with target_readme_file.open('w') as f: 359 f.write(README_CONTENT) 360 361 362def copy_patches(): 363 """Copy patches dir, if it exists. 364 """ 365 366 staging_patches_dir = staging_dir / 'patches' 367 top_patches_dir = topsrc_dir / 'patches' 368 target_patches_dir = target_dir / 'patches' 369 370 if staging_patches_dir.is_dir(): 371 shutil.copytree(str(staging_patches_dir), str(target_patches_dir)) 372 elif top_patches_dir.is_dir(): 373 shutil.copytree(str(top_patches_dir), str(target_patches_dir)) 374 375 376def remove_python_cache(): 377 """Remove *.pyc and *.pyo files if any. 378 """ 379 for f in target_dir.glob('**/*.pyc'): 380 f.unlink() 381 for f in target_dir.glob('**/*.pyo'): 382 f.unlink() 383 384 385def stage(): 386 """Stage source tarball content. 387 """ 388 logging.info('Staging source tarball in {}...'.format(target_dir)) 389 390 create_target_dir() 391 sync_files() 392 copy_cargo_toml() 393 generate_configure() 394 copy_install() 395 copy_readme() 396 copy_patches() 397 remove_python_cache() 398 399 400def create_tar(): 401 """Roll the tarball. 402 """ 403 404 logging.info('Packaging source tarball at {}...'.format(package_file)) 405 406 subprocess.run([str(tar)] + tar_opts + [ 407 str(package_file), 408 '-C', 409 str(staging_dir), 410 version 411 ], check=True) 412 413 414def build(): 415 assert_clean() 416 stage() 417 create_tar() 418 419 420parser = argparse.ArgumentParser(description="Make SpiderMonkey source package") 421subparsers = parser.add_subparsers(dest='COMMAND') 422subparser_update = subparsers.add_parser('clean', 423 help='') 424subparser_update = subparsers.add_parser('build', 425 help='') 426args = parser.parse_args() 427 428if args.COMMAND == 'clean': 429 clean() 430elif not args.COMMAND or args.COMMAND == 'build': 431 build() 432