From dfb823ca03cc23e1340e84c15e65cb27576a7b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 28 Apr 2026 00:03:46 +0300 Subject: [PATCH 1/3] Fix an issue if user passes a relative path to Python, and some system cache libraries need rebuilding. Emcc tool would spawn sub-emcc tools to rebuild the cache, with different CWD, resulting in the relative path lookups pointing to the wrong relative directories. To fix the issue, normalize the relative tool paths first. --- test/test_sanity.py | 23 +++++++++++++++++++++++ tools/config.py | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/test/test_sanity.py b/test/test_sanity.py index e3385e42792eb..b87b760ad270a 100644 --- a/test/test_sanity.py +++ b/test/test_sanity.py @@ -846,3 +846,26 @@ def test_bootstrap_without_em_config(self): # Running bootstrap.py should not fail self.run_process([utils.exe_path_from_root('bootstrap')], env=env) + + # Verify that if user specifies a relative path to Python executable, then + # Emscripten is still able to build. + def test_emcc_with_relative_python_path(self): + restore_and_set_up() + # Clear the cache, since rebuilding the cache has been observed to fail + # if Python path is specified as relative. + self.clear_cache() + + try: + relative_python = os.path.relpath(os.environ.get('EMSDK_PYTHON'), os.getcwd()) + except ValueError: + self.skipTest('Python and Emscripten are located on different drives, cannot run this test.') + + relative_python_escaped = relative_python.replace("\\", "\\\\") + add_to_config(f'PYTHON = "{relative_python_escaped}"') + + env = os.environ.copy() + env['EMSDK_PYTHON'] = relative_python + + output = self.do([EMCC, test_file('hello_world.c')], env=env) + self.assertNotContained('error', output) + self.assertExists('a.out.js') diff --git a/tools/config.py b/tools/config.py index d22f2f017c65d..de6fb678addec 100644 --- a/tools/config.py +++ b/tools/config.py @@ -74,6 +74,23 @@ def normalize_config_settings(): PORTS = os.path.join(CACHE, 'ports') +def normalize_relative_environment_variables(): + # User may have specified environment variables to point to the location + # of e.g. Python or Node, like with + # + # EMSDK_PYTHON=../../path/to/python emcc test/hello_world.c + # + # As part of its operation, emcc may spawn sub-emcc tasks when building + # libraries to cache. These sub-emcc tasks may run in a different CWD, so + # normalize the path directives to current environment here, so that any + # sub-tool spawns will see the path to the tool from the parent process. + # However, be careful not to normalize e.g. 'python' or other PATH lookups. + for env_var in {'EMSDK_PYTHON', 'NODE_JS'}: + path = os.environ.get(env_var) + if path and ('\\' in path or '/' in path): + os.environ[env_var] = os.path.abspath(os.environ[env_var]) + + def set_config_from_tool_location(config_key, tool_binary, f): val = globals()[config_key] if val is None: @@ -168,6 +185,7 @@ def read_config(): set_config_from_tool_location('BINARYEN_ROOT', 'wasm-opt', lambda x: os.path.dirname(os.path.dirname(x))) normalize_config_settings() + normalize_relative_environment_variables() def generate_config(path): From a83e41f1bb4e0fe15db58c4ff2d3ce5f92373e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Wed, 29 Apr 2026 00:44:02 +0300 Subject: [PATCH 2/3] ruff --- tools/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/config.py b/tools/config.py index de6fb678addec..548096aac3f0a 100644 --- a/tools/config.py +++ b/tools/config.py @@ -85,7 +85,7 @@ def normalize_relative_environment_variables(): # normalize the path directives to current environment here, so that any # sub-tool spawns will see the path to the tool from the parent process. # However, be careful not to normalize e.g. 'python' or other PATH lookups. - for env_var in {'EMSDK_PYTHON', 'NODE_JS'}: + for env_var in ['EMSDK_PYTHON', 'NODE_JS']: path = os.environ.get(env_var) if path and ('\\' in path or '/' in path): os.environ[env_var] = os.path.abspath(os.environ[env_var]) From 8f37812a044d5e630f24d4acbded4acd813fd2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Wed, 29 Apr 2026 00:55:16 +0300 Subject: [PATCH 3/3] Narrow the code to only address Python executable --- tools/config.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tools/config.py b/tools/config.py index 548096aac3f0a..147623070bf4a 100644 --- a/tools/config.py +++ b/tools/config.py @@ -74,21 +74,18 @@ def normalize_config_settings(): PORTS = os.path.join(CACHE, 'ports') -def normalize_relative_environment_variables(): - # User may have specified environment variables to point to the location - # of e.g. Python or Node, like with +def normalize_relative_python_path(): + # User may have specified the EMSDK_PYTHON environment variable to point to + # the Python interpreter, e.g. # # EMSDK_PYTHON=../../path/to/python emcc test/hello_world.c # # As part of its operation, emcc may spawn sub-emcc tasks when building - # libraries to cache. These sub-emcc tasks may run in a different CWD, so - # normalize the path directives to current environment here, so that any - # sub-tool spawns will see the path to the tool from the parent process. - # However, be careful not to normalize e.g. 'python' or other PATH lookups. - for env_var in ['EMSDK_PYTHON', 'NODE_JS']: - path = os.environ.get(env_var) - if path and ('\\' in path or '/' in path): - os.environ[env_var] = os.path.abspath(os.environ[env_var]) + # libraries to cache. These sub-emcc tasks will run in a different CWD, so + # reinitialize EMSDK_PYTHON here so that sub-tool spawns will use the same + # Python interpreter as the parent. + if os.environ.get('EMSDK_PYTHON'): + os.environ['EMSDK_PYTHON'] = sys.executable def set_config_from_tool_location(config_key, tool_binary, f): @@ -185,7 +182,7 @@ def read_config(): set_config_from_tool_location('BINARYEN_ROOT', 'wasm-opt', lambda x: os.path.dirname(os.path.dirname(x))) normalize_config_settings() - normalize_relative_environment_variables() + normalize_relative_python_path() def generate_config(path):