diff --git a/pkgs/development/compilers/emscripten/default.nix b/pkgs/development/compilers/emscripten/default.nix index 910be8e3417d..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, }: @@ -21,7 +22,7 @@ in stdenv.mkDerivation rec { pname = "emscripten"; - version = "4.0.21"; + version = "4.0.22"; llvmEnv = symlinkJoin { name = "emscripten-llvm-${version}"; @@ -37,7 +38,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 +51,7 @@ stdenv.mkDerivation rec { src = fetchFromGitHub { owner = "emscripten-core"; repo = "emscripten"; - hash = "sha256-8lh7ZpzVnoQXOGE/xJgHSWkYXUDOOprbSGaEkyU+vKE="; + hash = "sha256-tC+7zo5RnIo91SFzzwyU7qHFXf4TDcczf3mO4ObfsVE="; rev = version; }; @@ -71,88 +72,134 @@ stdenv.mkDerivation rec { ]; buildPhase = '' - runHook preBuild + runHook preBuild - patchShebangs . + # Make Python scripts executable so patchShebangs will patch their shebangs + chmod +x *.py tools/*.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 + patchShebangs . - # fixes cmake support - sed -i -e "s/print \('emcc (Emscript.*\)/sys.stderr.write(\1); sys.stderr.flush()/g" emcc.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 - sed -i "/^def check_sanity/a\\ return" 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) - 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 + # fixes cmake support + sed -i -e "s/print \('emcc (Emscript.*\)/sys.stderr.write(\1); sys.stderr.flush()/g" emcc.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 + sed -i "/^def check_sanity/a\\ return" tools/shared.py - runHook postBuild + 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 + + # 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 + + # 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 + + # 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 = '' - 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 - 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 - chmod +x $appdir/$b.py - 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 + mkdir -p $out/bin - # 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 - done - done - popd + # 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 - export PYTHON=${python3}/bin/python - export NODE_PATH=${nodeModules} - pushd $appdir - ${pythonWithPsutil}/bin/python test/runner.py test_hello_world - popd + # 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 = { @@ -160,6 +207,12 @@ stdenv.mkDerivation rec { # when building the javascript backend. targetPrefix = "em"; bintools = emscripten; + updateScript = nix-update-script { + extraArgs = [ + "--subpackage" + "nodeModules" + ]; + }; }; meta = { 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"}