Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

buildRustPackage: support custom targets #95542

Merged
merged 22 commits into from Nov 28, 2020

Conversation

aaronjanse
Copy link
Member

Replaces #95405. Ping @Ericson2314 :-)

I'd like feedback for how to refactor the changes in this PR. I do not recommend this PR is merged as-is. Possible changes might be:

  • using linkFarm instead of heredocs
  • sysroot code could be in its own file
Motivation for this change

tl;dr this makes it possible to write Nix derivations to compile many bare-metal Rust projects

I'm working to compile the Redox kernel using Nix, which involves using a custom json target (x86_64-unknown-none.json) in buildRustPackage.

At the end of the compile process, the target json path is not "shortened" (relevant Cargo code), causing a failure:

    Finished dev [optimized + debuginfo] target(s) in 42.60s
cp: missing destination file operand after 'target//nix/store/dk55xqrgyavw1gizs9b07p71j7przki0-x86_64-unknown-none.json/debug-tmp/'
Try 'cp --help' for more information.
builder for '/nix/store/dhpb8p1nibwr8hw95rymlipcqvw3swrs-redox-iso-latest-x86_64-unknown-redox.drv' failed with exit code 1
error: build of '/nix/store/dhpb8p1nibwr8hw95rymlipcqvw3swrs-redox-iso-latest-x86_64-unknown-redox.drv' failed

Also, compiling to a custom Rust target requires re-compiling the Rust standard library, the code for which is included in this PR.

Things done
  • added handling for when a buildRustPackage target is defined by a JSON file
  • added sysroot generation for custom targets
  • avoided causing existing Rust packages to need to be recompiled
  • Tested using sandboxing (nix.useSandbox on NixOS, or option sandbox in nix.conf on non-NixOS linux)
  • Built on platform(s)
    • NixOS
    • macOS
    • other Linux distributions
  • Tested via one or more NixOS test(s) if existing and applicable for the change (look inside nixos/tests)
  • Tested compilation of all pkgs that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review wip"
  • Tested execution of all binary files (usually in ./result/bin/)
  • Determined the impact on package closure size (by running nix path-info -S before and after)
  • Ensured that relevant documentation is up to date
  • Fits CONTRIBUTING.md.

@aaronjanse
Copy link
Member Author

aaronjanse commented Aug 15, 2020

P.S. @Ericson2314 Redox OS can now be declaratively compiled using Nix! (warning: very long build time)

$ nix-build https://github.com/nix-community/redoxpkgs/archive/vmdisk.tar.gz -A pkgsCross.x86_64-unknown-redox.redox-vmdisk
$ cp result/harddisk.bin .
$ chmod +rw harddisk.bin
$ qemu-system-x86_64 -serial mon:stdio -d cpu_reset -d guest_errors -smp 4 -m 1024 -s -machine q35 -device ich9-intel-hda -device hda-duplex -net nic,model=e1000 -net user -device nec-usb-xhci,id=xhci -device usb-tablet,bus=xhci.0 -enable-kvm -cpu host -drive file=harddrive.bin,format=raw

The magic can be found at https://github.com/nix-community/redoxpkgs/tree/vmdisk/pkgs/redox-vmdisk

@Ericson2314
Copy link
Member

Sorry for not seeing this earlier! (Feel free to harass me on IRC, your work is very important to me :)).

@Ericson2314
Copy link
Member

So this is great, first of all. Really excited.

The specific thing I was talking about is a bit different, but I think will readily combine with this. I startied writing some code to explain what I mean:

diff --git a/pkgs/development/compilers/rust/default.nix b/pkgs/development/compilers/rust/default.nix
index d08b63dd643..32102b03be4 100644
--- a/pkgs/development/compilers/rust/default.nix
+++ b/pkgs/development/compilers/rust/default.nix
@@ -13,14 +13,36 @@
 , llvmPackages_5
 , pkgsBuildTarget, pkgsBuildBuild
 }: rec {
-  toRustTarget = platform: with platform.parsed; let
-    cpu_ = {
-      "armv7a" = "armv7";
-      "armv7l" = "armv7";
-      "armv6l" = "arm";
-    }.${cpu.name} or platform.rustc.arch or cpu.name;
-  in platform.rustc.config
-    or "${cpu_}-${vendor.name}-${kernel.name}${lib.optionalString (abi.name != "unknown") "-${abi.name}"}";
+  _rustTargetJson = platform:
+    builtins.removeAttrs (platform.rustc or {}) [ "name" ];
+
+  _toRustTargetJsonOpt = platform: let
+      targetJson = _rustTargetJson platform;
+      targetFileJsonContents = builtins.toJSON targetJson;
+      targetFileName = (platform.rustc.name or (builtins.hash "sha256" targetFileJsonContents)) + ".json";
+    in
+      if targetJson == {}
+      then null
+      else [ targetFileName targetFileJsonContents ];
+
+  toRustTarget = platform: let
+    noJsonDefaultTargetName = with platform.parsed; let
+      cpu_ = {
+        "armv7a" = "armv7";
+        "armv7l" = "armv7";
+        "armv6l" = "arm";
+      }.${cpu.name} or platform.rustc.arch or cpu.name;
+    in platform.rustc.config
+      or "${cpu_}-${vendor.name}-${kernel.name}${lib.optionalString (abi.name != "unknown") "-${abi.name}"}";
+    jsonAndNameOpt = _toRustTargetJsonOpt platform;
+  in
+    if jsonAndNameOpt == null
+    then noJsonDefaultTargetName
+    else builtins.head jsonAndNameOpt;
+
+  # Don't builtins.readFile this, else eval depends on derivation building
+  # which sadly is not allowed for nixpkgs.
+  toRustTargetTomlFileOpt = platform: ... (_toRustTargetJsonOpt platform) ...;
 
   makeRustPlatform = { rustc, cargo, ... }: rec {
     rust = {

So fill in toRustTargetTomlFileOpt with what you've got, following the comment, and then use this back in the other file.

Hopefully with NixOS/nix#3929 we can stop worrying about builtins.readFile and get rid of the heredoc, but I'm afraid that what you've got so far is largely unavoidable.

I suppose there is one alternative: keep everything in nix until you do one big toToml for everything. You can try that if you like, but I don't much care either way.

@Ericson2314
Copy link
Member

CC @cleverca22 & @Mic92. Looks like we are on the cusp of proper custom target cross-compilation support!

@aaronjanse
Copy link
Member Author

aaronjanse commented Aug 20, 2020

Hmm, I'm having trouble understanding that diff. Are you suggesting that the the target JSON be stored in stdenv.hostPlatform.rustc? I'd also like to note that the TOML code is only used for sysroot code (needed for building targets that rustc itself wasn't compiled for). TOML isn't used for the target JSON.

Either way, based on crossing paths with the work you've done with Rust for custom targets, you seem to be much more experienced with this than I.

@Ericson2314
Copy link
Member

Are you suggesting that the target JSON be stored in stdenv.hostPlatform.rustc?

Yup!

I'd also like to note that the TOML code is only used for sysroot code (needed for building targets that rustc itself wasn't compiled for).

Ah, I didn't realize that. So it is TOML for sysroot, and JSON for everything else?

Either way, based on crossing paths with the work you've done with Rust for custom targets, you seem to be much more experienced with this than I.

I'm really rusty with the details of target JSON etc, vs general idea / nixpkgs-specific stuff, so don't take me too seriously on the nitty-gritty! The basic ideas are:

  • Let's try to specify all target-config-like stuff from stdenv.*Platform. platform.rustc is directly inspired by platform.gcc.
    • Always better to have structured values than files and strings
    • Because build.readFile ...derivation... and import ...derivation... are prohibited for stuff Nixpkgs' hydra builds, let's try to avoid "baking" config to files until as late as possible.
  • Sysrooots are kinda evil, but definitely worth it for now until https://github.com/rust-lang/wg-cargo-std-aware wraps up.
    • Over time, with the work of that working group, standard library and even rustc itself should be built with buildRustPackage or buildRustCrate (or whatever the latest and great riff on those is).

You're doing great so far :), moving the config to stdenv.hostPlatform I don't think is a major deparature from what you've got.

@aaronjanse
Copy link
Member Author

Ah, I didn't realize that. So it is TOML for sysroot, and JSON for everything else?

Correct

You're doing great so far :), moving the config to stdenv.hostPlatform I don't think is a major deparature from what you've got.

Thanks! So I can figure out how to use this for Redox, how would I define a custom stdenv platform for the Redox kernel (which, unlike userspace, blacklists some instructions and lacks a libc)?

Redox userspace supports enough packages that I could create a PR to put that platform in Nixpkgs. The Redox kernel platform, however, would be used for nothing but the Redox kernel. Is there some way to define a stdenv.hostPlatform outside of Redox, then have that platform used for only one package (instead of needing to re-compile rustc etc)?

@Ericson2314
Copy link
Member

Ericson2314 commented Aug 20, 2020

Yeah! Do something like

import <nixpkgs> {
  crossSystem = {
    config = "x86_64-unknown-none-none"; # something like this
    platform.rustc = {
      name = "bare-metal-for-redox-or-whatever-you-want-to-call-it";
      ...jsonContents...
    };
  };
  overlays = [ ... ];
}

If the redox platform is stable, you can put it in lib/systems/platforms.nix. Basically that file is a grab-bag of stuff awaiting to be better organized, so I donn't really care if niche things are in there.

@Ericson2314
Copy link
Member

C.f. mozilla/nixpkgs-mozilla#222. Eventually, it would be nice if mozilla-nixpkgs could work well with this stuff to allow one to seamlessly switch between prebuilt sysroots (original made for rust-up) and nix-built ones.

@aaronjanse
Copy link
Member Author

Oh awesome! Thanks for the explanation. I'll try to work on that this weekend :-)

@Mic92
Copy link
Member

Mic92 commented Aug 21, 2020

Can we have also an derivation that exercise this feature? Rust releases happens quite quickly. We currently have a testplan that includes building firefox/thunderbird on Linux and fd on macOS. We could bundle those up in a attrset and also include a test for custom target so that we can have one command that we use for testing every update.

@aaronjanse
Copy link
Member Author

Can we have also an derivation that exercise this feature?

Yeah, that sounds like a good idea. Maybe add a derivation [1] with a new target (x86_64-unknown-blog_os)?

[1] https://github.com/phil-opp/blog_os/tree/first_edition_post_4

@aaronjanse
Copy link
Member Author

I feel like there should be some way to create a new pkgsCross without using import. Something like pkgsCross.fromCrossSystem { ... }

@aaronjanse
Copy link
Member Author

I've figured out a way to handle Rust targets having information both general to a runtime environment (e.g. CPU architecture) and specific to a project (e.g. project-specific compile flags). I've also written code for generating most of the target information from other attributes in stdenv.hostPlatform.

I'm having trouble putting this idea into words at the moment, but I'm currently waiting on [1] to see if it will work for x86_64

[1] https://users.rust-lang.org/t/why-is-there-no-default-x86-64-unknown-none-target/47825

@aaronjanse
Copy link
Member Author

aaronjanse commented Sep 6, 2020

I plan to push some fixes to this PR tomorrow or on Monday. Specifically, I plan to push code that uses Python to generate the sysroot Cargo.toml

EDIT: I'm still trying to find compute time to compile Rust, as mentioned on IRC. I have an idea, so I'll give it a shot

@aaronjanse
Copy link
Member Author

I've added a test for compiling blog_os and cleaned up a little. I put these in changes small commits so that they're easy to review.

I added assert useSysroot -> !(args.doCheck or true) and relevant documentation stating that tests won't work because custom sysroots are being built without the std crate (is this a blocking issue for this PR?).

I also pushed a commit (d884b2d) that sets the RUSTFLAGS environment variable as a derivation attribute instead of injecting it into buildPhase. This seems cleaner, but I'd like for the change to be reviewed since it's a notable last-minute change. I'd be happy to revert this commit if so desired.

P.S. Feel free to rename this PR and the final commit to Nixpkgs to buildRustPackage: support custom targets. I forgot where the original makeRustPlatform text in the title came from.

@aaronjanse
Copy link
Member Author

Note that the new update-lockfile.sh script doesn't update the cargoSha256.

ad-hoc escape hatch to `buildRustPackage` can be removed.

Note that currently custom targets aren't compiled with `std`, so `cargo test`
will fail. This can be ignored by adding `doCheck = false;` to your derivation.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is having the user explicitly disable tests better than silently disabling them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure, which tests are you talking about? Testing if rustc works, or the rust test crate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It refers to the checkPhase of buildRustPackage, where cargo test fails to run on users' code:

λ nix-shell . -A rustPackageWithCustomSysroot
$ cargo build # success
$ cargo test  # error

@Kloenk
Copy link
Member

Kloenk commented Oct 17, 2020

FYI: I have the feeling rust did some breaking changes to sysroot. Not sure if this will affect this pr (some folders are renamed)

@aaronjanse aaronjanse mentioned this pull request Oct 19, 2020
10 tasks
@ehmry ehmry added the 6.topic: exotic Exotic hardware or software platform label Nov 3, 2020
@Ericson2314
Copy link
Member

ugh I missed the test!

See the new docs for details. The difference is vis-a-vis older versions
of this PR, not master.
@Ericson2314 Ericson2314 changed the title makeRustPlatform: support custom targets buildRustPackage: support custom targets Nov 28, 2020
Copy link
Member

@Ericson2314 Ericson2314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally!

@Ericson2314 Ericson2314 merged commit 3049138 into NixOS:master Nov 28, 2020
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/build-rust-app-using-cargos-build-std-feature-with-naersk-fails-because-rust-src-is-missing/13161/5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants