nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1{
2 lib,
3 stdenv,
4 fetchFromGitHub,
5 python3,
6 nodejs,
7 closurecompiler,
8 jre,
9 binaryen,
10 llvmPackages,
11 symlinkJoin,
12 makeWrapper,
13 replaceVars,
14 buildNpmPackage,
15 nix-update-script,
16 emscripten,
17}:
18
19let
20 pythonWithPsutil = python3.withPackages (ps: [ ps.psutil ]);
21in
22
23stdenv.mkDerivation rec {
24 pname = "emscripten";
25 version = "4.0.23";
26
27 llvmEnv = symlinkJoin {
28 name = "emscripten-llvm-${version}";
29 paths = with llvmPackages; [
30 clang-unwrapped
31 (lib.getLib clang-unwrapped)
32 lld
33 llvm
34 ];
35 };
36
37 nodeModules = buildNpmPackage {
38 name = "emscripten-node-modules-${version}";
39 inherit pname version src;
40
41 npmDepsHash = "sha256-3P4H30nS6RBe2Bd3aqa2ueLOm/hxSBux53GgJu/D4Xc=";
42
43 dontBuild = true;
44
45 # Copy node_modules directly.
46 installPhase = ''
47 cp -r node_modules $out/
48 '';
49 };
50
51 src = fetchFromGitHub {
52 owner = "emscripten-core";
53 repo = "emscripten";
54 hash = "sha256-i65AWbKuh2KsnugGKmmpUON20He2kgPR6EzwKKA09nQ=";
55 rev = version;
56 };
57
58 strictDeps = true;
59
60 nativeBuildInputs = [
61 makeWrapper
62 python3
63 ];
64 buildInputs = [
65 nodejs
66 ];
67
68 patches = [
69 (replaceVars ./0001-emulate-clang-sysroot-include-logic.patch {
70 resourceDir = "${llvmEnv}/lib/clang/${lib.versions.major llvmPackages.llvm.version}/";
71 })
72 ];
73
74 buildPhase = ''
75 runHook preBuild
76
77 # Make Python scripts executable so patchShebangs will patch their shebangs
78 chmod +x *.py tools/*.py
79
80 patchShebangs .
81
82 # emscripten 4.0.12 requires LLVM tip-of-tree instead of LLVM 21
83 sed -i -e "s/EXPECTED_LLVM_VERSION = 22/EXPECTED_LLVM_VERSION = 21.1/g" tools/shared.py
84
85 # Verify LLVM version patch was applied (fail when nixpkgs has LLVM 22+)
86 grep -q "EXPECTED_LLVM_VERSION = 21.1" tools/shared.py || \
87 (echo "ERROR: LLVM version patch failed - check if still needed" && exit 1)
88
89 # fixes cmake support
90 sed -i -e "s/print \('emcc (Emscript.*\)/sys.stderr.write(\1); sys.stderr.flush()/g" emcc.py
91
92 sed -i "/^def check_sanity/a\\ return" tools/shared.py
93
94 echo "EMSCRIPTEN_ROOT = '$out/share/emscripten'" > .emscripten
95 echo "LLVM_ROOT = '${llvmEnv}/bin'" >> .emscripten
96 echo "NODE_JS = '${nodejs}/bin/node'" >> .emscripten
97 echo "JS_ENGINES = [NODE_JS]" >> .emscripten
98 echo "CLOSURE_COMPILER = ['${closurecompiler}/bin/closure-compiler']" >> .emscripten
99 echo "JAVA = '${jre}/bin/java'" >> .emscripten
100 # to make the test(s) below work
101 # echo "SPIDERMONKEY_ENGINE = []" >> .emscripten
102 echo "BINARYEN_ROOT = '${binaryen}'" >> .emscripten
103
104 # make emconfigure/emcmake use the correct (wrapped) binaries
105 sed -i "s|^EMCC =.*|EMCC='$out/bin/emcc'|" tools/shared.py
106 sed -i "s|^EMXX =.*|EMXX='$out/bin/em++'|" tools/shared.py
107 sed -i "s|^EMAR =.*|EMAR='$out/bin/emar'|" tools/shared.py
108 sed -i "s|^EMRANLIB =.*|EMRANLIB='$out/bin/emranlib'|" tools/shared.py
109
110 # Remove --no-stack-first flag (not in LLVM 21, added in LLVM 22 when --stack-first became default)
111 # Replace else block with pass to avoid empty block syntax error
112 sed -i "s/cmd.append('--no-stack-first')/pass/" tools/building.py
113
114 # Verify --no-stack-first was removed (fail if patch is no longer needed)
115 grep -q "cmd.append('--no-stack-first')" tools/building.py && \
116 (echo "ERROR: --no-stack-first patch not needed anymore" && exit 1) || true
117
118 # Fix /tmp symlink issue (macOS: /tmp -> /private/tmp) causing relpath miscalculation
119 sed -i 's/os\.path\.relpath(source_dir, build_dir)/os.path.relpath(source_dir, os.path.realpath(build_dir))/' tools/system_libs.py
120 sed -i 's/os\.path\.relpath(src, build_dir)/os.path.relpath(src, os.path.realpath(build_dir))/' tools/system_libs.py
121
122 # Verify the relpath fix was applied
123 grep -q 'os.path.realpath(build_dir)' tools/system_libs.py || (echo "ERROR: relpath fix not applied" && exit 1)
124
125 # Functional test: verify relpath resolves correctly through symlinks
126 ${python3}/bin/python3 -c "
127 import os, tempfile
128 src = os.path.abspath('tools/system_libs.py')
129 with tempfile.TemporaryDirectory() as tmpdir:
130 build_dir_real = os.path.realpath(tmpdir)
131 relpath = os.path.relpath(src, build_dir_real)
132 resolved = os.path.normpath(os.path.join(build_dir_real, relpath))
133 assert resolved == src, f'relpath test failed: {resolved} != {src}'
134 print('relpath symlink fix test passed')
135 "
136
137 runHook postBuild
138 '';
139
140 installPhase = ''
141 runHook preInstall
142
143 appdir=$out/share/emscripten
144 mkdir -p $appdir
145 cp -r . $appdir
146 chmod -R +w $appdir
147
148 mkdir -p $appdir/node_modules/.bin
149 cp -r ${nodeModules}/* $appdir/node_modules
150 cp -r ${nodeModules}/* $appdir/node_modules/.bin
151
152 cp ${./locate_cache.sh} $appdir/locate_cache.sh
153 chmod +x $appdir/locate_cache.sh
154
155 export EM_CACHE=$out/share/emscripten/cache
156
157 mkdir -p $out/bin
158
159 # Wrap all tools consistently via their .py entry points
160 for b in em++ emcc em-config emar embuilder emcmake emconfigure emmake emranlib emrun emscons emsize; do
161 makeWrapper $appdir/$b.py $out/bin/$b \
162 --set NODE_PATH ${nodeModules} \
163 --set EM_EXCLUSIVE_CACHE_ACCESS 1 \
164 --set PYTHON ${python3}/bin/python \
165 --run "source $appdir/locate_cache.sh"
166 done
167
168 # Create extensionless aliases for tools that need them (e.g., file_packager)
169 for tool in file_packager; do
170 ln -sf $appdir/tools/$tool.py $appdir/tools/$tool
171 done
172
173 # Symlinks for CMake toolchain (expects tools in share/emscripten/)
174 for tool in emcc em++ em-config emar emranlib emcmake emconfigure; do
175 ln -sf $out/bin/$tool $appdir/$tool
176 done
177
178 # precompile libc (etc.) in all variants:
179 pushd $TMPDIR
180 echo 'int __main_argc_argv( int a, int b ) { return 42; }' >test.c
181 for LTO in -flto ""; do
182 for BIND in "" "--bind"; do
183 for PTHREAD in "" "-pthread"; do
184 $out/bin/emcc $LTO $BIND $PTHREAD test.c || true
185 done
186 done
187 done
188 popd
189
190 export PYTHON=${python3}/bin/python
191 export NODE_PATH=${nodeModules}
192 pushd $appdir
193 ${pythonWithPsutil}/bin/python test/runner.py test_hello_world
194 popd
195
196 # fail if any .py files still have unpatched shebangs
197 if grep -l '#!/usr/bin/env' $appdir/*.py $appdir/tools/*.py 2>/dev/null; then
198 echo "ERROR: unpatched shebangs found in .py files"
199 exit 1
200 fi
201
202 runHook postInstall
203 '';
204
205 passthru = {
206 # HACK: Make emscripten look more like a cc-wrapper to GHC
207 # when building the javascript backend.
208 targetPrefix = "em";
209 bintools = emscripten;
210 updateScript = nix-update-script {
211 extraArgs = [
212 "--subpackage"
213 "nodeModules"
214 ];
215 };
216 };
217
218 meta = {
219 homepage = "https://github.com/emscripten-core/emscripten";
220 description = "LLVM-to-JavaScript Compiler";
221 platforms = lib.platforms.all;
222 maintainers = with lib.maintainers; [
223 qknight
224 raitobezarius
225 willcohen
226 ];
227 license = lib.licenses.ncsa;
228 };
229}