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

Tutorial for cross-compilation #160

Merged
merged 9 commits into from
Jun 17, 2021
Merged

Conversation

domenkozar
Copy link
Member

@domenkozar domenkozar commented Jun 14, 2021

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jun 14, 2021

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 608df83
Status: ✅  Deploy successful!
Preview URL: https://5031d9ad.nix-dev.pages.dev

View logs

Copy link
Member

@sternenseemann sternenseemann left a comment

Choose a reason for hiding this comment

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

Rough first pass.

`autoconf terminology <https://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Hosts-and-Cross_002dCompilation.html>`_.

.. note:: macOS/Darwin is a special case, as not the whole OS is Open Source.
It's only possible to cross-compile between ``aarch64-darwin`` and ``x86_64-darwin``.
Copy link
Member

Choose a reason for hiding this comment

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

Technically only from x86_64-darwin to aarch64-darwin. Not sure if I'd mention this here though, as aarch64-darwin is far from mature at this point and cross compilation is not working at all in any channel currently…

Copy link
Member Author

Choose a reason for hiding this comment

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

Technically only from x86_64-darwin to aarch64-darwin.

Is that because we're missing pkgsCross.x86_64-darwin or something else?

Not sure if I'd mention this here though, as aarch64-darwin is far from mature at this point and cross compilation is not working at all in any channel currently…

Since the fix from @alyssais should work, the whole cross-compilation is not well supported but I'd like this tutorial to show that it's entirely feasible with enough maintainers. WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Is that because we're missing pkgsCross.x86_64-darwin or something else?

I'm not sure as to why exactly, but I can confirm that nixpkgs can't even cross compile from x86_64-darwin to x86_64-darwin due to an evaluation problem very early in stdenv.

the whole cross-compilation is not well supported but I'd like this tutorial to show that it's entirely feasible with enough maintainers

Still, aarch64-linux for example has had more time to mature, accumulate fixes etc. Maybe not mentioning aarch64-darwin saves someone a bit of frustration?

Copy link
Member Author

Choose a reason for hiding this comment

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

How about a disclaimer that we've just added support for it?

Copy link
Member Author

Choose a reason for hiding this comment

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

I've added a disclaimer and move this further down.

source/tutorials/cross-compilation.rst Outdated Show resolved Hide resolved
source/tutorials/cross-compilation.rst Outdated Show resolved Hide resolved
source/tutorials/cross-compilation.rst Outdated Show resolved Hide resolved
as you'd then like to build a compiler on the build platform, compile code on the
target plaform and run the final executable on the host platform.

Since that's rarely needed, we'll treat target platform the same as the build.
Copy link
Member

Choose a reason for hiding this comment

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

Just write that you ignore the platform for this tutorial. The default case of targetPlatform is the same as hostPlatform afaik.

Also interesting to note: On the outermost interface nixpkgs doesn't support targetPlatform, it only accepts localSystem (~ build) and crossSystem (~ host) since cross compiling a cross compiler is a rare use case outside of stdenv bootstrapping.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd like to clarify since it took me quite some time to figure out why target is not important and explain why it can be ignored to save confusion. The user will eventually stumble upon it as they dive into cross-compilation.

Copy link
Member

Choose a reason for hiding this comment

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

I've thought about this yesterday and maybe it would actually help the tutorial to explain target completely. This would make it very easy to clear the following things up:

  • Why are binutils, pkg-config and cc prefixed? What does the prefix mean? This is quite easy to answer if you explain the relation of the target prefix to the target platform
  • What is buildPackages? Here having an understanding of target is nice: buildPackages usually has build and host platform equivalent to localSystem, but its target is crossSystem — having an understanding of target automatically explains how we bootstrap our cross package sets

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed! I don't think I know enough to really explain those, unfortunately.

-----------------------------

The build platform is determined automatically by Nix
as it can just guess it during the configure phase.
Copy link
Member

Choose a reason for hiding this comment

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

You can also set it by passing localSystem.

Copy link
Member Author

Choose a reason for hiding this comment

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

In what cases is that really needed? In most cases guessing should be sufficient?

Copy link
Member

Choose a reason for hiding this comment

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

The two main use cases would be remote build (if you have a builder with a different platform than your local machine) and testing evaluation of cross-related things by simulating being on another platform.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd like to mention that then in #157 tutorial (and consider developing nixpkgs out of scope here).

source/tutorials/cross-compilation.rst Show resolved Hide resolved
source/tutorials/cross-compilation.rst Outdated Show resolved Hide resolved
source/tutorials/cross-compilation.rst Show resolved Hide resolved
source/tutorials/cross-compilation.rst Outdated Show resolved Hide resolved
@domenkozar
Copy link
Member Author

domenkozar commented Jun 15, 2021

cc @zimbatm @zupo (for all the previous help with tutorials!) @Ericson2314 @matthewbauer @alyssais (for cross-compilation expertise) @otobrglez @boxofrox (for thumbs up on the tutorial issue)

@k-bx
Copy link

k-bx commented Jun 15, 2021

Apologies for a noob question, but I'm getting this:

$ nix-shell --run '$CC hello.c -o hello' cross-compile-shell.nix
error: undefined variable 'file' at /home/kb/workspace/nix-crosscompile-test/cross-compile-shell.nix:8:36
$ cat cross-compile-shell.nix
{ nixpkgs ? fetchTarball "https://github.com/NixOS/nixpkgs/archive/bba3474a5798b5a3a87e10102d1a55f19ec3fca5.tar.gz"
, pkgs ? (import nixpkgs {}).pkgsCross.aarch64-multiplatform
}:

# pkgs.callPackage is needed due to https://github.com/NixOS/nixpkgs/pull/126844
pkgs.callPackage ({ mkShell, zlib, pkg-config }: mkShell {
  # these tools run on the build platform, but are configure to target the target platform
  nativeBuildInputs = [ pkg-config file ];
  # libraries needed for the target platform
  buildInputs = [ zlib ];
}) {}

Am I doing something wrong?

@nh2
Copy link
Contributor

nh2 commented Jun 15, 2021

@k-bx In

nativeBuildInputs = [ pkg-config file ];

the pkg-config comes from the lambda arguments { mkShell, zlib, pkg-config }: mkShell, but file doesn't. You need to add it to those arguments so that it is in scope.

@domenkozar
Copy link
Member Author

Apologies for a noob question, but I'm getting this:

$ nix-shell --run '$CC hello.c -o hello' cross-compile-shell.nix
error: undefined variable 'file' at /home/kb/workspace/nix-crosscompile-test/cross-compile-shell.nix:8:36
$ cat cross-compile-shell.nix
{ nixpkgs ? fetchTarball "https://github.com/NixOS/nixpkgs/archive/bba3474a5798b5a3a87e10102d1a55f19ec3fca5.tar.gz"
, pkgs ? (import nixpkgs {}).pkgsCross.aarch64-multiplatform
}:

# pkgs.callPackage is needed due to https://github.com/NixOS/nixpkgs/pull/126844
pkgs.callPackage ({ mkShell, zlib, pkg-config }: mkShell {
  # these tools run on the build platform, but are configure to target the target platform
  nativeBuildInputs = [ pkg-config file ];
  # libraries needed for the target platform
  buildInputs = [ zlib ];
}) {}

Am I doing something wrong?

Not at all, I added file and didn't do the full test again. Should be fixed now!

@k-bx
Copy link

k-bx commented Jun 15, 2021

Aha! It worked now, thanks. Sorry for hijacking the thread, but I'm trying to build hello.c for my Raspberry Pi Zero. What should I put instead of aarch64-multiplatform in that cross-compile-shell.nix exactly?

UPDATE: ok, figured it's just raspberryPi hehe :)

@domenkozar
Copy link
Member Author

Aha! It worked now, thanks. Sorry for hijacking the thread, but I'm trying to build hello.c for my Raspberry Pi Zero. What should I put instead of aarch64-multiplatform in that cross-compile-shell.nix exactly?

UPDATE: ok, figured it's just raspberryPi hehe :)

https://b5839745.nix-dev.pages.dev/tutorials/cross-compilation#determining-the-host-platform is supposed to teach how to determine platform config.

https://b5839745.nix-dev.pages.dev/tutorials/cross-compilation#choosing-the-host-platform-with-nix is supposed to teach how to pick the right attribute with that platform config in mind.

Let me know where my explaining went south :)

@k-bx
Copy link

k-bx commented Jun 15, 2021

@domenkozar oh, I see. I was confused by the terminology, assuming that the host is the machine that does the building, and the "target" (or some other term) is my raspberryPi, since that command runs nix to determine the architecture, and I would assume you wouldn't need to install Nix on raspberry pi. Now I see that you would.

@k-bx
Copy link

k-bx commented Jun 15, 2021

Again apologies for spamming, just trying to get that raspberry pi zero hello world to work.

Installing nix is not an option (sorry, there is no binary distribution of Nix for your platform) to determine the host platform. I would assume it's armv6*. If I were to try armv6l-unknown-linux-gnueabihf mentioned in that tutorial, I'd get an error of that attribute missing:

$ nix-shell --run '$CC hello.c -o hello' cross-compile-shell.nix
error: attribute 'armv6l-unknown-linux-gnueabihf' missing, at /home/kb/workspace/nix-crosscompile-test/cross-compile-shell.nix:2:10
(use '--show-trace' to show detailed location information)

$ cat ./cross-compile-shell.nix
{ nixpkgs ? fetchTarball "https://github.com/NixOS/nixpkgs/archive/bba3474a5798b5a3a87e10102d1a55f19ec3fca5.tar.gz"
, pkgs ? (import nixpkgs {}).pkgsCross.armv6l-unknown-linux-gnueabihf
}:

# pkgs.callPackage is needed due to https://github.com/NixOS/nixpkgs/pull/126844
pkgs.callPackage ({ mkShell, zlib, pkg-config, file }: mkShell {
  # these tools run on the build platform, but are configure to target the target platform
  nativeBuildInputs = [ pkg-config file ];
  # libraries needed for the target platform
  buildInputs = [ zlib ];
}) {}

Am I not reading the tutorial correctly? Again, sorry for noob stuff.

@domenkozar
Copy link
Member Author

@k-bx thanks, this is really useful! I've added a sentence to hint how to get platform config if it's not possible to use Nix:

If you can't install Nix, find a way to run config.guess (usually comes with
the autoconf package) from the OS you're able to run on the host platform.

pkgsCross attributes are made-up names that map into the platform config. I've clarified that in the tutorial.

Unfortunately, there's no simple way to show us all attribute name -> platform config pairs.

@k-bx
Copy link

k-bx commented Jun 15, 2021

Aha! Interestingly, config.guess is not in PATH, but found it in /usr/share/misc/config.guess. It's armv6l-unknown-linux-gnueabihf indeed. Due to https://github.com/NixOS/nixpkgs/blob/master/lib/systems/examples.nix#L38 the value raspberryPi was indeed the right one. However, if I copy the resulting executable on my rpi0, I get this:

pi@raspberrypi:~ $ ls -la
total 88
drwxr-xr-x 10 pi   pi    4096 Jun 15 12:45 .
drwxr-xr-x  3 root root  4096 Mar  4 22:47 ..
-rwxr-xr-x  1 pi   pi   11600 Jun 15 15:46 hello
pi@raspberrypi:~ $ ./hello 
-bash: ./hello: No such file or directory
pi@raspberrypi:~ $ ldd ./hello 
        /usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so => /usr/lib/arm-linux-gnueabihf/libarmmem-v6l.so (0xb6f86000)
        libc.so.6 => /usr/lib/arm-linux-gnueabihf/libc.so.6 (0xb6e38000)
        /nix/store/bs0yybprj2jdz4gk4g2738pnfbraaxh2-glibc-2.31-74-armv6l-unknown-linux-gnueabihf/lib/ld-linux-armhf.so.3 => /lib/ld-linux-armhf.so.3 (0xb6f99000)

That's why I thought raspberryPi wasn't the right value initially. Do you know what could be the reason for this?

Unfortunately, there's no simple way to show us all attribute name -> platform config pairs.

That is indeed one of the pain-points of Nix: you see the final spell, but it's hard to inspect what other keys and values are available. I hope to dive into Nix near time and see if I can help improve this situation.

@sternenseemann
Copy link
Member

sternenseemann commented Jun 15, 2021

@k-bx The no such file or directory is the dynamic linker telling you that it is not finding the libraries linked against. To fix that you can either a) compile statically by passing -static to $CC or b) copy all dependencies of your executable. Finding this out for a binary built with nix-shell is a bit annoying, but if you have a proper derivation you could use nix-store --requisites <realised store path>.

The topic of dynamic linking and dependencies should be mentioned in the tutorial as well. For the example it is probably easier just using static linking though.

@k-bx
Copy link

k-bx commented Jun 15, 2021

@sternenseemann thank you! May I just mention that adding -static will produce an error:

/nix/store/s8j8w9kik99ffxcgwz3bdpk71ijr3qj7-armv6l-unknown-linux-gnueabihf-binutils-2.31.1/bin/armv6l-unknown-linux-gnueabihf-ld: cannot find -lc

I've tried adding glibc, that didn't help. libc is not found when accepted as lambda argument, not sure what exactly to do next (again, probably due to lack of linker knowledge/experience).

@sternenseemann
Copy link
Member

@k-bx Oh yeah, you need to use the static output for glibc, i. e. glibc.static or alternatively musl, but in that case you probably want to switch stdenv (static compilation is also a lot easier if you actually use a static stdenv of course…).

@k-bx
Copy link

k-bx commented Jun 15, 2021

@sternenseemann thank you! Changing buildInputs argument to glibc.static indeed helped!

I guess I finally have a starting point of how to get from here to a point where I can cross-compile my Rust project that is using some native libraries :)

@sternenseemann
Copy link
Member

sternenseemann commented Jun 15, 2021

@k-bx Yeah, this is something we should take into consideration as well. There is some special casing to be done for rust possibly, I'm not quite sure.

In general the way would be:

  • Package your project normally as a nix derivation, take extra care to properly distinguish between nativeBuildInputs and buildInputs (see manual) and add it via an overlay to nixpkgs
  • Then compiling pkgsCross.<platform>.my-package should just work (if everything is supported)
    • For shared executables (the default case) you need to copy the entire closure (i. e. what nix-store --requisites prints) while preserving its location (i. e. everything must be under /nix/store). If you have nix on the other machine, you can use nix-copy-closure for this.
    • static executables can just be copied as is, you can build your derivation statically using pkgsCross.<platform>.pkgsStatic.my-package

source/tutorials/cross-compilation.rst Outdated Show resolved Hide resolved
source/tutorials/cross-compilation.rst Outdated Show resolved Hide resolved
Co-authored-by: John Ericson <git@JohnEricson.me>
@domenkozar
Copy link
Member Author

Thanks everyone for the feedback!

I've included most of it in the last commit, please take another look!

@domenkozar domenkozar force-pushed the cross-compilation-tutorial branch 2 times, most recently from c4e37e2 to 665302f Compare June 17, 2021 08:57
@domenkozar
Copy link
Member Author

@sternenseemann thank you! Changing buildInputs argument to glibc.static indeed helped!

I guess I finally have a starting point of how to get from here to a point where I can cross-compile my Rust project that is using some native libraries :)

The example for developer environment uses now a cross compiler that emits static binaries. Thanks for bringing this up!

@domenkozar
Copy link
Member Author

I'm going to merge this in a few hours to prevent perfect becoming the enemy of the good.

Let me know if I missed some feedback and happy to address it further, but I might convert it into issues to keep things going.

@domenkozar domenkozar merged commit ec9298d into master Jun 17, 2021
@github-actions github-actions bot locked and limited conversation to collaborators Jun 17, 2021
@fricklerhandwerk fricklerhandwerk deleted the cross-compilation-tutorial branch October 9, 2023 15:22
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cross-compilation tutorial
7 participants