diff options
Diffstat (limited to '')
-rwxr-xr-x | js/src/make-source-package.py | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/js/src/make-source-package.py b/js/src/make-source-package.py new file mode 100755 index 0000000000..edef6001da --- /dev/null +++ b/js/src/make-source-package.py @@ -0,0 +1,447 @@ +#!/usr/bin/env -S python3 -B +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import argparse +import enum +import logging +import os +import shutil +import stat +import subprocess +import sys +from pathlib import Path + +logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.INFO) + + +def find_command(names): + """Search for command in `names`, and returns the first one that exists.""" + + for name in names: + path = shutil.which(name) + if path is not None: + return name + + return None + + +def assert_command(env_var, name): + """Assert that the command is not empty + The command name comes from either environment variable or find_command. + """ + if not name: + logging.error("{} command not found".format(env_var)) + sys.exit(1) + + +def parse_version(topsrc_dir): + """Parse milestone.txt and return the entire milestone and major version.""" + milestone_file = topsrc_dir / "config" / "milestone.txt" + if not milestone_file.is_file(): + return ("", "", "") + + with milestone_file.open("r") as f: + for line in f: + line = line.strip() + if not line: + continue + if line.startswith("#"): + continue + + v = line.split(".") + return tuple((v + ["", ""])[:3]) + + return ("", "", "") + + +tmp_dir = Path("/tmp") + +tar = os.environ.get("TAR", find_command(["tar"])) +assert_command("TAR", tar) + +rsync = os.environ.get("RSYNC", find_command(["rsync"])) +assert_command("RSYNC", rsync) + +autoconf = os.environ.get( + "AUTOCONF", + find_command( + [ + "autoconf-2.13", + "autoconf2.13", + "autoconf213", + ] + ), +) +assert_command("AUTOCONF", autoconf) + +src_dir = Path(os.environ.get("SRC_DIR", Path(__file__).parent.absolute())) +mozjs_name = os.environ.get("MOZJS_NAME", "mozjs") +staging_dir = Path(os.environ.get("STAGING", tmp_dir / "mozjs-src-pkg")) +dist_dir = Path(os.environ.get("DIST", tmp_dir)) +topsrc_dir = src_dir.parent.parent.absolute() + +parsed_major_version, parsed_minor_version, parsed_patch_version = parse_version( + topsrc_dir +) + +major_version = os.environ.get("MOZJS_MAJOR_VERSION", parsed_major_version) +minor_version = os.environ.get("MOZJS_MINOR_VERSION", parsed_minor_version) +patch_version = os.environ.get("MOZJS_PATCH_VERSION", parsed_patch_version) +alpha = os.environ.get("MOZJS_ALPHA", "") + +version = "{}-{}.{}.{}".format( + mozjs_name, major_version, minor_version, patch_version or alpha or "0" +) +target_dir = staging_dir / version +package_name = "{}.tar.xz".format(version) +package_file = dist_dir / package_name +tar_opts = ["-Jcf"] + +# Given there might be some external program that reads the following output, +# use raw `print`, instead of logging. +print("Environment:") +print(" TAR = {}".format(tar)) +print(" RSYNC = {}".format(rsync)) +print(" AUTOCONF = {}".format(autoconf)) +print(" STAGING = {}".format(staging_dir)) +print(" DIST = {}".format(dist_dir)) +print(" SRC_DIR = {}".format(src_dir)) +print(" MOZJS_NAME = {}".format(mozjs_name)) +print(" MOZJS_MAJOR_VERSION = {}".format(major_version)) +print(" MOZJS_MINOR_VERSION = {}".format(minor_version)) +print(" MOZJS_PATCH_VERSION = {}".format(patch_version)) +print(" MOZJS_ALPHA = {}".format(alpha)) +print("") + +rsync_filter_list = """ +# Top-level config and build files + ++ /configure.py ++ /LICENSE ++ /Makefile.in ++ /moz.build ++ /moz.configure ++ /test.mozbuild ++ /.babel-eslint.rc.js ++ .eslintignore ++ .eslintrc.js ++ .flake8 ++ .gitignore ++ .hgignore ++ .lldbinit ++ .prettierignore ++ .prettierrc ++ .ycm_extra_conf.py + +# Additional libraries (optionally) used by SpiderMonkey + ++ /mfbt/** ++ /nsprpub/** + +- /intl/icu/source/data +- /intl/icu/source/test +- /intl/icu/source/tools ++ /intl/icu/** + ++ /memory/replace/dmd/dmd.py ++ /memory/build/** ++ /memory/moz.build ++ /memory/mozalloc/** + ++ /modules/fdlibm/** ++ /modules/zlib/** + ++ /mozglue/baseprofiler/** ++ /mozglue/build/** ++ /mozglue/misc/** ++ /mozglue/moz.build ++ /mozglue/static/** + ++ /tools/rb/fix_stacks.py ++ /tools/fuzzing/moz.build ++ /tools/fuzzing/interface/** ++ /tools/fuzzing/registry/** ++ /tools/fuzzing/libfuzzer/** + +# Build system and dependencies + ++ /Cargo.lock ++ /build/** ++ /config/** ++ /python/** + ++ /.cargo/config.in + +- /third_party/python/gyp ++ /third_party/python/** ++ /third_party/rust/** + ++ /layout/tools/reftest/reftest/** + ++ /testing/moz.build ++ /testing/mozbase/** ++ /testing/performance/** ++ /testing/web-platform/tests/streams/** + ++ /toolkit/crashreporter/tools/symbolstore.py ++ /toolkit/mozapps/installer/package-name.mk + +# SpiderMonkey itself + ++ /js/src/** ++ /js/app.mozbuild ++ /js/*.configure ++ /js/examples/** ++ /js/public/** ++ /js/rust/** + ++ */ +- /** +""" + +INSTALL_CONTENT = """\ +Full build documentation for SpiderMonkey is hosted on MDN: + https://developer.mozilla.org/en-US/docs/SpiderMonkey/Build_Documentation + +Note that the libraries produced by the build system include symbols, +causing the binaries to be extremely large. It is highly suggested that `strip` +be run over the binaries before deploying them. + +Building with default options may be performed as follows: + cd js/src + mkdir obj + cd obj + ../configure + make # or mozmake on Windows +""" + +README_CONTENT = """\ +This directory contains SpiderMonkey {major_version}. + +This release is based on a revision of Mozilla {major_version}: + https://hg.mozilla.org/releases/ +The changes in the patches/ directory were applied. + +MDN hosts the latest SpiderMonkey {major_version} release notes: + https://developer.mozilla.org/en-US/docs/SpiderMonkey/{major_version} +""".format( + major_version=major_version +) + + +def is_mozjs_cargo_member(line): + """Checks if the line in workspace.members is mozjs-related""" + + return '"js/' in line + + +def is_mozjs_crates_io_local_patch(line): + """Checks if the line in patch.crates-io is mozjs-related""" + + return 'path = "js' in line + + +def clean(): + """Remove temporary directory and package file.""" + logging.info("Cleaning {} and {} ...".format(package_file, target_dir)) + if package_file.exists(): + package_file.unlink() + if target_dir.exists(): + shutil.rmtree(str(target_dir)) + + +def assert_clean(): + """Assert that target directory does not contain generated files.""" + makefile_file = target_dir / "js" / "src" / "Makefile" + if makefile_file.exists(): + logging.error("found js/src/Makefile. Please clean before packaging.") + sys.exit(1) + + +def create_target_dir(): + if target_dir.exists(): + logging.warning("dist tree {} already exists!".format(target_dir)) + else: + target_dir.mkdir(parents=True) + + +def sync_files(): + # Output of the command should directly go to stdout/stderr. + p = subprocess.Popen( + [ + str(rsync), + "--delete-excluded", + "--prune-empty-dirs", + "--quiet", + "--recursive", + "{}/".format(topsrc_dir), + "{}/".format(target_dir), + "--filter=. -", + ], + stdin=subprocess.PIPE, + ) + + p.communicate(rsync_filter_list.encode()) + + if p.returncode != 0: + sys.exit(p.returncode) + + +def copy_cargo_toml(): + cargo_toml_file = topsrc_dir / "Cargo.toml" + target_cargo_toml_file = target_dir / "Cargo.toml" + + with cargo_toml_file.open("r") as f: + + class State(enum.Enum): + BEFORE_MEMBER = 1 + INSIDE_MEMBER = 2 + AFTER_MEMBER = 3 + INSIDE_PATCH = 4 + AFTER_PATCH = 5 + + content = "" + state = State.BEFORE_MEMBER + for line in f: + if state == State.BEFORE_MEMBER: + if line.strip() == "members = [": + state = State.INSIDE_MEMBER + elif state == State.INSIDE_MEMBER: + if line.strip() == "]": + state = State.AFTER_MEMBER + elif not is_mozjs_cargo_member(line): + continue + elif state == State.AFTER_MEMBER: + if line.strip() == "[patch.crates-io]": + state = State.INSIDE_PATCH + elif state == State.INSIDE_PATCH: + if line.startswith("["): + state = State.AFTER_PATCH + if "path = " in line: + if not is_mozjs_crates_io_local_patch(line): + continue + + content += line + + with target_cargo_toml_file.open("w") as f: + f.write(content) + + +def generate_configure(): + """Generate configure files to avoid build dependency on autoconf-2.13""" + + src_configure_in_file = topsrc_dir / "js" / "src" / "configure.in" + src_old_configure_in_file = topsrc_dir / "js" / "src" / "old-configure.in" + dest_configure_file = target_dir / "js" / "src" / "configure" + dest_old_configure_file = target_dir / "js" / "src" / "old-configure" + + shutil.copy2( + str(src_configure_in_file), str(dest_configure_file), follow_symlinks=False + ) + st = dest_configure_file.stat() + dest_configure_file.chmod(st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + + js_src_dir = topsrc_dir / "js" / "src" + + with dest_old_configure_file.open("w") as f: + subprocess.run( + [ + str(autoconf), + "--localdir={}".format(js_src_dir), + str(src_old_configure_in_file), + ], + stdout=f, + check=True, + ) + + +def copy_install(): + """Copy or create INSTALL.""" + + staging_install_file = staging_dir / "INSTALL" + target_install_file = target_dir / "INSTALL" + + if staging_install_file.exists(): + shutil.copy2(str(staging_install_file), str(target_install_file)) + else: + with target_install_file.open("w") as f: + f.write(INSTALL_CONTENT) + + +def copy_readme(): + """Copy or create README.""" + + staging_readme_file = staging_dir / "README" + target_readme_file = target_dir / "README" + + if staging_readme_file.exists(): + shutil.copy2(str(staging_readme_file), str(target_readme_file)) + else: + with target_readme_file.open("w") as f: + f.write(README_CONTENT) + + +def copy_patches(): + """Copy patches dir, if it exists.""" + + staging_patches_dir = staging_dir / "patches" + top_patches_dir = topsrc_dir / "patches" + target_patches_dir = target_dir / "patches" + + if staging_patches_dir.is_dir(): + shutil.copytree(str(staging_patches_dir), str(target_patches_dir)) + elif top_patches_dir.is_dir(): + shutil.copytree(str(top_patches_dir), str(target_patches_dir)) + + +def remove_python_cache(): + """Remove *.pyc and *.pyo files if any.""" + for f in target_dir.glob("**/*.pyc"): + f.unlink() + for f in target_dir.glob("**/*.pyo"): + f.unlink() + + +def stage(): + """Stage source tarball content.""" + logging.info("Staging source tarball in {}...".format(target_dir)) + + create_target_dir() + sync_files() + copy_cargo_toml() + generate_configure() + copy_install() + copy_readme() + copy_patches() + remove_python_cache() + + +def create_tar(): + """Roll the tarball.""" + + logging.info("Packaging source tarball at {}...".format(package_file)) + + subprocess.run( + [str(tar)] + tar_opts + [str(package_file), "-C", str(staging_dir), version], + check=True, + ) + + +def build(): + assert_clean() + stage() + create_tar() + + +parser = argparse.ArgumentParser(description="Make SpiderMonkey source package") +subparsers = parser.add_subparsers(dest="COMMAND") +subparser_update = subparsers.add_parser("clean", help="") +subparser_update = subparsers.add_parser("build", help="") +args = parser.parse_args() + +if args.COMMAND == "clean": + clean() +elif not args.COMMAND or args.COMMAND == "build": + build() |