From 4513656785728af99673e580cfdd94372a5cb63d Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Tue, 24 Feb 2026 14:59:16 +0100 Subject: [PATCH 1/6] gh-145177: Support multiple Emscripten versions for Emscripten buildbot This adds an `--emsdk-cache` argument to the Emscripten build script and an emscripten_version.txt file. If the `--emsdk-cache` argument is passed, the build script will look in emscripten_version.txt to get the expected emsdk version is installed in a folder called e.g., 4.0.12 in the directory indicated by the `--emsdk-cache` argument. Otherwise, it will exit with an error. --- Tools/wasm/emscripten/__main__.py | 56 ++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/Tools/wasm/emscripten/__main__.py b/Tools/wasm/emscripten/__main__.py index c88e9edba6d230..1c80dec01bf899 100644 --- a/Tools/wasm/emscripten/__main__.py +++ b/Tools/wasm/emscripten/__main__.py @@ -22,6 +22,7 @@ EMSCRIPTEN_DIR = Path(__file__).parent CHECKOUT = EMSCRIPTEN_DIR.parent.parent.parent +EMSCRIPTEN_VERSION_FILE = EMSCRIPTEN_DIR / "emscripten_version.txt" CROSS_BUILD_DIR = CHECKOUT / "cross-build" NATIVE_BUILD_DIR = CROSS_BUILD_DIR / "build" @@ -36,7 +37,32 @@ LOCAL_SETUP_MARKER = b"# Generated by Tools/wasm/emscripten.py\n" -def updated_env(updates={}): +@functools.cache +def get_required_emscripten_version(): + """Read the required emscripten version from emscripten_version.txt.""" + return EMSCRIPTEN_VERSION_FILE.read_text().strip() + + +@functools.cache +def get_emsdk_activate_path(emsdk_cache): + required_version = get_required_emscripten_version() + return Path(emsdk_cache) / required_version / "emsdk_env.sh" + + +def validate_emsdk_version(emsdk_cache): + """Validate that the emsdk cache contains the required emscripten version.""" + required_version = get_required_emscripten_version() + emsdk_env = get_emsdk_activate_path(emsdk_cache) + if not emsdk_env.is_file(): + print( + f"Required emscripten version {required_version} not found in {emsdk_cache}", + file=sys.stderr, + ) + sys.exit(1) + print(f"โœ… Emscripten version {required_version} found in {emsdk_cache}") + + +def updated_env(updates, emsdk_cache): """Create a new dict representing the environment to use. The changes made to the execution environment are printed out. @@ -53,6 +79,15 @@ def updated_env(updates={}): pass # Might be building from a tarball. # This layering lets SOURCE_DATE_EPOCH from os.environ takes precedence. environment = env_defaults | os.environ | updates + if emsdk_cache: + env = os.environ.copy() + call( + ["bash", "-c", f"source {get_emsdk_activate_path(emsdk_cache)}"], + env=env, + quiet=False, + ) + for key in ["PATH", "EMSDK", "EMSDK_NODE"]: + environment[key] = env[key] env_diff = {} for key, value in environment.items(): @@ -204,7 +239,7 @@ def make_emscripten_libffi(context, working_dir): ) call( [EMSCRIPTEN_DIR / "make_libffi.sh"], - env=updated_env({"PREFIX": PREFIX_DIR}), + env=updated_env({"PREFIX": PREFIX_DIR}, context.emsdk_cache), cwd=libffi_dir, quiet=context.quiet, ) @@ -231,6 +266,7 @@ def make_mpdec(context, working_dir): ], cwd=mpdec_dir, quiet=context.quiet, + env=updated_env({}, context.emsdk_cache), ) call( ["make", "install"], @@ -300,7 +336,7 @@ def configure_emscripten_python(context, working_dir): configure.extend(context.args) call( configure, - env=updated_env(env_additions), + env=updated_env(env_additions, context.emsdk_cache), quiet=context.quiet, ) @@ -358,7 +394,7 @@ def make_emscripten_python(context, working_dir): """Run `make` for the emscripten/host build.""" call( ["make", "--jobs", str(cpu_count()), "all"], - env=updated_env(), + env=updated_env({}, context.emsdk_cache), quiet=context.quiet, ) @@ -439,6 +475,14 @@ def main(): dest="quiet", help="Redirect output from subprocesses to a log file", ) + subcommand.add_argument( + "--emsdk-cache", + action="store", + default=None, + dest="emsdk_cache", + help="Path to emsdk cache directory. If provided, validates that " + "the required emscripten version is installed.", + ) for subcommand in configure_build, configure_host: subcommand.add_argument( "--clean", @@ -463,6 +507,10 @@ def main(): context = parser.parse_args() + if context.emsdk_cache: + validate_emsdk_version(context.emsdk_cache) + context.emsdk_cache = Path(context.emsdk_cache).absolute() + dispatch = { "make-libffi": make_emscripten_libffi, "make-mpdec": make_mpdec, From f1a5336747953abc3bf44f705095561363511488 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Wed, 25 Feb 2026 11:21:59 +0100 Subject: [PATCH 2/6] Move version into global variable --- Tools/wasm/emscripten/__main__.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Tools/wasm/emscripten/__main__.py b/Tools/wasm/emscripten/__main__.py index 1c80dec01bf899..e6f6aa7a6f90c0 100644 --- a/Tools/wasm/emscripten/__main__.py +++ b/Tools/wasm/emscripten/__main__.py @@ -22,7 +22,7 @@ EMSCRIPTEN_DIR = Path(__file__).parent CHECKOUT = EMSCRIPTEN_DIR.parent.parent.parent -EMSCRIPTEN_VERSION_FILE = EMSCRIPTEN_DIR / "emscripten_version.txt" +EMSCRIPTEN_VERSION = "4.0.12" CROSS_BUILD_DIR = CHECKOUT / "cross-build" NATIVE_BUILD_DIR = CROSS_BUILD_DIR / "build" @@ -37,29 +37,21 @@ LOCAL_SETUP_MARKER = b"# Generated by Tools/wasm/emscripten.py\n" -@functools.cache -def get_required_emscripten_version(): - """Read the required emscripten version from emscripten_version.txt.""" - return EMSCRIPTEN_VERSION_FILE.read_text().strip() - - @functools.cache def get_emsdk_activate_path(emsdk_cache): - required_version = get_required_emscripten_version() - return Path(emsdk_cache) / required_version / "emsdk_env.sh" + return Path(emsdk_cache) / EMSCRIPTEN_VERSION / "emsdk_env.sh" def validate_emsdk_version(emsdk_cache): """Validate that the emsdk cache contains the required emscripten version.""" - required_version = get_required_emscripten_version() emsdk_env = get_emsdk_activate_path(emsdk_cache) if not emsdk_env.is_file(): print( - f"Required emscripten version {required_version} not found in {emsdk_cache}", + f"Required emscripten version {EMSCRIPTEN_VERSION} not found in {emsdk_cache}", file=sys.stderr, ) sys.exit(1) - print(f"โœ… Emscripten version {required_version} found in {emsdk_cache}") + print(f"โœ… Emscripten version {EMSCRIPTEN_VERSION} found in {emsdk_cache}") def updated_env(updates, emsdk_cache): @@ -428,6 +420,11 @@ def clean_contents(context): print(f"๐Ÿงน Deleting generated {LOCAL_SETUP} ...") +def print_emscripten_version(context): + """Print the required emscripten version.""" + print(EMSCRIPTEN_VERSION) + + def main(): default_host_runner = "node" @@ -458,6 +455,9 @@ def main(): clean = subcommands.add_parser( "clean", help="Delete files and directories created by this script" ) + emscripten_version_cmd = subcommands.add_parser( + "emscripten-version", help="Print the required emscripten version" + ) for subcommand in ( build, configure_build, @@ -467,6 +467,7 @@ def main(): configure_host, make_host, clean, + emscripten_version_cmd, ): subcommand.add_argument( "--quiet", @@ -520,6 +521,7 @@ def main(): "make-host": make_emscripten_python, "build": build_all, "clean": clean_contents, + "emscripten-version": print_emscripten_version, } if not context.subcommand: From e2a1afff9be741398641ffc5ad9bfc4ec7ecbe07 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Wed, 4 Mar 2026 10:02:49 +0100 Subject: [PATCH 3/6] Revert "Move version into global variable" This reverts commit f1a5336747953abc3bf44f705095561363511488. --- Tools/wasm/emscripten/__main__.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/Tools/wasm/emscripten/__main__.py b/Tools/wasm/emscripten/__main__.py index e6f6aa7a6f90c0..1c80dec01bf899 100644 --- a/Tools/wasm/emscripten/__main__.py +++ b/Tools/wasm/emscripten/__main__.py @@ -22,7 +22,7 @@ EMSCRIPTEN_DIR = Path(__file__).parent CHECKOUT = EMSCRIPTEN_DIR.parent.parent.parent -EMSCRIPTEN_VERSION = "4.0.12" +EMSCRIPTEN_VERSION_FILE = EMSCRIPTEN_DIR / "emscripten_version.txt" CROSS_BUILD_DIR = CHECKOUT / "cross-build" NATIVE_BUILD_DIR = CROSS_BUILD_DIR / "build" @@ -37,21 +37,29 @@ LOCAL_SETUP_MARKER = b"# Generated by Tools/wasm/emscripten.py\n" +@functools.cache +def get_required_emscripten_version(): + """Read the required emscripten version from emscripten_version.txt.""" + return EMSCRIPTEN_VERSION_FILE.read_text().strip() + + @functools.cache def get_emsdk_activate_path(emsdk_cache): - return Path(emsdk_cache) / EMSCRIPTEN_VERSION / "emsdk_env.sh" + required_version = get_required_emscripten_version() + return Path(emsdk_cache) / required_version / "emsdk_env.sh" def validate_emsdk_version(emsdk_cache): """Validate that the emsdk cache contains the required emscripten version.""" + required_version = get_required_emscripten_version() emsdk_env = get_emsdk_activate_path(emsdk_cache) if not emsdk_env.is_file(): print( - f"Required emscripten version {EMSCRIPTEN_VERSION} not found in {emsdk_cache}", + f"Required emscripten version {required_version} not found in {emsdk_cache}", file=sys.stderr, ) sys.exit(1) - print(f"โœ… Emscripten version {EMSCRIPTEN_VERSION} found in {emsdk_cache}") + print(f"โœ… Emscripten version {required_version} found in {emsdk_cache}") def updated_env(updates, emsdk_cache): @@ -420,11 +428,6 @@ def clean_contents(context): print(f"๐Ÿงน Deleting generated {LOCAL_SETUP} ...") -def print_emscripten_version(context): - """Print the required emscripten version.""" - print(EMSCRIPTEN_VERSION) - - def main(): default_host_runner = "node" @@ -455,9 +458,6 @@ def main(): clean = subcommands.add_parser( "clean", help="Delete files and directories created by this script" ) - emscripten_version_cmd = subcommands.add_parser( - "emscripten-version", help="Print the required emscripten version" - ) for subcommand in ( build, configure_build, @@ -467,7 +467,6 @@ def main(): configure_host, make_host, clean, - emscripten_version_cmd, ): subcommand.add_argument( "--quiet", @@ -521,7 +520,6 @@ def main(): "make-host": make_emscripten_python, "build": build_all, "clean": clean_contents, - "emscripten-version": print_emscripten_version, } if not context.subcommand: From d063eb4cfcb9b5dceb5678901d80926816430304 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Wed, 4 Mar 2026 10:48:04 +0100 Subject: [PATCH 4/6] Update Tools/wasm/emscripten/__main__.py Co-authored-by: Russell Keith-Magee --- Tools/wasm/emscripten/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tools/wasm/emscripten/__main__.py b/Tools/wasm/emscripten/__main__.py index 1c80dec01bf899..6971d3c10c29ca 100644 --- a/Tools/wasm/emscripten/__main__.py +++ b/Tools/wasm/emscripten/__main__.py @@ -510,6 +510,8 @@ def main(): if context.emsdk_cache: validate_emsdk_version(context.emsdk_cache) context.emsdk_cache = Path(context.emsdk_cache).absolute() + else: + print("Build will use EMSDK from current environment.") dispatch = { "make-libffi": make_emscripten_libffi, From 90f65ea0f0bc0832aba60cbf900a5ec246254b3d Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Wed, 4 Mar 2026 11:00:26 +0100 Subject: [PATCH 5/6] Fix --- Tools/wasm/emscripten/__main__.py | 32 ++++++++++++++------ Tools/wasm/emscripten/emscripten_version.txt | 1 + 2 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 Tools/wasm/emscripten/emscripten_version.txt diff --git a/Tools/wasm/emscripten/__main__.py b/Tools/wasm/emscripten/__main__.py index 6971d3c10c29ca..e2137b69a30283 100644 --- a/Tools/wasm/emscripten/__main__.py +++ b/Tools/wasm/emscripten/__main__.py @@ -62,6 +62,28 @@ def validate_emsdk_version(emsdk_cache): print(f"โœ… Emscripten version {required_version} found in {emsdk_cache}") +def parse_env(text): + result = {} + for line in text.splitlines(): + key, val = line.split("=", 1) + result[key] = val + return result + + +@functools.cache +def get_emsdk_env(emsdk_cache): + env_text = subprocess.check_output( + [ + "bash", + "-c", + f"EMSDK_QUIET=1 source {get_emsdk_activate_path(emsdk_cache)} && env", + ], + text=True, + ) + env = parse_env(env_text) + return {key: env[key] for key in ["PATH", "EMSDK", "EMSDK_NODE"]} + + def updated_env(updates, emsdk_cache): """Create a new dict representing the environment to use. @@ -80,15 +102,7 @@ def updated_env(updates, emsdk_cache): # This layering lets SOURCE_DATE_EPOCH from os.environ takes precedence. environment = env_defaults | os.environ | updates if emsdk_cache: - env = os.environ.copy() - call( - ["bash", "-c", f"source {get_emsdk_activate_path(emsdk_cache)}"], - env=env, - quiet=False, - ) - for key in ["PATH", "EMSDK", "EMSDK_NODE"]: - environment[key] = env[key] - + environment |= get_emsdk_env(emsdk_cache) env_diff = {} for key, value in environment.items(): if os.environ.get(key) != value: diff --git a/Tools/wasm/emscripten/emscripten_version.txt b/Tools/wasm/emscripten/emscripten_version.txt new file mode 100644 index 00000000000000..4c05e4ef57dbf8 --- /dev/null +++ b/Tools/wasm/emscripten/emscripten_version.txt @@ -0,0 +1 @@ +4.0.12 From 3b80a0b95d93342d37b543eeee399e339290b042 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 5 Mar 2026 11:16:55 +0100 Subject: [PATCH 6/6] Don't assume we know which envs emsdk_env sets --- Tools/wasm/emscripten/__main__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tools/wasm/emscripten/__main__.py b/Tools/wasm/emscripten/__main__.py index e2137b69a30283..856a7f8252bb7c 100644 --- a/Tools/wasm/emscripten/__main__.py +++ b/Tools/wasm/emscripten/__main__.py @@ -71,7 +71,10 @@ def parse_env(text): @functools.cache -def get_emsdk_env(emsdk_cache): +def get_emsdk_environ(emsdk_cache): + """Returns os.environ updated by sourcing emsdk_env.sh""" + if not emsdk_cache: + return os.environ env_text = subprocess.check_output( [ "bash", @@ -80,8 +83,7 @@ def get_emsdk_env(emsdk_cache): ], text=True, ) - env = parse_env(env_text) - return {key: env[key] for key in ["PATH", "EMSDK", "EMSDK_NODE"]} + return parse_env(env_text) def updated_env(updates, emsdk_cache): @@ -100,9 +102,7 @@ def updated_env(updates, emsdk_cache): except subprocess.CalledProcessError: pass # Might be building from a tarball. # This layering lets SOURCE_DATE_EPOCH from os.environ takes precedence. - environment = env_defaults | os.environ | updates - if emsdk_cache: - environment |= get_emsdk_env(emsdk_cache) + environment = env_defaults | get_emsdk_environ(emsdk_cache) | updates env_diff = {} for key, value in environment.items(): if os.environ.get(key) != value: