rcl/flake.lock
Ruud van Asseldonk ca9ab7eece Build a Python manylinux wheel with Maturin and Nix
This is a squash and rebase of a lot of messy work over the past few
evenings. In the end the diff doesn't look so bad, but the path to get
there ...

== THE GOAL ==

I want to publish the Python module to Pypi, and include a wheel. I want
to automate building it with Nix, so that it's a *single* step, and it
can run on every commit.

Building a wheel that is widely compatible, is somewhat challenging. One
way to achieve it is to build in a container, using an image of some
ancient Linux distro. I don't want Docker in my build process, I want a
single command that builds anything and that always works. Fortunately,
when using Zig as the linker, we can build a manylinux compatible wheel,
and Maturin supports that. So we want to call Maturin from inside Nix to
build the wheel, that's the goal.

== CONSTRAINTS ==

The Nixpkgs snapshot I was using shipped Maturin 1.1. Maturin versions
before 1.9.2 contain a bug where the set the author metadata incorrectly.
So I need 1.9.2. We can't *just* override it though, because Maturin
1.9.2 depends on a library that only compiles with Rust 1.73.0. So we
need to update Rust too.

Now we have two choices: update the Nixpkgs snapshot to one with Rust
1.73 or later, or take Rust from the binary overlay. I explored both,
backtracked, backtracked again, and reverted, but in the end, using the
binary was infeasible. Why? Because the coverage build needs LLVM tools
that we take from Nixpkgs, and the version needs to be compatible. So we
bump the Rust toolchain, see below for the version rationale. It may be
possible to get LLVM tools from the binaries overlay, but the component
that is shipped by rustup does not include the coverage binaries, and
the outlook started looking more and more bleak, taking them from
Nixpkgs just works, and though it adds some constraints, it sidesteps
many problems.

Another place where we take tools from Nixpkgs, is the WASM build. This
requires a nightly compiler. That compiler needs to be recent enough to
be compatible, but because we include rust-src dependencies in our
lockfile to be able to build that component in Nix, the dependencies
can't be *that* recent that they require Rust Edition 2021, because that
is not supported by Rust 1.75. So finding the right nightly was a
challenge too. The nightly that coincides with 1.75 is already too new,
but one 6 weeks prior worked.

Wthe new Nixpkgs snapshot, aside from the WASM tools, we also get a new
version of Tree-sitter. Which of course broke things, but they were
relatively easy to repair. There are also additional warnings now, but
those can be fixed later.

== RUST VERSION ==

Pick Rust 1.75, because that is the version that is currently shipped
in Ubuntu 22.04 Jammy, which is the oldest supported Ubuntu LTS at
this time. I would like to consider Debian oldstable (Bookworm at this
time) as well, but it ships rustc 1.63, which is already older than the
version RCL was using anyway. Debian stable (Trixie) ships 1.85 at this
time. So I think 1.75 is a safe bet for wide compatibility, without
being ancient.

== ZIG CACHE DIR ==

I don't want Docker in my build process. Fortunately, Maturin features
this --zig mode to build a manylinux wheel. Calling it from Nix turned
out to be ... possible, but not trivial. I spent about two hours
debugging an AccessDenied error, which turned out to be due to [1]. When
you read that one, in hindsight it makes sense, but the lengths I had to
go through to figure out *what* was printing AccessDenied and why ...

[1]: github ziglang zig issue 6810

== NIXPKGS & CARGO AUDITABLE ==

In the process of sorting this out, I first got things working with
the older Nixpkgs snapshot, and the Maturin and Zig in there. But when
I needed a newer version of Maturin, things broke. Maturin's manylinux
wheel build with --zig worked in the older version of Nixpkgs, but then
in a newer release it started using a linker flag (--undefined) that Zig
did not understand. After several more hours of debugging, I learned
that that flag is injected by cargo-auditable [2], which despite not
being used by Maturin or cargo-zigbuild, is being used because Nixpkgs
silently replaces cargo with cargo-auditable by default. After learning
that, it was easy enough to turn off. Also just one line, but the effort
to discover that one line ...

[2]: github rust-cross cargo-zigbuild issue 162

== WASM ==

At some point in the process, I was using a later nightly to build the
WASM binary, and I needed to add the --enable-bulk-memory to wasm-opt.

== MATURIN 1.9.3 ==

With Maturin updated, the author metadata is fixed, but it introduced
a new regression: recent versions stop tagging the manylinux wheel
properly. It would get tagged as linux_x86_64, rather than the right
manylinux tags. Fortunately, the Python 'auditwheel' package can fix
this. So I added that one to the build, at that point still from a more
recent Nixpkgs snapshot. Then I pinned Nixpkgs to one with Rust 1.75,
which downgraded auditwheel, and that broke the command line: the newer
version automatically detects compatibility, but the older one needs an
explicit target with --plat. That could be fixed, but then the older
version would still build wheels that contained an unnecessary (and
empty) lib dir, so just pin it to a newer version too.

== VERSION BUMPS SUMMARY ==

 * Rust 1.70.0 -> 1.75.0.
 * Nixpkgs goes along to one with Rust 1.75.0.
 * Rust nightly to 2023-11-09, and Cargo.lock to update the dependencies
   of rust-std along with it.
 * Wasm-bindgen 0.2.84 -> 0.2.89 due to the Nixpkgs bump.
 * Tree-sitter update due to the Nixpkgs bump.

== RESULT ==

With all of this in place, we can now nix build .#pyrcl-wheel, and we
get a portable manylinux wheel that can be used directly with uv, and
can be uploaded to Pypi!
2025-08-28 23:07:31 +02:00

48 lines
1.1 KiB
JSON

{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1708815994,
"narHash": "sha256-hL7N/ut2Xu0NaDxDMsw2HagAjgDskToGiyZOWriiLYM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9a9dae8f6319600fa9aebde37f340975cab4b8c0",
"type": "github"
},
"original": {
"id": "nixpkgs",
"rev": "9a9dae8f6319600fa9aebde37f340975cab4b8c0",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1735525800,
"narHash": "sha256-pcN8LAL021zdC99a9F7iEiFCI1wmrE4DpIYUgKpB/jY=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "10faa81b4c0135a04716cbd1649260d82b2890cd",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "10faa81b4c0135a04716cbd1649260d82b2890cd",
"type": "github"
}
}
},
"root": "root",
"version": 7
}