1{
2 lib,
3 stdenv,
4 removeReferencesTo,
5 pkgsBuildBuild,
6 pkgsBuildHost,
7 pkgsBuildTarget,
8 targetPackages,
9 llvmShared,
10 llvmSharedForBuild,
11 llvmSharedForHost,
12 llvmSharedForTarget,
13 llvmPackages,
14 runCommandLocal,
15 fetchurl,
16 file,
17 python3,
18 cargo,
19 cmake,
20 rustc,
21 rustfmt,
22 pkg-config,
23 openssl,
24 xz,
25 zlib,
26 bintools,
27 which,
28 libffi,
29 withBundledLLVM ? false,
30 enableRustcDev ? true,
31 version,
32 sha256,
33 patches ? [ ],
34 fd,
35 ripgrep,
36 wezterm,
37 firefox,
38 thunderbird,
39 # This only builds std for target and reuses the rustc from build.
40 fastCross,
41 lndir,
42 makeWrapper,
43}:
44
45let
46 inherit (lib)
47 optionals
48 optional
49 optionalString
50 concatStringsSep
51 ;
52 useLLVM = stdenv.targetPlatform.useLLVM or false;
53in
54stdenv.mkDerivation (finalAttrs: {
55 pname = "${targetPackages.stdenv.cc.targetPrefix}rustc";
56 inherit version;
57
58 src = fetchurl {
59 url = "https://static.rust-lang.org/dist/rustc-${version}-src.tar.gz";
60 inherit sha256;
61 # See https://nixos.org/manual/nixpkgs/stable/#using-git-bisect-on-the-rust-compiler
62 passthru.isReleaseTarball = true;
63 };
64
65 hardeningDisable = optionals stdenv.cc.isClang [
66 # remove once https://github.com/NixOS/nixpkgs/issues/318674 is
67 # addressed properly
68 "zerocallusedregs"
69 ];
70
71 __darwinAllowLocalNetworking = true;
72
73 # rustc complains about modified source files otherwise
74 dontUpdateAutotoolsGnuConfigScripts = true;
75
76 # Running the default `strip -S` command on Darwin corrupts the
77 # .rlib files in "lib/".
78 #
79 # See https://github.com/NixOS/nixpkgs/pull/34227
80 #
81 # Running `strip -S` when cross compiling can harm the cross rlibs.
82 # See: https://github.com/NixOS/nixpkgs/pull/56540#issuecomment-471624656
83 stripDebugList = [ "bin" ];
84
85 NIX_LDFLAGS = toString (
86 # when linking stage1 libstd: cc: undefined reference to `__cxa_begin_catch'
87 # This doesn't apply to cross-building for FreeBSD because the host
88 # uses libstdc++, but the target (used for building std) uses libc++
89 optional (
90 stdenv.hostPlatform.isLinux && !withBundledLLVM && !stdenv.targetPlatform.isFreeBSD && !useLLVM
91 ) "--push-state --as-needed -lstdc++ --pop-state"
92 ++
93 optional
94 (stdenv.hostPlatform.isLinux && !withBundledLLVM && !stdenv.targetPlatform.isFreeBSD && useLLVM)
95 "--push-state --as-needed -L${llvmPackages.libcxx}/lib -lc++ -lc++abi -lLLVM-${lib.versions.major llvmPackages.llvm.version} --pop-state"
96 ++ optional (stdenv.hostPlatform.isDarwin && !withBundledLLVM) "-lc++ -lc++abi"
97 ++ optional stdenv.hostPlatform.isDarwin "-rpath ${llvmSharedForHost.lib}/lib"
98 );
99
100 # Increase codegen units to introduce parallelism within the compiler.
101 RUSTFLAGS = "-Ccodegen-units=10";
102 RUSTDOCFLAGS = "-A rustdoc::broken-intra-doc-links";
103
104 # We need rust to build rust. If we don't provide it, configure will try to download it.
105 # Reference: https://github.com/rust-lang/rust/blob/master/src/bootstrap/configure.py
106 configureFlags =
107 let
108 prefixForStdenv = stdenv: "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}";
109 ccPrefixForStdenv =
110 stdenv: "${prefixForStdenv stdenv}${if (stdenv.cc.isClang or false) then "clang" else "cc"}";
111 cxxPrefixForStdenv =
112 stdenv: "${prefixForStdenv stdenv}${if (stdenv.cc.isClang or false) then "clang++" else "c++"}";
113 setBuild = "--set=target.\"${stdenv.buildPlatform.rust.rustcTargetSpec}\"";
114 setHost = "--set=target.\"${stdenv.hostPlatform.rust.rustcTargetSpec}\"";
115 setTarget = "--set=target.\"${stdenv.targetPlatform.rust.rustcTargetSpec}\"";
116 ccForBuild = ccPrefixForStdenv pkgsBuildBuild.targetPackages.stdenv;
117 cxxForBuild = cxxPrefixForStdenv pkgsBuildBuild.targetPackages.stdenv;
118 ccForHost = ccPrefixForStdenv pkgsBuildHost.targetPackages.stdenv;
119 cxxForHost = cxxPrefixForStdenv pkgsBuildHost.targetPackages.stdenv;
120 ccForTarget = ccPrefixForStdenv pkgsBuildTarget.targetPackages.stdenv;
121 cxxForTarget = cxxPrefixForStdenv pkgsBuildTarget.targetPackages.stdenv;
122 in
123 [
124 "--sysconfdir=${placeholder "out"}/etc"
125 "--release-channel=stable"
126 "--set=build.rustc=${rustc}/bin/rustc"
127 "--set=build.cargo=${cargo}/bin/cargo"
128 ]
129 ++ lib.optionals (!(finalAttrs.src.passthru.isReleaseTarball or false)) [
130 # release tarballs vendor the rustfmt source; when
131 # git-bisect'ing from upstream's git repo we must prevent
132 # attempts to download the missing source tarball
133 "--set=build.rustfmt=${rustfmt}/bin/rustfmt"
134 ]
135 ++ [
136 "--tools=rustc,rustdoc,rust-analyzer-proc-macro-srv"
137 "--enable-rpath"
138 "--enable-vendor"
139 # For Nixpkgs it makes more sense to use stdenv's linker than
140 # letting rustc build its own.
141 "--disable-lld"
142 "--build=${stdenv.buildPlatform.rust.rustcTargetSpec}"
143 "--host=${stdenv.hostPlatform.rust.rustcTargetSpec}"
144 # std is built for all platforms in --target.
145 "--target=${
146 concatStringsSep "," (
147 # Other targets that don't need any extra dependencies to build.
148 optionals (!fastCross) [
149 "wasm32-unknown-unknown"
150 "wasm32v1-none"
151 "bpfel-unknown-none"
152 "bpfeb-unknown-none"
153 ]
154 # (build!=target): When cross-building a compiler we need to add
155 # the build platform as well so rustc can compile build.rs
156 # scripts.
157 ++ optionals (stdenv.buildPlatform != stdenv.targetPlatform && !fastCross) [
158 stdenv.buildPlatform.rust.rustcTargetSpec
159 ]
160 # (host!=target): When building a cross-targeting compiler we
161 # need to add the host platform as well so rustc can compile
162 # build.rs scripts.
163 ++ optionals (stdenv.hostPlatform != stdenv.targetPlatform && !fastCross) [
164 stdenv.hostPlatform.rust.rustcTargetSpec
165 ]
166 ++ [
167 # `make install` only keeps the docs of the last target in the list.
168 # If the `targetPlatform` is not the last entry, we may end up without
169 # `alloc` or `std` docs (if the last target is `no_std`).
170 # More information: https://github.com/rust-lang/rust/issues/140922
171 stdenv.targetPlatform.rust.rustcTargetSpec
172 ]
173 )
174 }"
175
176 "${setBuild}.cc=${ccForBuild}"
177 "${setHost}.cc=${ccForHost}"
178 "${setTarget}.cc=${ccForTarget}"
179
180 # The Rust compiler build identifies platforms by Rust target
181 # name, and later arguments override previous arguments. This
182 # means that when platforms differ in configuration but overlap
183 # in Rust target name (for instance, `pkgsStatic`), only one
184 # setting will be applied for any given option.
185 #
186 # This is usually mostly harmless, especially as `fastCross`
187 # means that we usually only compile `std` in such cases.
188 # However, the build does still need to link helper tools for the
189 # build platform in that case. This was breaking the Darwin
190 # `pkgsStatic` build, as it was attempting to link build tools
191 # with the target platform’s static linker, and failing to locate
192 # an appropriate static library for `-liconv`.
193 #
194 # Since the static build does not link anything for the target
195 # platform anyway, we put the target linker first so that the
196 # build platform linker overrides it when the Rust target names
197 # overlap, allowing the helper tools to build successfully.
198 #
199 # Note that Rust does not remember these settings in the built
200 # compiler, so this does not compromise any functionality of the
201 # resulting compiler.
202 #
203 # The longer‐term fix would be to get Rust to use a more nuanced
204 # understanding of platforms, such as by explicitly constructing
205 # and passing Rust JSON target definitions that let us
206 # distinguish the platforms in cases like these. That would also
207 # let us supplant various hacks around the wrappers and hooks
208 # that we currently need.
209 "${setTarget}.linker=${ccForTarget}"
210 "${setBuild}.linker=${ccForBuild}"
211 "${setHost}.linker=${ccForHost}"
212
213 "${setBuild}.cxx=${cxxForBuild}"
214 "${setHost}.cxx=${cxxForHost}"
215 "${setTarget}.cxx=${cxxForTarget}"
216
217 "${setBuild}.crt-static=${lib.boolToString stdenv.buildPlatform.isStatic}"
218 "${setHost}.crt-static=${lib.boolToString stdenv.hostPlatform.isStatic}"
219 "${setTarget}.crt-static=${lib.boolToString stdenv.targetPlatform.isStatic}"
220 ]
221 ++ optionals (!withBundledLLVM) [
222 "--enable-llvm-link-shared"
223 "${setBuild}.llvm-config=${llvmSharedForBuild.dev}/bin/llvm-config"
224 "${setHost}.llvm-config=${llvmSharedForHost.dev}/bin/llvm-config"
225 "${setTarget}.llvm-config=${llvmSharedForTarget.dev}/bin/llvm-config"
226 ]
227 ++ optionals fastCross [
228 # Since fastCross only builds std, it doesn't make sense (and
229 # doesn't work) to build a linker.
230 "--disable-llvm-bitcode-linker"
231 ]
232 ++ optionals (stdenv.targetPlatform.isLinux && !(stdenv.targetPlatform.useLLVM or false)) [
233 "--enable-profiler" # build libprofiler_builtins
234 ]
235 ++ optionals stdenv.buildPlatform.isMusl [
236 "${setBuild}.musl-root=${pkgsBuildBuild.targetPackages.stdenv.cc.libc}"
237 ]
238 ++ optionals stdenv.hostPlatform.isMusl [
239 "${setHost}.musl-root=${pkgsBuildHost.targetPackages.stdenv.cc.libc}"
240 ]
241 ++ optionals stdenv.targetPlatform.isMusl [
242 "${setTarget}.musl-root=${pkgsBuildTarget.targetPackages.stdenv.cc.libc}"
243 ]
244 ++ optionals stdenv.targetPlatform.rust.isNoStdTarget [
245 "--disable-docs"
246 ]
247 ++ optionals (stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isx86_64) [
248 # https://github.com/rust-lang/rust/issues/92173
249 "--set rust.jemalloc"
250 ]
251 ++ optionals (useLLVM && !stdenv.targetPlatform.isFreeBSD) [
252 # https://github.com/NixOS/nixpkgs/issues/311930
253 "--llvm-libunwind=${if withBundledLLVM then "in-tree" else "system"}"
254 "--enable-use-libcxx"
255 ]
256 ++ optionals (!stdenv.hostPlatform.isx86_32) [
257 # This enables frame pointers for the compiler itself.
258 #
259 # Note that when compiling std, frame pointers (or at least non-leaf
260 # frame pointers, depending on version) are unconditionally enabled and
261 # cannot be overridden, so we do not touch that. (Also note this only
262 # applies to functions that can be immediately compiled when building
263 # std. Generic functions that do codegen when called in user code obey
264 # -Cforce-frame-pointers specified then, if any)
265 "--set rust.frame-pointers"
266 ];
267
268 # if we already have a rust compiler for build just compile the target std
269 # library and reuse compiler
270 buildPhase =
271 if fastCross then
272 ''
273 runHook preBuild
274
275 mkdir -p build/${stdenv.hostPlatform.rust.rustcTargetSpec}/stage0-{std,rustc}/${stdenv.hostPlatform.rust.rustcTargetSpec}/release/
276 ln -s ${rustc.unwrapped}/lib/rustlib/${stdenv.hostPlatform.rust.rustcTargetSpec}/libstd-*.so build/${stdenv.hostPlatform.rust.rustcTargetSpec}/stage0-std/${stdenv.hostPlatform.rust.rustcTargetSpec}/release/libstd.so
277 ln -s ${rustc.unwrapped}/lib/rustlib/${stdenv.hostPlatform.rust.rustcTargetSpec}/librustc_driver-*.so build/${stdenv.hostPlatform.rust.rustcTargetSpec}/stage0-rustc/${stdenv.hostPlatform.rust.rustcTargetSpec}/release/librustc.so
278 ln -s ${rustc.unwrapped}/bin/rustc build/${stdenv.hostPlatform.rust.rustcTargetSpec}/stage0-rustc/${stdenv.hostPlatform.rust.rustcTargetSpec}/release/rustc-main
279 touch build/${stdenv.hostPlatform.rust.rustcTargetSpec}/stage0-std/${stdenv.hostPlatform.rust.rustcTargetSpec}/release/.libstd-stamp
280 touch build/${stdenv.hostPlatform.rust.rustcTargetSpec}/stage0-rustc/${stdenv.hostPlatform.rust.rustcTargetSpec}/release/.librustc-stamp
281 python ./x.py --keep-stage=0 --stage=1 build library
282
283 runHook postBuild
284 ''
285 else
286 null;
287
288 installPhase =
289 if fastCross then
290 ''
291 runHook preInstall
292
293 python ./x.py --keep-stage=0 --stage=1 install library/std
294 mkdir -v $doc $man
295 ln -s ${rustc.unwrapped}/{bin,libexec} $out
296 rm -rf -v $out/lib/rustlib/{manifest-rust-std-,}${stdenv.hostPlatform.rust.rustcTargetSpec}
297 ln -s ${rustc.unwrapped}/lib/rustlib/{manifest-rust-std-,}${stdenv.hostPlatform.rust.rustcTargetSpec} $out/lib/rustlib/
298 ln -s ${rustc.unwrapped}/lib/rustlib/etc $out/lib/rustlib/
299 echo rust-std-${stdenv.hostPlatform.rust.rustcTargetSpec} >> $out/lib/rustlib/components
300 lndir ${rustc.doc} $doc
301 lndir ${rustc.man} $man
302
303 runHook postInstall
304 ''
305 else
306 null;
307
308 # the rust build system complains that nix alters the checksums
309 dontFixLibtool = true;
310
311 inherit patches;
312
313 postPatch = ''
314 patchShebangs src/etc
315
316 # rust-lld is the name rustup uses for its bundled lld, so that it
317 # doesn't conflict with any system lld. This is not an
318 # appropriate default for Nixpkgs, where there is no rust-lld.
319 substituteInPlace compiler/rustc_target/src/spec/*/*.rs \
320 --replace-quiet '"rust-lld"' '"lld"'
321
322 ${optionalString (!withBundledLLVM) "rm -rf src/llvm"}
323
324 # Useful debugging parameter
325 # export VERBOSE=1
326 ''
327 + lib.optionalString (stdenv.hostPlatform.isDarwin || stdenv.targetPlatform.isDarwin) ''
328 # Replace hardcoded path to strip with llvm-strip
329 # https://github.com/NixOS/nixpkgs/issues/299606
330 substituteInPlace compiler/rustc_codegen_ssa/src/back/link.rs \
331 --replace-fail "/usr/bin/strip" "${lib.getExe' llvmShared "llvm-strip"}"
332 ''
333 + lib.optionalString (stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isx86_64) ''
334 # See https://github.com/jemalloc/jemalloc/issues/1997
335 # Using a value of 48 should work on both emulated and native x86_64-darwin.
336 export JEMALLOC_SYS_WITH_LG_VADDR=48
337 ''
338 + lib.optionalString (!(finalAttrs.src.passthru.isReleaseTarball or false)) ''
339 mkdir .cargo
340 cat > .cargo/config.toml <<\EOF
341 [source.crates-io]
342 replace-with = "vendored-sources"
343 [source.vendored-sources]
344 directory = "vendor"
345 EOF
346 ''
347 + lib.optionalString (stdenv.hostPlatform.isFreeBSD) ''
348 # lzma-sys bundles an old version of xz that doesn't build
349 # on modern FreeBSD, use the system one instead
350 substituteInPlace src/bootstrap/src/core/build_steps/tool.rs \
351 --replace 'cargo.env("LZMA_API_STATIC", "1");' ' '
352 '';
353
354 # rustc unfortunately needs cmake to compile llvm-rt but doesn't
355 # use it for the normal build. This disables cmake in Nix.
356 dontUseCmakeConfigure = true;
357
358 depsBuildBuild = [
359 pkgsBuildHost.stdenv.cc
360 pkg-config
361 ];
362 depsBuildTarget = lib.optionals stdenv.targetPlatform.isMinGW [ bintools ];
363
364 nativeBuildInputs = [
365 file
366 python3
367 rustc
368 cmake
369 which
370 libffi
371 removeReferencesTo
372 pkg-config
373 xz
374 ]
375 ++ optionals fastCross [
376 lndir
377 makeWrapper
378 ];
379
380 buildInputs = [
381 openssl
382 ]
383 ++ optionals stdenv.hostPlatform.isDarwin [
384 zlib
385 ]
386 ++ optional (!withBundledLLVM) llvmShared.lib
387 ++ optional (useLLVM && !withBundledLLVM) llvmPackages.libunwind;
388
389 outputs = [
390 "out"
391 "man"
392 "doc"
393 ];
394 setOutputFlags = false;
395
396 postInstall =
397 lib.optionalString (enableRustcDev && !fastCross) ''
398 # install rustc-dev components. Necessary to build rls, clippy...
399 python x.py dist rustc-dev
400 tar xf build/dist/rustc-dev*tar.gz
401 cp -r rustc-dev*/rustc-dev*/lib/* $out/lib/
402 rm $out/lib/rustlib/install.log
403 for m in $out/lib/rustlib/manifest-rust*
404 do
405 sort --output=$m < $m
406 done
407
408 ''
409 + ''
410 # remove references to llvm-config in lib/rustlib/x86_64-unknown-linux-gnu/codegen-backends/librustc_codegen_llvm-llvm.so
411 # and thus a transitive dependency on ncurses
412 find $out/lib -name "*.so" -type f -exec remove-references-to -t ${llvmShared} '{}' '+'
413
414 # remove uninstall script that doesn't really make sense for Nix.
415 rm $out/lib/rustlib/uninstall.sh
416 '';
417
418 configurePlatforms = [ ];
419
420 enableParallelBuilding = true;
421
422 setupHooks = ./setup-hook.sh;
423
424 requiredSystemFeatures = [ "big-parallel" ];
425
426 passthru = {
427 llvm = llvmShared;
428 inherit llvmPackages;
429 inherit (rustc) targetPlatforms targetPlatformsWithHostTools badTargetPlatforms;
430 tests = {
431 inherit fd ripgrep wezterm;
432 }
433 // lib.optionalAttrs stdenv.hostPlatform.isLinux { inherit firefox thunderbird; };
434 };
435
436 meta = with lib; {
437 homepage = "https://www.rust-lang.org/";
438 description = "Safe, concurrent, practical language";
439 maintainers = with maintainers; [ havvy ];
440 teams = [ teams.rust ];
441 license = [
442 licenses.mit
443 licenses.asl20
444 ];
445 platforms = rustc.targetPlatformsWithHostTools;
446 # If rustc can't target a platform, we also can't build rustc for
447 # that platform.
448 badPlatforms = rustc.badTargetPlatforms;
449 };
450})