Introduction
This is the all-in-one "book" of the Debian Rust Team. It's a living document, updated as packaging practices evolve.
If you are new to Rust packaging in Debian, or Rust the language, consider reading Getting started. If you are also new to Debian packaging in general, consider reading the Packaging Tutorial by Lucas Nussbaum first. You need to have a basic understanding of Debian packaging before packaging Rust things for Debian.
The team
What the Debian Rust Team does:
- Package the Rust compiler (rustc) and the cargo package manager
- Package various programs and libraries distributed on crates.io, the official Rust package registry
- Coordinate with and help other teams with packages that involve Rust1
- Maintain the Rust packaging policy
Team packages are hosted on salsa:
- rustc and cargo are at rust-team/rust (cargo used to be its own packaging at rust-team/cargo, but has since been merged into the rustc package since 1.70.0+dfsg1-1)
- normal packages are in a monorepo at rust-team/debcargo-conf, see Single crate packaging process
For example, the Debian Gnome Team has its own packaging policy for Gnome apps written in Rust; the Debian Python Team has seen packages starting to use extensions written in Rust, rather than C/C++.
Get involved
- Mailing list: debian-rust@lists.debian.org, subscribe (also list archive)
- IRC: #debian-rust:irc.oftc.net
- Matrix: #debian-rust:matrix.debian.social (bridged to the IRC channel)
Join the team
Welcome, and thank you for helping out! Please subscribe to the mailing list and say hello in the IM channels. Because the packaging processes are a bit different than in other places, it's helpful to start with a merge request, so we can help you getting used to it. When you are confident, Request Access on the salsa team page ("︙" button to the right of the team name).
This book
The source is at rust-team/book. It's built using salsa CI and
published to https://rust-team.pages.debian.net/book/. Its pipeline definition
is in the .gitlab-ci.yml
file in the repo.
Packaging policy
Current revision: 2023.2.11
NOTE: This is a work in progress to move the old policy on the wiki here and tidy it up.
The revision is the last update date of the wiki page; to be decided.
Note on automation tools
Debian Rust Team uses the debcargo
package preparation tool and
the dh-cargo
debhelper buildsystem to automate most requirements
of this policy. Normally it's not necessary to manually implement them. This
policy mainly serves as the specification, with accompanying explanation, and
highlights those parts not automatically handled.
Terminology
- source package
- A Debian source package defined by a
Source
stanza indebian/control
, to-be-packed into a.dsc
file. - binary package
- A Debian binary package defined by a
Package
stanza indebian/control
, to-be-built into a.deb
file. - crate
- Cargo's unit-of-source-distribution.
Under this policy, each Cargo crate corresponds to a single Debian source package. - library crate
- A Cargo crate containing a library-type target. Each crate can have none or one of these. If none then it's not a "library crate" for the purposes of this document.
Under this policy, each library crate is translated to one or more Debian binary packages, a "main library package" and some number of "feature library packages". - application crate
- A Cargo crate containing a binary-type target. Each crate can have any number of these. If none then it's not an "application crate" for the purposes of this document.
Under this policy, all the binary-type targets of a crate are bundled into one Debian binary package.
Package naming
Rust library crates must have one of the following package names:
- Either
Source: rust-$cratename
Package: librust-$cratename-dev
- Or
Source: rust-$cratename-$shortversion
Package: librust-$cratename-$shortversion-dev
, where$shortversion
is explained below.
If the crate name contains underscores (_
), the source and binary package
names must replace them with dashes (-
); in this case, dh-cargo requires the
package's debian/control
file to have an X-Cargo-Crate:
field in the source
section specifying the upstream name of the crate.
$shortversion
is any prefix of $crateversion
needed to support
co-installation, in the case where multiple versions of the same crate are
needed to satisfy the build dependencies of another package. For a crate with a
semantic version (SemVer) major.minor.patch
, it is recommended to
use major
(if major != 0
) or 0.minor
(if major == 0
). This supports the
Cargo and SemVer notion of "compatible versions"; see
http://doc.crates.io/specifying-dependencies.html#caret-requirements. The
latest version of a crate in Debian should use the first naming option above,
without any $shortversion
.
Rust applications may use any binary package name; they do not need to include a version in the package name or allow concurrent installation of multiple versions. A source package that builds no Rust library packages may use any source package name; a source package that builds both Rust library packages and Rust application packages must follow the naming rules for its source package name and its library package names.
This policy does not yet specify any policies for cdylib
crates, though they
will need to follow all applicable policies for shared libraries.
Crate features
A library crate must also generate binary packages for every Cargo "feature" it declares, named:
Package: librust-$cratename+$featurename-dev
, orPackage: librust-$cratename-$shortversion+$featurename-dev
depending on the naming choice made for the source and main library packages earlier.
Cargo crate names do not allow a plus sign (+
) in them, so this package name
will not conflict with the package name for any other Rust library crate or
feature package. If the feature name contains underscores (_
), the
corresponding binary package name must replace them with dashes (-
).
Each feature package must have a versioned dependency on the same version of the main library package. Each feature package must pull in all the dependencies needed to build a package that depends on that feature of the crate, including any dependencies on other features of the same crate. That is, if a feature of the crate depends on another feature of the same crate, the corresponding feature package for the first feature must have a dependency on the same version of the feature package for the second feature.
In order to avoid too many feature packages, it is allowed to "collapse" some
of them together by using Provides
. Specifically, if a feature package pulls
in the same-or-a-subset-of additional dependencies (i.e. from other source
packages) as another feature package, the latter package may Provide:
the
former package at version (= $crateversion)
in which case the former package
may be dropped as a real package. The main library package may participate in
this process as well.
Provides
field
For a crate version X.Y.Z, the main library package must declare Provides
:
librust-$cratename-dev (= $crateversion),
librust-$cratename-X-dev (= $crateversion),
librust-$cratename-X.Y-dev (= $crateversion),
librust-$cratename-X.Y.Z-dev (= $crateversion)
.
where up to one of these may be omitted if it is the same name as the binary package.
A library binary package that includes features 0 .. n-1
with crate version
X.Y.Z must declare Provides
for the following:
librust-$cratename+$feature[i]-dev (= $crateversion)
librust-$cratename-X+$feature[i]-dev (= $crateversion)
librust-$cratename-X.Y+$feature[i]-dev (= $crateversion)
librust-$cratename-X.Y.Z+$feature[i]-dev (= $crateversion)
for all i
in [0 .. n-1]
, where up to one of these may be omitted if it is
the same name as the binary package.
Package dependencies
Library packages must include Depends
corresponding to the crate and feature
dependencies specified in the crate's Cargo.toml
file. These dependencies
must satisfy the crate's [dependencies]
and [build-dependencies]
sections,
including any architecture-specific or target-specific dependencies; this
allows users and packages to cross-compile for other target platforms,
including Windows. However, the dependencies need not satisfy the crate's
[dev-dependencies]
, as those may require experimental or nightly tools for
testing or code analysis.
The version constraints on those dependencies must ensure that the version
satisfies the corresponding version predicates in Cargo.toml
, so that the
package can build successfully. However, in some situations, you may want to
loosen the version predicates in Cargo.toml and the corresponding version
constraints in debian/control. In particular, this can arise if the version
constraints across multiple such packages would otherwise require multiple
versions of a library crate simultaneously (such as two libraries with the same
non-zero major version and different minor versions), while loosening the
version constraints would allow packaging just the newer version of that
library crate. If you think you may need to change a crate's dependencies,
please contact the mailing list first. (These cases most commonly arise because
Cargo cannot currently express constraints on external non-Rust packages,
including the rustc compiler itself; upstream may depend on an older version
because they do not want to require a newer rustc compiler. Debian can use the
newer compatible version for all such packages, once Debian packages the newer
rustc compiler.)
In order for constraints to be preserved correctly, a Cargo dependency on a
given crate must be translated to a single item in the comma-separated
Depends:
AND-list in the metadata of a Debian binary package. This single
item could be a |-separated OR-list, so for example a constraint like foo (>= 6.1, << 9.5)
must be translated to librust-foo-6-dev (>= 6.1) | librust-foo-7-dev | librust-foo-8-dev | librust-foo-9-dev (<< 9.5)
, and
''not'' for example librust-foo-dev (>= 6.1), librust-foo-dev (<< 9.5)
.
debcargo as of version 2.1.0 implements an algorithm to do this. Furthermore,
in the translated Debian dependencies it is recommended to use only >=
and
<<
constraints and append a ~~
onto version names, in order to better allow
for backporting and future patches to the same crate.
Library packages may have Recommends
and Suggests
for their feature
packages.
Library packages may do a test build in dh_auto_test
and in these cases they
must have Build-Depends
on the Rust library and feature packages needed to
run this test build, with <!nocheck>
annotated as appropriate. Otherwise,
they do not need any Build-Depends
since they do not build any Rust code when
constructing their binary -dev
packages (which only contain source code; see
below). Application crates must have Build-Depends
on the Rust library and
feature packages needed to build, with appropriate version constraints
corresponding to the version predicates of that crate's Cargo dependencies.
Library package structure
A library crate's main binary package must ship the source code of the crate in
the /usr/share/cargo/registry/$cratename-$version/
directory.
Motivation: At the time of writing, Rust does not have a stable ABI. So, we can't reasonably ship compiled versions of Rust libraries. Instead, library packages ship source code, and application packages build all the library crates they use from source. We will revisit this point, if the situation changes.
The main binary package must also ship a .cargo-checksum.json
file in that
directory. This file must include a key "package"
, whose value provides the
SHA256 checksum of the corresponding upstream .crate file, as an ASCII hex
lowercase string. This file must also include a key "files", with either the
value {}
, or a value providing checksums of the source files in the package
in the format expected for a Cargo directory registry. dh-cargo expects the
source package to contain this file as debian/cargo-checksum.json
.
This format allows using /usr/share/cargo/registry
as a Cargo directory
registry with directory sources. For more details on how this mechanism works,
see http://doc.crates.io/source-replacement.html#directory-sources.
Feature packages should not contain any files, other than a symlink from
/usr/share/doc/packagename
to the main library package.
In order to support cross-compiling, each library binary package - the main
package plus the feature packages - should use Architecture: any
and
Multi-Arch: same
. (This is not ideal; it is due to a deficiency in the
current Debian cross-compiling infrastructure and the Multi-Arch
specification.) This applies even if the library crate only runs on certain
target architectures or operating systems. Note that if running a test build in
dh_auto_test
, this might need to be disabled based on the target/foreign
architecture (DEB_HOST_ARCH
) and unlikely but possibly also the host/native
architecture (DEB_BUILD_ARCH
).
A higher-level library crate or application crate may use an
architecture-specific library conditionally, using Cargo conditionals to only
require it on the architectures it supports. In such cases the Depends
or
Build-Depends
may be annotated with architecture-specific constraints. If an
application crate depends unconditionally on a non-portable library crate, that
makes the application itself non-portable, and the application can use a
correspondingly narrower Architecture
field.
Package build process
Package builds must not allow Cargo to access the network when building. In particular, they must not download or check out any sources at build time. Instead, builds must use the packaged versions of crate sources, via the corresponding library crate packages, which provide a Cargo directory registry.
Package builds must set $CARGO_HOME
to a directory within the package build
directory, to avoid writing to the building user's home directory outside the
package build directory.
Package builds should run Cargo in --verbose
mode. They may set --cap-lints warn
to avoid newer compiler warnings breaking old crates, which is mostly
relevant for test builds done as part of dh_auto_test
but also relevant when
building old applications.
Package builds should pass on the Rust equivalent of anything set in
dpkg-buildflags
. dh-cargo
should contain code for the latest version of
this; an incomplete list includes -g -O2
and translating LDFLAGS
to -C link-arg=
arguments.
Package builds should support cross-compiling by passing --target ${DEB_HOST_RUST_TYPE}
and -C linker=${DEB_HOST_GNU_TYPE}-gcc
. These
variables are available from /usr/share/{dpkg,rustc}/architecture.mk
. Again,
dh-cargo
should contain code for the latest version of this.
Maintenance
Library crates should generally have
Maintainer: Debian Rust Maintainers <pkg-rust-maintainers@alioth-lists.debian.net>
to keep them grouped together, and to simplify transitions and rebuilds. Applications may list any maintainer, but please coordinate with the Rust Team when packaging new Rust libraries or applications. Application packages may require bin-NMUs to rebuild with newer versions of Rust libraries.
Copyright
Rust packages must have a debian/copyright
file that follows DEP 5,
"Machine-readable debian/copyright".
If the license places the restriction such that derivative works in binary form
can only be distributed together with the source code, and the license is not
one of the reserved short names in the format document just mentioned, then the
copyright file must contain a X-Binary-Requires-Source: yes
line in the
header stanza. This allows dh-cargo to correctly generate
Built-Using fields for packages that are built from this
package.
Upstreaming changes
Packaging a Rust library or application may in some cases require changes to its source code, to integrate better with the Debian ecosystem, comply with Debian policies, or otherwise produce better results with packaging. Whenever possible, prefer to integrate such changes upstream, rather than as patches in Debian (upstream-first approach). Whenever possible, seek to improve the debcargo logic or the upstream crate metadata used by that, rather than patching the resulted generated sources (automation-first approach). This makes the Rust and crates.io ecosystem easier to maintain both upstream and in Debian, and avoids technical debt.
dh-cargo
Rust library and application packages should use dh-cargo, to simplify implementation of the Rust packaging policy, including any future transitions or new policy requirements.
Architectures and Multi-Arch
Rust library packages must use Architecture: any
in debian/control
. The
detailed reasoning is explained below. It should never be changed unless dpkg
changes fundamentally. dpkg maintainers also confirmed this to be best Debian
policy.
The concrete problem is related to cross-compilation. If a rust package were arch:any that build-depended on another arch:all rust lib that in turn (runtime-)depended on an arch:any package, cross-compilation will fail as dpkg will be unable to resolve the correct architecture for the last package.
Why Architecture: any
?
Copied from debcargo comment:
This is the best but not ideal option for us.
Currently Debian Multi-Arch spec has a deficiency where a package X that build-depends on a M-A:foreign + arch:all package that itself depends on an arch:any package Z, will pick up the BUILD_ARCH of package Z instead of the HOST_ARCH. This is because we currently have no way of telling dpkg to use HOST_ARCH when checking that the dependencies of Y are satisfied, which is done at install time without any knowledge that we're about to do a cross-compile. It is also problematic to tell dpkg to "accept any arch" because of the presence of non-M-A:same packages in the archive, that are not co-installable - different arches of Z might be depended-upon by two conflicting chains. (dpkg has so far chosen not to add an exception for the case where package Z is M-A:same co-installable).
The recommended work-around for now from the dpkg developers is to make our packages arch:any M-A:same even though this results in duplicate packages in the Debian archive. For very large crates we will eventually want to make debcargo generate -data packages that are arch:all and have the arch:any -dev packages depend on it.
Getting started
Welcome to Rust, and packaging all things Rust in Debian! Like for other languages, it's recommended to first get an idea of what Rust is if you haven't.
Rust is famous for its comprehensive and well written documentation; check out the official learning materials, including The Rust Programming Language. There's also a crash course on the language grammar at Learn X in Y minutes, though it doesn't cover many aspects integral to the Rust ecosystem.
Rust has an extremely streamlined and easy to use build system called
cargo. Most of the time, just run cargo build
to build a program (in
development mode; to build in release mode, add --release
). There is also
cargo check
, to check if the code is correct; and cargo run
, to run a built
program (after building it if not already; likewise, add --release
for
release build). Dependencies are managed consistently. There's no need to fight
with the build system like CMake.
Like other languages that are normally compiled, Rust crates are thus split
into two categories: binary (programs only, as of now) and library. Binary
crates are more or less simple: point cargo to Debian's crate registry (using
source replacement), run cargo build --release
,
install the resulting executable, and you are done. Libraries, on the other
hand, are more involved.
What is a crate? TRPL covers it in detail, but in the context of Debian Rust packaging, we often use package to refer to a Debian package, and crate to refer to a Rust package.
Things to know
Before diving into packaging the new shiny tool, however, some things need to be known:
Static linking
In contrast to the C/C++ world, which is the source of a norm in Linux/*nix that is dynamic linking, Rust, like Go, is statically linked. Advantages and disadvantages aside (both linking strategies have both), this means Rust libraries are not packaged like C/C++ libraries, which carry foo.so objects; but rather, they are simply source files, installed to a specific location. (Rust could build as dynamic libraries, "dylibs" by its jargon, but the support is not as mature as static linking, and has yet to see an example in Debian.)
Features, SemVer, and encoding in virtual packages
Below crates, they have another level of optional compilation and dependency,
called features. With presence and absence of the #[cfg(feature = "feather")]
macro, certain parts of the source code could be included or
excluded in compilation. They can also enable or disable optional dependencies.
To make features work in Debian, they currently are (mostly) encoded as virtual
packages (as per policy): librust-foo-dev Provides
librust-foo+feather-dev, encoding its "feather" feature. A package who
Depend
s on it semantically "depends" on that feature. Sometimes, though
rarely, there is a feature dependency loop, then we have to split feature
packages into real ones (i.e. Package: librust-foo+feather-dev
).
Another thing encoded in virtual packages is semantic versioning:
librust-foo-1.2.3-dev Provides
version 1.2.3 of the "foo" crate. This is
due to the semantic differences between (cargo's flavor of) SemVer and Debian
version specifiers. A package who Depends
on librust-foo-1.2-dev semantically
"depends" on at least version 1.2 of the "foo" crate.
Combined, librust-foo+feather-1.2.3-dev
Provides the "feather" feature of
version 1.2.3 of the "foo" crate.
This sure leads to exponential bloat of the Provides
field. Do you have a
better idea? Don't hesitate to talk to us ;)
Patching, a lot of patching
Due to various reasons, we can't always just use the original sources, and need to patch the them. The most common ones are 1. dependency version mismatch, 2. removing things Debian don't have or need, 3. adapting to Debian specific things, 4. devendoring and excluding non-packageable content.
This has a minor consequence: cargo projects have a Cargo.lock file, "locking" the versions and checksums of dependencies, a security measure. However, we often don't have the pristine sources in Debian. Thus, we remove the lock file when building, instead relying on Debian package checksums.
Packaging processes
The current packaging process in the Rust team is focused on crates published on crates.io. See their packaging process.
For projects not published on crates.io, and/or projects organized as a workspace with a lot of internal crates not for outside use, consult the workspace process.
Help, something went wrong!
Sometimes, the error messages are not the most informative. In this case you
can try re-running the command with RUST_BACKTRACE=1
. If you are using the
debcargo
from Debian's own repositories, you should also install the
debcargo-dbgsym
package, otherwise the stack trace will be next to useless.
Make sure you have the debug repository enabled in your APT
sources.
Packaging a single crate for Debian
NOTE: This is a work in progress to move the README.rst file in the debcargo-conf repository here, and tidy it up.
debcargo and the debcargo-conf registry are the core of this process.
It's recommended to work in Debian unstable, unless you have specific backport needs.
To get set up:
apt update && apt install debcargo git
git clone git@salsa.debian.org:rust-team/debcargo-conf.git
cd debcargo-conf/
Prepare new (version of) crate
./update.sh <rust-crate-name>
and follow its instructions.
Package a co-installable older version
To maintain an old version of a crate alongside the latest one, first make sure the latest version is packaged by doing all of the above, then run:
./update.sh <rust-crate-name> <old-version>
and follow its instructions. To save time, you can first copy anything relevant
from src/<rust-crate-name>
to src/<rust-crate-name>-<old-version>
, then
adapt it as needed.
You will need excellent reasons to do this. It should be done only for core crates used by many other crates or programs for which the upgrade path is complex. For example, if the API significantly changed and requires a lot of work.
Instead, please consider:
- Downgrading or upgrading the dependency
- If it doesn't exist, open an issue on the upstream issue tracker to encourage them to upgrade
- If possible/relevant, disable a feature if it uses it
- Wait until upstream upgraded
Prepare a release
./release.sh <rust-crate-name> # or
./release.sh <rust-crate-name> <old-version> # as appropriate
DISTRO=experimental ./release.sh <rust-crate-name> # to target another distro
EXTRA_DEBS=build/*.deb ./release.sh <rust-crate-name> # use local dependencies
This prepares the necessary Debian files in build/
, and creates a git branch
to manage the packaging until it is accepted in Debian itself. You need to run
additional commands after this - more specific instructions are given to you
about this by the script after you run it.
Hold packages at old versions
If you need to keep the latest version in Debian at an older version than is released on crates.io, e.g. to upload an important bugfix without being blocked on having to package all the dependencies of the newest version, you can:
REALVER=<old-version> ./update.sh <rust-crate-name> # then
REALVER=<old-version> ./release.sh <rust-crate-name>
Repackage the existing revision
To build the version of a package already in debcargo-conf/src
:
$ ./repackage.sh A
$ (cd build && ./build.sh A)
If this package is already in the archive and you want to recreate that
exactly, you will need to use the exact same version of debcargo that was used
previously. This version is mentioned in debian/changelog
.
Set up build environment
To set up a suitable build environment for ./build.sh
:
With schroot:
$ sudo apt-get install devscripts reprepro debootstrap sbuild dh-cargo schroot autopkgtest quilt
$ sudo sbuild-createchroot --include=eatmydata,ccache,gnupg,dh-cargo,cargo,lintian,perl-openssl-defaults \
--chroot-prefix debcargo-unstable unstable \
/srv/chroot/debcargo-unstable-amd64-sbuild http://deb.debian.org/debian
With sbuild + unshare:
$ sudo apt install devscripts mmdebstrap sbuild uidmap dh-cargo autopkgtest quilt
$ mkdir -p ~/.cache/sbuild
$ mmdebstrap --variant=buildd --include ccache,gnupg,dh-cargo,cargo,lintian,perl-openssl-defaults unstable ~/.cache/sbuild/debcargo-unstable-amd64.tar.xz
Note that you need to set CHROOT_MODE=unshare
to use ./build.sh with unshare.
An explanation of this, plus more recipes, can be found on the wiki page of sbuild.
If you need to pass additional options to sbuild, like --arch=i386
, then set
the SBUILD_OPTS
environment variable.
Normally, ./build.sh
will fail early if not all the build dependencies are
available in your local apt cache. If you are packaging a large dependency tree
however, to avoid many round-trips through NEW it is possible to bypass this
check and build all the packages together. Suppose package B depends on package
A, then you can run something like:
$ ./release.sh A
$ ( cd build && ./build.sh A )
# push pending and checkout master
$ ./release.sh B
$ ( cd build && ./build.sh B librust-A*.deb )
Or you can use the EXTRA_DEBS
environment variable, which build.sh
will
inherit, like:
$ EXTRA_DEBS=build/*.deb ./release.sh A
The extra arguments after ./build.sh B <args>
are extra deb files to pass to
sbuild to use as dependencies. In this case, librust-A*.deb
should have been
built by the previous step. Alternatively, use the environment variable
EXTRA_DEBS
, like so:
$ EXTRA_DEBS=librust-A*.deb ./build.sh B
$ EXTRA_DEBS=librust-A.deb,librust-B.deb ./build.sh C
After everything is built successfully, you can dput
all of them and then
push all the pending-*
branches as normal.
Repository structure
pending-*
branches are managed by ./release.sh
, so please don't manage them
yourself as you will interfere with the working of that script. The intention
is that they should only differ from the master branch by 1 commit, i.e. the
dch -r
commit created by ./release.sh
.
If you want to create separate non-master branches, that is fine - just don't
call them pending-*
and don't run ./release.sh
on those branches. If you
want to test your crate, instead run:
cd build && [SOURCEONLY=1] ./build.sh <rust-crate-name> [<old-version>]
omitting the stuff in [...]
as needed.
Like many other Debian git repositories, Rust team don't follow "feature branch" practices here. The team generally don't package just 1 or 2 rust crates at a time, but all of its dependencies and sometimes some reverse-dependencies too. So normally a few dozen packages are updated at once. In this context, it's good to merge often, to avoid conflicts with someone else that might also need to touch those too in the next few days.
To match a release (i.e. a .deb
or a .dsc
file) to a commit, find the
commit message that actually says "package: release x.y.z-d". It's commonly
followed by a merge commit.
Expert mode & packaging multiple packages
You should get used to the single-packaging workflow a bit first, including doing a few test builds of your package. Otherwise the instructions below may seem a bit opaque.
rm -rf build/* && sbuild-update -udr debcargo-unstable-amd64-sbuild
- clears out your build directory, making the subsequent steps a bit faster../update.sh <CRATE>
for all your relevant packages.- Do any manual updates.
cd build
then./build.sh <CRATE> *.deb
for all your relevant packages, in dependency order.- Deal with any issues that come up.
- Push your updates to debcargo-conf.
- Run
dev/list-rdeps <CRATE> [<CRATE> ...]
on all the crates you updated. Any reverse-dependencies that are affected also need to be updated, and you should repeat steps 1-7 (including this step) for them as well until this step lists no new packages that are affected. ./release.sh <CRATE>
for all the packages you updated, running the build again if necessary. It may be possible to do this out of dependency order, assuming you didn't have to make significant changes in step (5). If you did, then this step also has to be done in dependency order.- Push your
pending-*
branches to debcargo-conf.
There are also various scripts in dev/*
that might help you. They should have
a couple lines at the top of the source code describing their functionality and
some brief usage instructions.
Whew, thanks for all your work!
Update dependencies
In some cases, libraries/programs are forcing an old version of a library as dependencies. In order to limit the number of duplicated libraries in the archive, please try to evaluate if a newer version of the dependencies could be used.
To achieve that, after ./update.sh
, try:
$ cd build/<package>/
$ rm -rf .pc # sometimes this is necessary due to minor debcargo bug
$ quilt push -a
$ quilt new relax-dep.diff
$ quilt edit Cargo.toml
$ quilt header -e --dep3
$ quilt refresh
$ cargo build # check that it works. if it does, then
$ cp -R patches ../../src/<package>/debian
Suppose you want to change the dependency from 0.3 to 0.5. If the crate builds
with no further source changes, then change the required version in
Cargo.toml
from 0.3
to >= 0.3, < 0.6
or something like that. Then the
convention is to put all these changes into a single patch called
relax-dep-versions.patch
.
OTOH, if the cargo build fails, and you can fix it up by editing the source
code in a minor way to use the new crate API, then: for each crate that needs
to be updated, you should instead name the patch update-dep-<crate>.patch
and
add both the Cargo.toml
and the source code changes to it. The change to
Cargo.toml
would then simply say (e.g.) 0.5
since the older versions
actually don't work, and not the version range from the previous paragraph.
If you want to make a crate work with an older dependency version than listed
in Cargo.toml
(for example 0.3 instead of 0.5), you cannot use a flexible
version requirement like >= 0.3, < 0.6
. Instead you have to specify only the
older version, in this example 0.3
.
An explanation was given by Ximin Luo (infinity0) in this MR:
It's a quirky annoyance of sbuild/apt (I can't remember which) that for build-depends only the first in a series of alternatives is considered, and yes in these sorts of situations we are in fact forced to downgrade the dependency version.
The authors refused to fix it the last time I spoke to them about this, giving the incorrect reasoning "it would mess with build reproducibility". The reasoning is incorrect because by the same argument one should not allow different versions of the same compiler, and when doing reproducibility (which I worked, paid, on several years ago) one is supposed to record the actually-installed versions of the build-depends and use these exact same ones when attempting a 2nd reproduction, otherwise one cannot expect reproducibility anyways.
Collapsing features in debcargo.toml
TL;DR: Set collapse_features = true
in debcargo.toml, unless something
breaks. This eliminates empty feature packages and speeds up the NEW trip.
Rust and Debian have two different levels of abstraction when handling dependencies and the relationship between them. In Rust the lowest level is a feature, while in Debian it's the binary package.
This means that the following dependency chain is not a problem in rust:
- crate A with feature AX depends on crate B with feature BY
- crate B with feature BX depends on crate A with feature AY
This is a perfectly valid situation in the Rust ecosystem. Notice that there is
no dependency cycle on the per-feature level, as this is enforced by cargo; but
if collapse_features = true
then package A+AX+AY would cyclicly depend on
package B+BX+BY in Debian.
This is reflected in the Debian packages by producing Provides
lines for all
combinations of features, and this can become a quite large section.
Setting collapse_features = true
in debcargo.toml removes this behaviour and
is generally recommended, unless when it leads to dependency cycles of Debian
packages. If that happens, those must be broken up by having some or all of the
packages set this feature to false.
Changed orig tarballs
Sometimes the orig.tar generated by debcargo might change e.g. if you are using
a newer version of debcargo and one of the dependencies relating to generating
the tarball was updated and its behaviour changed - compression settings,
tarball archive ordering, etc. This will cause your upload to get REJECTED by
the Debian FTP archive for having a different orig.tar. In this case, set
REUSE_EXISTING_ORIG_TARBALL=1
when running ./release.sh
.
Testing
Debian has two types of tests:
- pre-install tests run in
debian/rules
- post-install tests defined in
debian/tests/control
, run by/with autopkgtest
For Debian Rust packages, in (1) the crate's test suite is run with default
features but only if there are no dev-dependencies, and in (2) the whole test
suite is run with each feature enabled separately plus --no-default-features
and --all-features
.
Sometimes, tests require extra tweaks and settings to work. In this case, you
can tweak debian/rules
for (1), and for (2) you will simply have to mark the
relevant tests as broken using test_is_broken = true
. See the existing crate
configs for examples.
Other times, the tests are simply broken or can't be run in Debian. In this
case you should disable the test in (1) by running dh_auto_test -- build
instead of the default dh_auto_test -- test --all
, and for (2) again you
should mark the relevant tests as broken. These tests are going to be marked as
flaky in autopkgtest, still executed but won't fail the autopkgtest run.
Please note that
[packages.lib]
test_is_broken = true
will transitively disable tests for all combinations of features. Sometimes
this is correct e.g. if the test actually breaks for all features. Sometimes
this is not correct, e.g. if the test only breaks for
--no-default-features
. In the latter case you should instead patch the crate
to ignore those tests when the relevant features are absent.
Packaging "hacks"
Scope of this document
This document aims to document some nice "hacks" and tricks to employ when packaging crates.
Getting an overview of missing crates
A nice tool to generate a graphical overview of a rust projects' dependency
tree is cargo debstatus
. Install it like that: apt install cargo-debstatus
(trixie and sid). Then download either a release or clone the git project and
cd
into there. Run cargo debstatus
to get a nice graph about dependencies
and reverse dependencies.
Please note that cargo debstatus
may be unable to find
some of the crates which are already packaged in Debian, so before packaging
new crates double-check they are not in the archive using
apt search $CRATE_NAME
or apt list '*$CRATE_NAME*'
(replace underscores
with dashes in $CRATE_NAME
).
Patching crates
If a crate needs a) a newer dependency or b) an older dependency than the one in the archive you need to patch the crate. This is relatively common. You can also use this to patch out features in Cargo.toml or make changes to the source code.
Since the source is pulled from crates.io and not from github/lab, patching requires downloading the source from there. There are two ways to achieve this:
- Use
cargo-download
:cargo install cargo-download
- Download the source:
cargo download foocrate version > foocrate.tar.gz
- Extract the crate:
tar -xf foocrate.tar.gz
cd foocrate-version && git init && git add . && git commit -m "d"
- Edit e.g. Cargo.toml to relax the dependencies: Instead of version 0.4 of a crate bump it to 0.5 ( if that is the corresponding debian version) or down to 0.3 (if that is the debian version)
- Generate a patch with your changes:
git diff -p >> relax-deps.diff
cp relax-deps.diff debcargo-conf/src/foocrate/debian
cd debcargo-conf/src/foocrate/debian && mkdir patches
mv relax-deps.diff patches/ && cd patches && echo relax-deps.diff >> series
cd ../../../../
- run
./update.sh foocrate
again. The patch should get applied and allow you to build against an older/newer dependency / with features disabled. - Document your patches in d/changelog
wget http://crates.io/api/v1/crates/foocrate/version/download -O foocrate-version.tar.gz
then follow 1.1
Alternatively you can do it directly in the build directory with quilt:
- In the root directory of
debcargo-conf
,mkdir -p src/foo/debian/patches && ./update.sh foo && ln -srf src/foo/debian/patches build/foo/debian/
(linking it there so it's "sync"ed) cd build/foo
quilt series
to check existing patches,quilt push -a
to test apply all of them, andquilt pop -a
to unapply them,quilt push patch-name
to apply the series until exactly that patchquilt new patch-name.patch
to create a new patch,quilt edit path/to/file
to edit a file with changes saved in current patch,quilt header -e --dep3
to add a DEP-3 patch headerquilt refresh
to update current patch and prevent fuzz (not allowed when building)
You may want to also rm -r build/foo/.pc
if quilt complains about something
you don't recognize.
Note that update.sh
deletes and re-creates build/foo
, so an open terminal
in it needs to go up and down once, into the new directory.
capitol did a nice writeup which can be read here: https://blog.hackeriet.no/packaging-rust-part-II/
built-using-dh-cargo
If you get an error like this:
You must patch build.rs of CRATE to output 'println!(\"dh-cargo:deb-built-using=$lib=\$s={}\", env::var(\"CARGO_MANIFEST_DIR\").unwrap());'
where: $s is 1 if the license(s) of the included static libs require source distribution alongside binaries, otherwise 0"
when building a FFI rust library you need to patch build.rs like stated above. $s is 0 for BSD-like licenses such as MIT and 1 for copyleft licenses like GPL.
Special d/rules overrides
In case you need special overrides for d/rules not provided by debcargo:
touch src/foocrate/debian/rules && ./update.sh foocrate
. This will generate arules.debcargo.hint
alongside it, which you can use as template, similar to the case of d/copyrightcd src/foocrate/debian && cp rules.debcargo.hint rules
- use your favorite editor to edit the rules file to your needs.
# runs all tests on a single thread
override_dh_auto_test:
dh_auto_test -- test -- --test-threads 1
Packaging binary crates
File an ITP for them as this is team policy. Add the following content (as minimum) in debcargo.toml:
summary = "short program summary"
description = """
long, exhaustive description of the program. this equals the extended-description line in debian/control
for "regular" packages.
"""
# the section for the application
[source]
section = rust
Examples to take inspiration from include lsd, bat, alacritty, ...
If a package ships a binary but you only want to use it as library add this stanza in debcargo.toml:
[packages.lib]
bin = false
Packaging a lot of crates
Use the dev/chain_build.py
script. It has a self-explanatory help message,
but basically you run
dev/chain_build.py lowest_dep_1 lowest_dep2 lower_dep_1 lower_dep_2 target_crate
And solve build failures, press enter to continue, solve, continue, repeat, until the target crate is successfully built.
debcargo.toml tweaks
Excluding files
In case you need to exclude certain files from debcargo.toml
, there is an
easy way to do that: just add
#![allow(unused)] fn main() { excludes = ["foo/bar.rs", "bar/non-dfsg-file.c"] }
in debcargo.toml. This has the following usecases:
- Exclude non-dfsg/unnecessary files form the orig tarball (Example: svg-metadata) This is also required for some sys-crates to exclude vendored copies of the C library already in debian
- Exclude broken tests that do not run (when in doubt, ask on #debian-rust) (Example: cxx )
Passing external packages to the buildsystem
If the crate needs external packages (such as -dev libraries) you can also pass
those conveniently via debcargo.toml
: depends = ["libfoo-dev"]
. This is
essentially needed for all -sys crates which provide rust bindings to C
developement libraries such as GTK, for instance.
Passing runtime test dependencies to autopkgtest
In rare cases the autopkgtest can fail on the official runners compared to the local one because it has a slightly different setup. You can do it like this:
test_depends = ["foo"]
That happens very rarely, see cxx
for an example.
Whitelisting files
Sometimes debcargo marks files as suspicious, most of the time those are tests written in C for -sys crates. Whitelist them like this:
whitelist = ["tests/foo.c"]
Example: libadwaita-sys
Pre-release dependencies
Some crates depend on a crate with an alpha/beta version strings. debcargo will
emit an error if that is the case. To allow those deps, pass the following:
allow_prerelease_deps = true
. Do this only if you are sure this will work !
Collapsing features
If a crate has features, collapse_features = true
should be set in
debcargo.toml
. This is strongly recommended. See issue #17 in the debcargo
repo for the reasoning.
Marking feature tests as broken
Let's assume you are building a package and get the following output at the end:
autopkgtest [22:45:42]: test librust-foo-dev:: -----------------------]
autopkgtest [22:45:42]: test librust-foo-dev:: - - - - - - - - - - results - - - - - - - - - -
librust-foo-dev: PASS
autopkgtest [22:45:42]: @@@@@@@@@@@@@@@@@@@@ summary
rust-foo:@ FAIL non-zero exit status 101
librust-foo-dev:default PASS
librust-foo-dev: PASS
librust-foo-dev:serde: FAIL non-zero exit status 101
Tests of serde
and @
features failed. Since the other features (and the
package itself) passes, those need to be marked as flaky so the package can
migrate to testing.
Do it like this:
#serde and @ feature fail the autopkgtest
[packages.lib]
test_is_broken = false
[packages."lib+@"]
test_is_broken = true
[packages."lib+serde"]
test_is_broken = true
Examples: cxx, hashbrown, uom, ...
Do this only if some features fail. If all feature tests fail, read the test logs and look at the upstream test system. Make sure that some tests pass or that the tests aren't meant to be run. Some upstream projects run all test in a specific container or use specific setup. Also some crates are only tested with their default features enabled by upstream. If that's the case mark all tests as broken:
# tests need a postgres container to run
[packages.lib]
test_is_broken = true
Examples: tokio-postgres, ...
Sometime you need to combine some tricks to run at least some tests: Exclude a faulty test, patch dependencies away and mark tests as broken.
More resources
For a full documentation of all keywords available in debcargo.toml
refer to
debcargo.toml.example
in the the debcargo repo.
Packaging workspaces and crates not published on crates.io
In an ideal world, all crates are published on crates.io, so then can be packaged one by one. However, the reality is different.
Some projects pack tens of internal crates in a workspace. Some still publish on crates.io, but it's quite a hassle to packaging them one by one. Some don't, often seen in projects that build programs - these internal crates are not used outside.
Some projects do not publish on crates.io. For programs, this doesn't matter. For libraries, however, their name scheme has yet to be decided, since the librust-*-dev namespace is reserved for libraries from crates.io. Luckily, there is currently no need to.
General setup
This kind of packaging falls back to "traditional" ways in Debian. This assumes you are already familiar with general Debian packaging. Experience with the single crate process isn't strictly needed but is helpful.
-
Import upstream source by either cloning the upstream repository,
gbp import-orig
, or ways you prefer. -
Prepare the common things in debian/ as usual.
-
Check packaging status of dependencies. There is currently no ready-made tool for this, but you can gather all
[dependencies]
and[dev-dependencies]
in Cargo.toml of these internal crates into an empty cargo project, and runcargo-debstatus
there. -
Add them as
Build-Depends
along withrustc
andcargo
in debian/control. -
Prepare a .cargo/config.toml replacing
source.crates-io
to the Debian registry:[source.crates-io] replace-with = 'debian' [source.debian] directory = '/usr/share/cargo/registry'
and put it in place before build (in debian/rules):
execute_before_dh_auto_configure: mkdir -p .cargo cp debian/cargo-config.toml .cargo/config.toml
You may want to add .cargo/config.toml as a clean target.
-
To follow Multi-Arch, use the variables provided by rustc:
include /usr/share/rustc/architecture.mk CARGO_BASE_ARGS= --release --target $(DEB_HOST_RUST_TYPE) override_dh_auto_build: cargo build $(CARGO_BASE_ARGS) override_dh_auto_test: cargo test $(CARGO_BASE_ARGS)
-
Patch the source as needed.
No need to publish internal crates
This is the easy part. After adding needed dependencies as B-D, patching them
to use versions in Debian, patching out unavailable dependencies and feature
depending on them, just run cargo build
in debian/rules and install the
resulting binary(-ies).
Publish internal crates
First, add a Package
stanza for each of them in debian/control, which
Provides
their versioned virtual packages, and install to their respective
location at /usr/share/cargo/registry/$crate-$version
.
Then, consider these situations:
- Crates in workspace are released in lockstep, sharing the same version for each release.
- They only reside in a workspace, but are released in their own pace.
There's not much to say about (1). For (2), though, there is a problem: the
source package, or the workspace, has a version. Each binary package, or crate,
also has their own version. The only known way to achieve this is to
dpkg-gencontrol
/dh_gencontrol
for each binary package; see
rust-curve25519-dalek for an example.
Then it's the tiresome work of keeping B-Ds, Package
s, and Depends
in sync.
There hasn't been strong urge to write a tool for this.
General packaging tips
Disabling things in Cargo.toml
Dependencies, tests, examples, or benchmarks often need to be disabled in Cargo.toml. They used to be commented out or removed entirely:
-[[bench]]
-name = "file"
-path = "benches/file.rs"
+#[[bench]]
+#name = "file"
+#path = "benches/file.rs"
-[[bench]]
-name = "file"
-path = "benches/file.rs"
People have different ideas on these two approaches: some think removal is acceptable since patches are shipped along, some think comments are more obvious to users.
They are both cumbersome and complicates future updates, though. For blocks, here is a simpler way:
-[[bench]]
+[[disabled.bench]]
Individual lines still need to be removed or commented out, unfortunately.
package.metadata
is safe to ignore
As the name suggests, this table is metadata, more specifically, "Extra settings for external tools."
Disabling things in Rust code
As for Cargo.toml, commenting things out with /* */
and //
used to be a
common approach. There is also a simpler way:
#[test]
+#[cfg(any())]
fn test() {
The cfg()
macro enables the item only when the conditions in it are met;
any()
is true when, well, any thing in it is met, but there is none! So
it's never met, essentially disabling the code.
But note, this completely erases that thing; depending on the situation, this might not be wanted.
For tests, it could be changed to #[ignore]
: the test is still there, but
ignored; ignored tests can be manualy run with cargo test -- --ignored
.
#[test]
+#[ignore = "the ignore macro can also show a comment about the situation"]
fn test() {
For non-test code, cfg(any())
could be changed to something that normally is
not met, for example cfg(only-enabled-through-a-flag)
, which is enabled
through a --cfg only-enabled-through-a-flag
argument to rustc.
Tests and examples
Usually tests and examples are run, to make sure they work in Debian. Examples are a good complement to tests.
However, when they are flaky, lack test data (some upstreams exclude them when publishing to crates.io), or lack resources (privileged or otherwise unavailable on Debian infrastructure, the most common being network access), they must be disabled.
If not explicitly listed as [[test]]
and [[example]]
blocks, they can be
lumpsum disabled with this under the [package]
section (if not already set by
upstream):
+autotests = false
+autoexamples = false
Some tests take too long to run on certain architectures. They could be patched to not run there, avoiding timeout failures:
#[test]
+#[cfg(not(target_arch = "riscv64"))] // too slow
fn test() {
Skipping doctests
In case doctests get run during the build process and you want to disable them add the following lines in Cargo.toml (as patch):
+[lib]
+doctest = false
They often fail so you can safely disable them that way. Example: xdg-home, rust-apt
Benchmarks
Benchmarks help upstream keep track of the performance of their work. There is
generally no need to run them in Debian. Try to disable them in Cargo.toml, by
setting autobenches = false
in the [package]
section if not already set,
and disabling [[bench]]
entries.
OS-specific crates
See redox-syscall for examples on how to deal with these.
If this is unclear, ask on IRC.
Architecture-specific crates
This is a bit harder. Usually there are two options:
- The crate should build a dummy/no-op version of itself "out-of-the-box" on the architectures it doesn't work on.
- Dependent crates should depend on it with a platform-specific dependency.
(1) involves less burden for others, both for dependent crates and for us packagers, since overriding d/rules isn't needed to ignore test failures on non-working architectures. You should communicate to upstream that this is the preferred approach.
In the case of (2), the crate should document exactly what conditional should be used, and keep this documentation up-to-date. This allows us to easily determine if dependent crates are using the correct conditional. You will then have to override d/rules for this crate, see src/simd for an example.
You should file a bug upstream if the crate does neither (1) nor document the conditions for (2), e.g. https://github.com/hsivonen/simd/issues/25
(Actually the above applies even for "OS-specific crates" but then (2) is obvious so documentation is less necessary, and dependent crates all do it correctly already.)
Arch-specific failures
It can rarely happen that tests (read: autopkgtest) fail on specific arches because how bytes are addressed on that arch. The best course of action is to investigate first if it indeed is an arch-specific failure. If that's the case you need to write a patch that skips those faulty tests (on that arch) so the package can enter testing. Because arch names are different in rust, here is a handy table comparing them:
Debian arch name | rust arch name (target_arch) |
---|---|
Arches autopkgtest runs on (needed for testing migration) | |
amd64 | x86-64 |
i386 | x86 |
arm64 | aarch64 |
armel | arm |
armhf | arm |
ppc64el | powerpc64 |
s390x | powerpc64? s390x? |
riscv64 | riscv64 |
Other official arches 1 | |
mips64el | mips64 |
Unoffical ports with rustc/cargo (not really relevant) | |
powerpc | powerpc? |
loongarch64 | loongarch64? |
ppc64 | powerpc? |
sparc64 | sparc64? |
x32 | ? |
Arches without rustc/cargo:
- sh4 2
- alpha
- arc
- hppa
- hurd-i386
- ia64
- m68k
Only the first seven are really relevant, the rest are included for completeness's sake.
If you encounter a test failure e.g. on armel
, add this macro before the
#[test]
macro:
#![allow(unused)] fn main() { #[cfg(not(target_arch = "arm"))] }
Then generate a patch with the changes and include it in the usual way. Also notify upstream that this arch is broken and send them your patch.
To skip a test on e.g. all big-endian arches you can use something like
#[cfg(not(target_endian = "big"))]
.
sh4 has only one test failure for cargo
ITPs
File ITPs (Intent to Package) for user-facing things, e.g. programs, as usual.
For libraries, the Rust team used to consider it a hassle and spam to the bug tracker, while some disagreed. The team now starts to file ITPs for libraries with a big stack of crates too, in order to ease coordination and tracking of efforts.
Binary crate ([[bin]]
) has required-features
TODO: see if it actually requires manual handling.
Binary (crate) name conflicts with other package
There are two ways to handle this:
- Patch the
name
of the[[bin]]
in Cargo.toml to something else, commonly "thing-rs". This changes both the binary name and the binary package name. - Install it to another location with the same name, and/or install it as
another name, then let the user choose to alias/link it. The fd-find package
does both:
/usr/lib/cargo/bin/fd
and/usr/bin/fdfind
.
Patch headers
DEP 3 formalized a set of patch headers. Use them accordingly to
explain why a patch exists. quilt header -e --dep3
opens $EDITOR
with a
template.
Dependencies on clippy
Clippy is a linting tool, run by developers. Crates normally don't need to
depend on it to function. If grepping its name shows only cfg_attr
macros, it
can safely be removed. If there are use
statements, and the code heavily
relies on it, then leave it be.
Some ramblings
In #debian-rust
came these two blog posts along with the remark of good read
- https://blog.hackeriet.no/packaging-a-rust-project-for-debian/
- https://blog.hackeriet.no/packaging-rust-part-II/
Now are they, those two blog posts, parked here. Waiting for better integration.
Developing Rust code using Debian-packaged crates
While perhaps not the stated intention, the Rust ecosystem in Debian is
actually quite usable for developing Rust code in general. Thanks to source
replacement, Cargo can be configured to use only local,
Debian-provided packages by placing something like the following in
~/.cargo/config.toml
(for user-wide effect) or in a given project's
.cargo/config.toml
:
[net]
offline = true
[source]
[source.crates-io]
replace-with = "debian"
[source.debian]
directory = "/usr/share/cargo/registry"
In this state, Cargo will only look for crates installed as Debian packages on the local system.
Tools
cargo-debstatus
Run in crate directory (alongside Cargo.toml) to check packaging status of dependencies.
See its section in Hacks.
bacon
Runs cargo check/clippy/test/run/etc. in the background, as you code. Customizable. Nice for patching, but beware, unless you install all the dependencies and replace source to them, it pulls from crates.io, which might be different with what's in Debian.
Not yet packaged: install with cargo install bacon
.
quilt
Patch management tool. Debian source format 3.0 is named after it (3.0 (quilt)
in debian/source/format).