From f878d2bb79ef7037719e96569cf3bc02d94408a9 Mon Sep 17 00:00:00 2001 From: Will Cohen Date: Fri, 26 Dec 2025 12:11:51 -0500 Subject: [PATCH 1/4] emscripten: 4.0.21 -> 4.0.22 --- pkgs/development/compilers/emscripten/default.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/development/compilers/emscripten/default.nix b/pkgs/development/compilers/emscripten/default.nix index 910be8e3417d..5a7d52e96b62 100644 --- a/pkgs/development/compilers/emscripten/default.nix +++ b/pkgs/development/compilers/emscripten/default.nix @@ -21,7 +21,7 @@ in stdenv.mkDerivation rec { pname = "emscripten"; - version = "4.0.21"; + version = "4.0.22"; llvmEnv = symlinkJoin { name = "emscripten-llvm-${version}"; @@ -37,7 +37,7 @@ stdenv.mkDerivation rec { name = "emscripten-node-modules-${version}"; inherit pname version src; - npmDepsHash = "sha256-IwiH+GELJzd4rDq31arhiF5miIRLDe7nrVsM7Yg9rTg="; + npmDepsHash = "sha256-2baHlyXdFF7JIY+FxpgHhe5NEqzjHpTSS/NhvM0ARZc="; dontBuild = true; @@ -50,7 +50,7 @@ stdenv.mkDerivation rec { src = fetchFromGitHub { owner = "emscripten-core"; repo = "emscripten"; - hash = "sha256-8lh7ZpzVnoQXOGE/xJgHSWkYXUDOOprbSGaEkyU+vKE="; + hash = "sha256-tC+7zo5RnIo91SFzzwyU7qHFXf4TDcczf3mO4ObfsVE="; rev = version; }; From 3e1614f4851e5f9117cd232e7c75eda776fabded Mon Sep 17 00:00:00 2001 From: Will Cohen Date: Fri, 26 Dec 2025 14:35:01 -0500 Subject: [PATCH 2/4] emscripten: fix cache --- .../compilers/emscripten/default.nix | 92 ++++++++++++------- .../compilers/emscripten/locate_cache.sh | 2 +- 2 files changed, 62 insertions(+), 32 deletions(-) diff --git a/pkgs/development/compilers/emscripten/default.nix b/pkgs/development/compilers/emscripten/default.nix index 5a7d52e96b62..c1663709507a 100644 --- a/pkgs/development/compilers/emscripten/default.nix +++ b/pkgs/development/compilers/emscripten/default.nix @@ -71,35 +71,58 @@ stdenv.mkDerivation rec { ]; buildPhase = '' - runHook preBuild + runHook preBuild - patchShebangs . + patchShebangs . - # emscripten 4.0.12 requires LLVM tip-of-tree instead of LLVM 21 - sed -i -e "s/EXPECTED_LLVM_VERSION = 22/EXPECTED_LLVM_VERSION = 21.1/g" tools/shared.py + # emscripten 4.0.12 requires LLVM tip-of-tree instead of LLVM 21 + sed -i -e "s/EXPECTED_LLVM_VERSION = 22/EXPECTED_LLVM_VERSION = 21.1/g" tools/shared.py - # fixes cmake support - sed -i -e "s/print \('emcc (Emscript.*\)/sys.stderr.write(\1); sys.stderr.flush()/g" emcc.py + # fixes cmake support + sed -i -e "s/print \('emcc (Emscript.*\)/sys.stderr.write(\1); sys.stderr.flush()/g" emcc.py - sed -i "/^def check_sanity/a\\ return" tools/shared.py + sed -i "/^def check_sanity/a\\ return" tools/shared.py - echo "EMSCRIPTEN_ROOT = '$out/share/emscripten'" > .emscripten - echo "LLVM_ROOT = '${llvmEnv}/bin'" >> .emscripten - echo "NODE_JS = '${nodejs}/bin/node'" >> .emscripten - echo "JS_ENGINES = [NODE_JS]" >> .emscripten - echo "CLOSURE_COMPILER = ['${closurecompiler}/bin/closure-compiler']" >> .emscripten - echo "JAVA = '${jre}/bin/java'" >> .emscripten - # to make the test(s) below work - # echo "SPIDERMONKEY_ENGINE = []" >> .emscripten - echo "BINARYEN_ROOT = '${binaryen}'" >> .emscripten + echo "EMSCRIPTEN_ROOT = '$out/share/emscripten'" > .emscripten + echo "LLVM_ROOT = '${llvmEnv}/bin'" >> .emscripten + echo "NODE_JS = '${nodejs}/bin/node'" >> .emscripten + echo "JS_ENGINES = [NODE_JS]" >> .emscripten + echo "CLOSURE_COMPILER = ['${closurecompiler}/bin/closure-compiler']" >> .emscripten + echo "JAVA = '${jre}/bin/java'" >> .emscripten + # to make the test(s) below work + # echo "SPIDERMONKEY_ENGINE = []" >> .emscripten + echo "BINARYEN_ROOT = '${binaryen}'" >> .emscripten - # make emconfigure/emcmake use the correct (wrapped) binaries - sed -i "s|^EMCC =.*|EMCC='$out/bin/emcc'|" tools/shared.py - sed -i "s|^EMXX =.*|EMXX='$out/bin/em++'|" tools/shared.py - sed -i "s|^EMAR =.*|EMAR='$out/bin/emar'|" tools/shared.py - sed -i "s|^EMRANLIB =.*|EMRANLIB='$out/bin/emranlib'|" tools/shared.py + # make emconfigure/emcmake use the correct (wrapped) binaries + sed -i "s|^EMCC =.*|EMCC='$out/bin/emcc'|" tools/shared.py + sed -i "s|^EMXX =.*|EMXX='$out/bin/em++'|" tools/shared.py + sed -i "s|^EMAR =.*|EMAR='$out/bin/emar'|" tools/shared.py + sed -i "s|^EMRANLIB =.*|EMRANLIB='$out/bin/emranlib'|" tools/shared.py - runHook postBuild + # Remove --no-stack-first flag (not in LLVM 21, added in LLVM 22 when --stack-first became default) + # Replace else block with pass to avoid empty block syntax error + sed -i "s/cmd.append('--no-stack-first')/pass/" tools/building.py + + # Fix /tmp symlink issue (macOS: /tmp -> /private/tmp) causing relpath miscalculation + sed -i 's/os\.path\.relpath(source_dir, build_dir)/os.path.relpath(source_dir, os.path.realpath(build_dir))/' tools/system_libs.py + sed -i 's/os\.path\.relpath(src, build_dir)/os.path.relpath(src, os.path.realpath(build_dir))/' tools/system_libs.py + + # Verify the relpath fix was applied + grep -q 'os.path.realpath(build_dir)' tools/system_libs.py || (echo "ERROR: relpath fix not applied" && exit 1) + + # Functional test: verify relpath resolves correctly through symlinks + ${python3}/bin/python3 -c " + import os, tempfile + src = os.path.abspath('tools/system_libs.py') + with tempfile.TemporaryDirectory() as tmpdir: + build_dir_real = os.path.realpath(tmpdir) + relpath = os.path.relpath(src, build_dir_real) + resolved = os.path.normpath(os.path.join(build_dir_real, relpath)) + assert resolved == src, f'relpath test failed: {resolved} != {src}' + print('relpath symlink fix test passed') + " + + runHook postBuild ''; installPhase = '' @@ -120,14 +143,9 @@ stdenv.mkDerivation rec { export EM_CACHE=$out/share/emscripten/cache mkdir -p $out/bin - for b in em++ emcc; do - makeWrapper $appdir/$b $out/bin/$b \ - --set NODE_PATH ${nodeModules} \ - --set EM_EXCLUSIVE_CACHE_ACCESS 1 \ - --set PYTHON ${python3}/bin/python \ - --run "source $appdir/locate_cache.sh" - done - for b in em-config emar embuilder emcmake emconfigure emmake emranlib emrun emscons emsize; do + + # Wrap all tools consistently via their .py entry points + for b in em++ emcc em-config emar embuilder emcmake emconfigure emmake emranlib emrun emscons emsize; do chmod +x $appdir/$b.py makeWrapper $appdir/$b.py $out/bin/$b \ --set NODE_PATH ${nodeModules} \ @@ -136,12 +154,24 @@ stdenv.mkDerivation rec { --run "source $appdir/locate_cache.sh" done + # Create extensionless aliases for tools that need them (e.g., file_packager) + for tool in file_packager; do + ln -sf $appdir/tools/$tool.py $appdir/tools/$tool + done + + # Symlinks for CMake toolchain (expects tools in share/emscripten/) + for tool in emcc em++ em-config emar emranlib emcmake emconfigure; do + ln -sf $out/bin/$tool $appdir/$tool + done + # precompile libc (etc.) in all variants: pushd $TMPDIR echo 'int __main_argc_argv( int a, int b ) { return 42; }' >test.c for LTO in -flto ""; do for BIND in "" "--bind"; do - $out/bin/emcc $LTO $BIND test.c + for PTHREAD in "" "-pthread"; do + $out/bin/emcc $LTO $BIND $PTHREAD test.c || true + done done done popd diff --git a/pkgs/development/compilers/emscripten/locate_cache.sh b/pkgs/development/compilers/emscripten/locate_cache.sh index e8cab885d1be..1fc97086dae5 100644 --- a/pkgs/development/compilers/emscripten/locate_cache.sh +++ b/pkgs/development/compilers/emscripten/locate_cache.sh @@ -1 +1 @@ -export EM_CACHE=${EM_CACHE:-"/tmp/$(basename $(realpath $(dirname $(which emcc))/..))_cache"} +export EM_CACHE=${EM_CACHE:-"/tmp/$(basename $(realpath $(dirname $(command -v emcc))/..))_cache"} From 9255e349836c26d26f7d7b2e7afb0b5929374ce7 Mon Sep 17 00:00:00 2001 From: Will Cohen Date: Sat, 27 Dec 2025 08:11:17 -0500 Subject: [PATCH 3/4] emscripten: fix patchShebangs and add regression test --- pkgs/development/compilers/emscripten/default.nix | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkgs/development/compilers/emscripten/default.nix b/pkgs/development/compilers/emscripten/default.nix index c1663709507a..b6f3e61cbb18 100644 --- a/pkgs/development/compilers/emscripten/default.nix +++ b/pkgs/development/compilers/emscripten/default.nix @@ -73,6 +73,9 @@ stdenv.mkDerivation rec { buildPhase = '' runHook preBuild + # Make Python scripts executable so patchShebangs will patch their shebangs + chmod +x *.py tools/*.py + patchShebangs . # emscripten 4.0.12 requires LLVM tip-of-tree instead of LLVM 21 @@ -146,7 +149,6 @@ stdenv.mkDerivation rec { # Wrap all tools consistently via their .py entry points for b in em++ emcc em-config emar embuilder emcmake emconfigure emmake emranlib emrun emscons emsize; do - chmod +x $appdir/$b.py makeWrapper $appdir/$b.py $out/bin/$b \ --set NODE_PATH ${nodeModules} \ --set EM_EXCLUSIVE_CACHE_ACCESS 1 \ @@ -181,6 +183,12 @@ stdenv.mkDerivation rec { pushd $appdir ${pythonWithPsutil}/bin/python test/runner.py test_hello_world popd + + # fail if any .py files still have unpatched shebangs + if grep -l '#!/usr/bin/env' $appdir/*.py $appdir/tools/*.py 2>/dev/null; then + echo "ERROR: unpatched shebangs found in .py files" + exit 1 +fi runHook postInstall ''; From 31e1f1b0f56bca1cc6ca767ba6ef9333179be0a4 Mon Sep 17 00:00:00 2001 From: Will Cohen Date: Sat, 27 Dec 2025 08:14:49 -0500 Subject: [PATCH 4/4] emscripten: add updateScript and patch verification --- .../compilers/emscripten/default.nix | 121 ++++++++++-------- 1 file changed, 68 insertions(+), 53 deletions(-) diff --git a/pkgs/development/compilers/emscripten/default.nix b/pkgs/development/compilers/emscripten/default.nix index b6f3e61cbb18..1d8184166f84 100644 --- a/pkgs/development/compilers/emscripten/default.nix +++ b/pkgs/development/compilers/emscripten/default.nix @@ -12,6 +12,7 @@ makeWrapper, replaceVars, buildNpmPackage, + nix-update-script, emscripten, }: @@ -81,6 +82,10 @@ stdenv.mkDerivation rec { # emscripten 4.0.12 requires LLVM tip-of-tree instead of LLVM 21 sed -i -e "s/EXPECTED_LLVM_VERSION = 22/EXPECTED_LLVM_VERSION = 21.1/g" tools/shared.py + # Verify LLVM version patch was applied (fail when nixpkgs has LLVM 22+) + grep -q "EXPECTED_LLVM_VERSION = 21.1" tools/shared.py || \ + (echo "ERROR: LLVM version patch failed - check if still needed" && exit 1) + # fixes cmake support sed -i -e "s/print \('emcc (Emscript.*\)/sys.stderr.write(\1); sys.stderr.flush()/g" emcc.py @@ -106,6 +111,10 @@ stdenv.mkDerivation rec { # Replace else block with pass to avoid empty block syntax error sed -i "s/cmd.append('--no-stack-first')/pass/" tools/building.py + # Verify --no-stack-first was removed (fail if patch is no longer needed) + grep -q "cmd.append('--no-stack-first')" tools/building.py && \ + (echo "ERROR: --no-stack-first patch not needed anymore" && exit 1) || true + # Fix /tmp symlink issue (macOS: /tmp -> /private/tmp) causing relpath miscalculation sed -i 's/os\.path\.relpath(source_dir, build_dir)/os.path.relpath(source_dir, os.path.realpath(build_dir))/' tools/system_libs.py sed -i 's/os\.path\.relpath(src, build_dir)/os.path.relpath(src, os.path.realpath(build_dir))/' tools/system_libs.py @@ -129,68 +138,68 @@ stdenv.mkDerivation rec { ''; installPhase = '' - runHook preInstall + runHook preInstall - appdir=$out/share/emscripten - mkdir -p $appdir - cp -r . $appdir - chmod -R +w $appdir + appdir=$out/share/emscripten + mkdir -p $appdir + cp -r . $appdir + chmod -R +w $appdir - mkdir -p $appdir/node_modules/.bin - cp -r ${nodeModules}/* $appdir/node_modules - cp -r ${nodeModules}/* $appdir/node_modules/.bin + mkdir -p $appdir/node_modules/.bin + cp -r ${nodeModules}/* $appdir/node_modules + cp -r ${nodeModules}/* $appdir/node_modules/.bin - cp ${./locate_cache.sh} $appdir/locate_cache.sh - chmod +x $appdir/locate_cache.sh + cp ${./locate_cache.sh} $appdir/locate_cache.sh + chmod +x $appdir/locate_cache.sh - export EM_CACHE=$out/share/emscripten/cache + export EM_CACHE=$out/share/emscripten/cache - mkdir -p $out/bin + mkdir -p $out/bin - # Wrap all tools consistently via their .py entry points - for b in em++ emcc em-config emar embuilder emcmake emconfigure emmake emranlib emrun emscons emsize; do - makeWrapper $appdir/$b.py $out/bin/$b \ - --set NODE_PATH ${nodeModules} \ - --set EM_EXCLUSIVE_CACHE_ACCESS 1 \ - --set PYTHON ${python3}/bin/python \ - --run "source $appdir/locate_cache.sh" - done - - # Create extensionless aliases for tools that need them (e.g., file_packager) - for tool in file_packager; do - ln -sf $appdir/tools/$tool.py $appdir/tools/$tool - done - - # Symlinks for CMake toolchain (expects tools in share/emscripten/) - for tool in emcc em++ em-config emar emranlib emcmake emconfigure; do - ln -sf $out/bin/$tool $appdir/$tool - done - - # precompile libc (etc.) in all variants: - pushd $TMPDIR - echo 'int __main_argc_argv( int a, int b ) { return 42; }' >test.c - for LTO in -flto ""; do - for BIND in "" "--bind"; do - for PTHREAD in "" "-pthread"; do - $out/bin/emcc $LTO $BIND $PTHREAD test.c || true + # Wrap all tools consistently via their .py entry points + for b in em++ emcc em-config emar embuilder emcmake emconfigure emmake emranlib emrun emscons emsize; do + makeWrapper $appdir/$b.py $out/bin/$b \ + --set NODE_PATH ${nodeModules} \ + --set EM_EXCLUSIVE_CACHE_ACCESS 1 \ + --set PYTHON ${python3}/bin/python \ + --run "source $appdir/locate_cache.sh" done - done - done - popd - export PYTHON=${python3}/bin/python - export NODE_PATH=${nodeModules} - pushd $appdir - ${pythonWithPsutil}/bin/python test/runner.py test_hello_world - popd - - # fail if any .py files still have unpatched shebangs - if grep -l '#!/usr/bin/env' $appdir/*.py $appdir/tools/*.py 2>/dev/null; then - echo "ERROR: unpatched shebangs found in .py files" - exit 1 -fi + # Create extensionless aliases for tools that need them (e.g., file_packager) + for tool in file_packager; do + ln -sf $appdir/tools/$tool.py $appdir/tools/$tool + done - runHook postInstall + # Symlinks for CMake toolchain (expects tools in share/emscripten/) + for tool in emcc em++ em-config emar emranlib emcmake emconfigure; do + ln -sf $out/bin/$tool $appdir/$tool + done + + # precompile libc (etc.) in all variants: + pushd $TMPDIR + echo 'int __main_argc_argv( int a, int b ) { return 42; }' >test.c + for LTO in -flto ""; do + for BIND in "" "--bind"; do + for PTHREAD in "" "-pthread"; do + $out/bin/emcc $LTO $BIND $PTHREAD test.c || true + done + done + done + popd + + export PYTHON=${python3}/bin/python + export NODE_PATH=${nodeModules} + pushd $appdir + ${pythonWithPsutil}/bin/python test/runner.py test_hello_world + popd + + # fail if any .py files still have unpatched shebangs + if grep -l '#!/usr/bin/env' $appdir/*.py $appdir/tools/*.py 2>/dev/null; then + echo "ERROR: unpatched shebangs found in .py files" + exit 1 + fi + + runHook postInstall ''; passthru = { @@ -198,6 +207,12 @@ fi # when building the javascript backend. targetPrefix = "em"; bintools = emscripten; + updateScript = nix-update-script { + extraArgs = [ + "--subpackage" + "nodeModules" + ]; + }; }; meta = {