Hilton Chain — June 7, 2025
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 automate and allows for modification, replacement and deletion of dependencies at the same time. The new model will significantly reduce our Rust packaging time and will help us to improve both package availability and quality.
Those changes are currently on the rust-team branch, slated to be merged in the 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 one person! :)
See #387, if you want to track the current progress and give feedback. I'll request merging the rust-team branch when the pull request is merged. After merging the branch, a news entry will be issued for guix pull.
The 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 fundamental mismatch here: while Guix packages—from applications like GIMP and Inkscape to C libraries like GnuTLS and Nettle—are meant to be compiled independently, Rust applications are meant to be compiled as a single unit together with all the crates they depend on, recursively. That mismatch meant that Guix would build each crate independently, but that build output was of no use at all.
The new model instead focuses on defining origins for crates, with actual builds happening only on the "leaves" of the graph—Rust applications. This is a major change with many implications, as we will see below.
Importer
guix import crate will support importing from Cargo.lock using the new --lockfile / -f option.
guix import --insert=gnu/packages/rust-crates.scm \ crate --lockfile=/path/to/Cargo.lock PACKAGE guix import -i gnu/packages/rust-crates.scm \ crate -f /path/to/Cargo.lock PACKAGETo avoid conflicts with the new lockfile importer, the crates.io importer will be altered so it will no longer support importing dependencies.
A new procedure, cargo-inputs-from-lock-file, will be added for use in the guix.scm of Rust projects. Note that Cargo workspaces in dependencies require manual intervention and are therefore not handled by this procedure.
(use-modules (guix import crate)) (package ... (inputs (cargo-inputs-from-lock-file "Cargo.lock")))Build system
cargo-build-sytem will support directory inputs and Cargo workspaces.
Build phase check-for-pregenerated-files will scan all unpacked sources and 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 is deprecated and will be removed after Dec. 31, 2026.
Packages
Rust libraries will be stored in two new modules and will be hidden from the user interface:
(gnu packages rust-sources)
Rust libraries that require a build process or complex modification involving external dependencies to unbundle dependencies.
(gnu packages rust-crates)
Rust libraries imported using the lockfile importer. This module exports a lookup-cargo-inputs interface, providing an identifier -> libraries mapping.
Libraries defined in this module can be modified via snippets and patches, replaced by changing their definitions to point to other variables, or removed by changing their definitions to #f. The importer will skip existing 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 external channels.
All other libraries (those currently in (gnu packages crates-...)) will be moved to an external channel. If you have packages depending on them, please add this channel and use its (past-crates packages crates-io) module to avoid possible breakage. Once merged, 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"))))Documentation
API references for cargo-build-sytem and packaging guidelines for Rust crates will be updated. A packaging workflow built upon the new features will be added under the Packaging chapter of Guix Cookbook.
Currently, our Rust packaging uses the traditional approach, treating each application and library equally.
This brings issues. Firstly on packaging and maintenance, due to the large number of libraries with limited people working on it, plus we can't reuse those packaged libraries so instead of the built libraries, their sources are extracted and used in the build process. As a result, the packaging experience is not very smooth, although the crates.io importer has helped mitigate this to some extent.
Secondly on the user interface, thousands of Rust libraries that can't be used by the user appear in the search result. Documentation can't be taken good care of for all these packages as well, understandably.
Lastly, the inconsistency in the packaging interface. Our dependency model cannot perfectly map to Rust's, and circular dependencies are possible. To solve this, build system arguments #:cargo-inputs and #:cargo-development-inputs were introduced and used for specifying Rust libraries, instead of the regular propagated-inputs and native-inputs. Additionally, inputs propagation logic had to be reimplemented for them, which resulted in additional performance overhead.
Approaches have been proposed to improve the situation, notably the antioxidant build system developed by Maxime Devos, and the cargo2guix tool developed by Murilo and Luis Guilherme Coelho:
The antioxidant build system builds Rust packages without Cargo, instead the build process is fully managed by Guix by invoking rustc directly.
This build system would allow Guix to produce and share build artifacts for Rust libraries. It's a step towards making our work on the current approach more reasonable.
However there's a downside. Since this is not what the Rust community expects, we'd also have to heavily patch many Rust packages, which would make it even harder for us to move forward.
This tool parses Cargo.lock and outputs package definitions. It's more reliable than the crates.io importer, since dependencies are already known offline. It should be the most efficient improvement for the current approach. The upcoming importer update integrates a modified version of this tool.
Murilo also proposes to package Rust applications in self-contained modules, each module containing a Rust application with all its dependencies, in order to reduce merge conflicts. However, one same library will be defined in multiple modules, duplicating the effort to check and manage them.
Let Cargo download dependencies
This is the "vendoring" approach, used in some distributions and can be implemented as a fixed-output derivation.
We don't use this approach since the dependency information is completely hidden from us. We can't locate a library easily when we want to modify or replace it. If we made a mistake on checking dependencies, it could be very difficult to find out later.
Another downside is that downloading of a single library can't be deduplicated. Since we use an isolated build environment, commonly used libraries will be downloaded repeatedly, despite already available in the store.
After reading the recent discussion, I thought about these existing approaches in the hope of finding one that does only the minimum necessary: since users can't use our packaged libraries, there's no reason to insist on the traditional approach -> libraries can be hidden from the user interface -> user-facing documentations are not needed -> since metadata is not used at this stage, why bother defining a package for the library?
Actually cargo2guix is more suitable for importing sources rather than packages, as it has issues handling licenses, and Cargo.lock only contains enough information to construct the source representation in Guix, which has support for simple patching.
Since the vendoring approach exists, packaging all Rust libraries as sources only has been proven effective. However, we'll lose important information in our representation when switching from packages to sources: license and dependency. Thanks to the awesome cargo-license tool, only the latter required further consideration.
The implementation has been changed a few times in the review process, but the idea remains: make automation and manual intervention coexist. As a result, the importer:
- outputs definitions with full versions.
- skips existing definitions.
- maintains an identifier -> libraries mapping, along with an accessing interface that handles modifications made to the libraries.
Despite proposing it, I was a bit worried about the mapping, which references all dependency libraries directly, but the result went quite well: with compact source definitions, we reduced 153k lines of definitions for Rust libraries to 42k after this migration.
Imported libraries, these are what the importer creates:
(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 with dependencies.
(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 to a directory and then running more common tools to check them is possible (some scripts 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, and help improve the collection ;)
One issue for this model is that all libraries are stored and referenced in one module, making merge conflicts harder to resolve.
I'm considering creating a separate repository to manage this module. Whenever there's a change, it will be applied into this repository first and then synced back to Guix.
We can also store dependency specifications and lockfiles in that separate repository to make the packaging process, which may require changing the specifications, more transparent. This may also allow automation in updating dependency libraries.
Thanks for reading! Happy hacking :)
Unless otherwise stated, blog posts on this site are copyrighted by their respective authors and published under the terms of the CC-BY-SA 4.0 license and those of the GNU Free Documentation License (version 1.3 or later, with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts).