If you've ever struggled with Rust packaging, here's some good news!
We have changed to a simplified Rust packaging model that is easier to automateand allows for modification, replacement and deletion of dependencies at thesame time. The new model will significantly reduce our Rust packaging time andwill help us to improve both package availability and quality.
Those changes are currently on the rust-team branch, slated to be merged inthe coming weeks.
How good is the news? Migration of our current Rust package collection, 150+applications with 3600+ dependency libraries, only took two weeks, all by oneperson! :)
See #387, if you want to track thecurrent progress and give feedback. I'll request merging the rust-team branchwhen the pull request is merged. After merging the branch, a news entry will beissued for guix pull.
Upcoming changesThe previous packaging model for Rust in Guix would map one crate (Rust package)to one Guix package. This seemed to make sense but there's a fundamentalmismatch here: while Guix packages—from applications like GIMP and Inkscape to Clibraries like GnuTLS and Nettle—are meant to be compiled independently, Rustapplications are meant to be compiled as a single unit together with all thecrates they depend on, recursively. That mismatch meant that Guix would buildeach crate independently, but that build output was of no use at all.
The new model instead focuses on definingoriginsfor crates, with actual builds happening only on the "leaves" of the graph—Rustapplications. This is a major change with many implications, as we will seebelow.
guix import cratewill support importing from Cargo.lock using the new --lockfile / -foption.
guix import --insert=gnu/packages/rust-crates.scm \ crate --lockfile=/path/to/Cargo.lock PACKAGEguix import -i gnu/packages/rust-crates.scm \ crate -f /path/to/Cargo.lock PACKAGE
To avoid conflicts with the new lockfile importer, thecrates.io importer will be altered so it will nolonger support importing dependencies.
A new procedure, cargo-inputs-from-lockfile, will be added for use in theguix.scmof Rust projects. Note that Cargo workspaces in dependencies require manualintervention and are therefore not handled by this procedure.
(use-modules (guix import crate))(package ... (inputs (cargo-inputs-from-lockfile "Cargo.lock")))
2. Build system
cargo-build-sytemwill support directory inputs and Cargo workspaces.
Build phase check-for-pregenerated-files will scan all unpacked sourcesand print out non-empty binary files.
We won't accept contributions using the old packaging approach(#:cargo-inputs and #:cargo-development-inputs) anymore. Its support isdeprecated and will be removed after Dec. 31, 2026.
3. Packages
Rust libraries will be stored in two new modules and will be hidden from theuser interface:
* `(gnu packages rust-sources)`
Rust libraries that require a build process or complex modificationinvolving external dependencies to unbundle dependencies.
* `(gnu packages rust-crates)`
Rust libraries imported using the lockfile importer. This moduleexports a `lookup-cargo-inputs` interface, providing an identifier ->libraries mapping.
Libraries defined in this module can be modified via snippets andpatches,replaced by changing their definitions to point to other variables, orremoved by changing their definitions to `#f`. The importer will skipexisting libraries to avoid overwriting modifications.
A template file for this module will be provided as`etc/teams/rust/rust-crates.tmpl` in Guix source tree, for use in externalchannels.All other libraries (those currently in `(gnu packages crates-...)`) **willbe moved to an external channel**. If you have packages depending on them,please add thischannel and useits `(past-crates packages crates-io)` module to avoid possible breakage. Oncemerged, you can migrate your packages and safely remove the channel.
(channel (name 'guix-rust-past-crates) (url "https://codeberg.org/guix/guix-rust-past-crates.git") (branch "trunk") (introduction (make-channel-introduction "1db24ca92c28255b28076792b93d533eabb3dc6a" (openpgp-fingerprint "F4C2D1DF3FDEEA63D1D30776ACC66D09CA528292"))))
4. Documentation
API references forcargo-build-sytemand packaging guidelines for Rustcrateswill be updated. A packaging workflow built upon the new features will beadded under the Packagingchapter of GuixCookbook.
BackgroundCurrently, our Rust packaging uses the traditional approach, treating eachapplication and library equally.
This brings issues. Firstly on packaging and maintenance, due to the largenumber of libraries with limited people working on it, plus we can't reuse thosepackaged libraries so instead of the built libraries, their sources areextracted and used in the build process. As a result, the packaging experienceis not very smooth, although the crates.io importer has helped mitigate this tosome extent.
Secondly on the user interface, thousands of Rust libraries that can't be usedby the user appear in the search result. Documentation can't be taken good careof for all these packages as well, understandably.
Lastly, the inconsistency in the packaging interface. Our dependency modelcannot perfectly map to Rust's, and circular dependencies are possible. Tosolve this, build system arguments #:cargo-inputs and#:cargo-development-inputs were introduced and used for specifying Rustlibraries, instead of the regular propagated-inputs and native-inputs.Additionally, inputs propagation logic had to be reimplemented for them, whichresulted in additional performance overhead.
Approaches have been proposed to improve the situation, notably theantioxidant buildsystem developed by Maxime Devos, and thecargo2guix tool developed by Murilo andLuis Guilherme Coelho:
The antioxidant build system builds Rust packages without Cargo, instead thebuild process is fully managed by Guix by invoking rustc directly.
This build system would allow Guix to produce and share build artifacts forRust libraries. It's a step towards making our work on the current approachmore reasonable.
However there's a downside. Since this is not what the Rust communityexpects, we'd also have to heavily patch many Rust packages, which wouldmake it even harder for us to move forward. 2. cargo2guix
This tool parses Cargo.lock and outputs package definitions. It's morereliable than the crates.io importer, since dependencies are already knownoffline. It should be the most efficient improvement for the currentapproach. The upcoming importer update integrates a modified version ofthis tool.
Murilo also proposes to package Rust applications in self-contained modules,each module containing a Rust application with all its dependencies, inorder to reduce merge conflicts. However, one same library will be definedin multiple modules, duplicating the effort to check and manage them. 3. Let Cargo download dependencies
This is the "vendoring" approach, used in some distributions and can beimplemented as a fixed-output derivation.
We don't use this approach since the dependency information is completelyhidden from us. We can't locate a library easily when we want to modify orreplace it. If we made a mistake on checking dependencies, it could be verydifficult to find out later.
Another downside is that downloading of a single library can't bededuplicated. Since we use an isolated build environment, commonly usedlibraries will be downloaded repeatedly, despite already available in thestore.
After reading the recentdiscussion,I thought about these existing approaches in the hope of finding one that doesonly the minimum necessary: since users can't use our packaged libraries,there's no reason to insist on the traditional approach -> libraries can behidden from the user interface -> user-facing documentations are not needed ->since metadata is not used at this stage, why bother defining apackagefor the library?
Actually cargo2guix is more suitable for importing sources rather than packages,as it has issues handling licenses, and Cargo.lock only contains enoughinformation to construct the sourcerepresentationin Guix, which has support for simple patching.
Since the vendoring approach exists, packaging all Rust libraries as sourcesonly has been proven effective. However, we'll lose important information inour representation when switching from packages to sources: license anddependency. Thanks to the awesomecargo-license tool, only the latterrequired further consideration.
The implementation has been changed a few times in the review process, but theidea remains: make automation and manual intervention coexist. As a result, theimporter:
Despite proposing it, I was a bit worried about the mapping, which referencesall dependency libraries directly, but the result went quite well: with compactsource definitions, we reduced 153k lines of definitions for Rust libraries to42k after this migration.
(define rust-unindent-0.2.4 (crate-source "unindent" "0.2.4" "1wvfh815i6wm6whpdz1viig7ib14cwfymyr1kn3sxk2kyl3y2r3j"))(define rust-ureq-2.10.0.1cad58f (origin (method git-fetch) (uri (git-reference (url "https://github.com/algesten/ureq") (commit "1cad58f5a4f359e318858810de51666d63de70e8"))) (file-name (git-file-name "rust-ureq" "2.10.0.1cad58f")) (sha256 (base32 "1ryn499kbv44h3lzibk9568ln13yi10frbpjjnrn7dz0lkrdin2w"))))
* Library with modification:
(define rust-libmimalloc-sys-0.1.24 (crate-source "libmimalloc-sys" "0.1.24" "0s8ab4nc33qgk9jybpv0zxcb75jgwwjb7fsab1rkyjgdyr0gq1bp" #:snippet '(begin (delete-file-recursively "c_src") (delete-file "build.rs") (with-output-to-file "build.rs" (lambda _ (format #t "fn main() {~@ println!(\"cargo:rustc-link-lib=mimalloc\");~@ }~%"))))))
* Library with replacement, for those requiring a build process withdependencies.
(define rust-pipewire-0.8.0.fd3d8f7 rust-pipewire-for-niri)
* Deleted library:
(define rust-unrar-0.5.8 #f)
* Accessing interface and identifier -> libraries mapping:
(define-cargo-inputs lookup-cargo-inputs (rust-deunicode-1 => (list rust-any-ascii-0.3.2 rust-emojis-0.6.4 rust-itoa-1.0.15 ...)) (rust-pcre2-utf32-0.2 => (list rust-bitflags-2.9.0 rust-cc-1.2.18 rust-cfg-if-1.0.0 ...)) (zoxide => (list rust-aho-corasick-1.1.3 rust-aliasable-0.1.3 rust-anstream-0.6.18 ...)))
* Dependency libraries lookup, module selection is supported:
(cargo-inputs 'rust-pcre2-utf32-0.2)
(define (my-cargo-inputs name) (cargo-inputs name #:module '(my packages rust-crates)))(my-cargo-inputs ...)
Since we have all the dependency information, unpacking any libraries we want toa directory and then running more common tools to check them is possible (somescripts are provided under etc/teams/rust, yet to be rewritten in Guile).You're encouraged to share yours and check the libraries after the merge, andhelp improve the collection ;)
Next stepsOne issue for this model is that all libraries are stored and referenced in onemodule, making merge conflicts harder to resolve.
I'm considering creating a separate repository to manage this module. Wheneverthere's a change, it will be applied into this repository first and then syncedback to Guix.
We can also store dependency specifications and lockfiles in that separaterepository to make the packaging process, which may require changing thespecifications, more transparent. This may also allow automation in updatingdependency libraries.
Thanks for reading! Happy hacking :)