Rust package guidelines

Arch package guidelines
32-bit
–
CLR
–
CMake
–
Cross
–
DKMS
–
Eclipse
–
Electron
–
Font
–
Free Pascal
–
GNOME
–
Go
–
Haskell
–
Java
–
KDE
–
Kernel modules
–
Lisp
–
Meson
–
MinGW
–
Node.js
–
Nonfree
–
OCaml
–
Perl
–
PHP
–
Python
–
R
–
Ruby
–
Rust
-
Security
–
Shell
–
VCS
–
Web
–
Wine
This document covers standards and guidelines on writing
PKGBUILDs
for software written in
Rust
.
Package naming
When packaging
Rust
projects, the package name should almost always be the same as the name of the generated binary. Note that it does not make any sense to package library crates, so only crates with bins will be packaged. For ones that generate more than one binary, the upstream crate name is usually appropriate. In any event the package name should be entirely lowercase.
Source
Most Rust projects may be built from tarballs, source archives (e.g. source links on GitHub releases), or any other published source.
When other sources are not available, most Rust projects are published on
crates.io
which provides a stable download URL scheme for use with
cargo
. The downside of this source is that it usually does not include all the test files, license files, or other assets normally in the other sources. If needed, the
PKGBUILD#source
can use the following template:
source=("$pkgname-$pkgver.tar.gz::https://static.crates.io/crates/$pkgname/$pkgname-$pkgver.crate")
Depends
While some Rust projects have external dependencies, most just use Rust ecosystem libraries that are statically embedded in the final binary. As such most projects will not need to specify many
depends
. The usual exceptions that most Rust binaries do link against glibc libraries, so
libgcc
and
glibc
are typically dependencies of most Rust packages. There may be more if the build process looks for and links against any system libraries.
For
makedepends
, the vast majority of Rust projects are designed to be built using the
cargo
dependency manager, which both orchestrates the download of libraries to satisfy build time dependencies as well as makes all the necessary calls to
rustc
, the actual Rust compiler. Currently both
cargo
and
rustc
are provided by the
rust
package, but there are also alternative ways of getting both of these together or separately including the
rustup
package. As such, the tool most PKGBUILDs are going to call is
cargo
and you should depend directly on it.
makedepends=(cargo)
If a project requires the use of a nightly version of the Rust tool chain, use:
makedepends=(cargo-nightly)
Prepare
The rust dependency manager
cargo
is able to download all the libraries required to build a project ahead of time. Running this fetch in the
prepare()
stage  enables the later
build()
and other stages to be run entirely offline.
prepare() {
    export RUSTUP_TOOLCHAIN=stable
    cargo fetch --locked --target host-tuple
}
where:
RUSTUP_TOOLCHAIN=stable
makes sure the default tool chain is set to
stable
in the event users have changed their default. Of course this should be set to
nightly
in the event that's what the upstream project requires. This avoids a common side effect of user preferences when not building in a chroot. Also note this is
not required
if the upstream project has a
rust-toolchain
file or
rust-toolchain.toml
file in their sources that serves this purpose.
--locked
tells
cargo
to strictly adhere to the versions specified in the
Cargo.lock
file and prevent it from updating dependencies. This is important for
reproducible builds
.
--target host-tuple
tells
cargo
to only fetch dependencies needed for the specific target platform being built, thus reducing downloads (see
Fetch options
).
Note
If building a VCS package for an upstream source project that does not keep its
Cargo.lock
file in sync with
Cargo.toml
between release cycles, add
cargo update
before running
cargo fetch
. All other aspects of the build should work as documented here, although the result will not be a fully reproducible build because the dependencies will be resolved at build time.
Build
Building a Rust package.
build() {
    export RUSTUP_TOOLCHAIN=stable
    export CARGO_TARGET_DIR=target
    cargo build --frozen --release --all-features
}
where:
--release
tells
cargo
to compile a release build (the default is a debug build)
--frozen
tells
cargo
to stay offline and only use the versions specified in the
Cargo.lock
file and as cached by the fetch run in the
prepare()
stage. This is functionally equivalent to
--locked --offline
, which may also be used. This is important for
reproducible builds
.
--all-features
tells
cargo
to compile with all features of the package enabled. Alternatively, use
--features
FEATURE1
,
FEATURE2
if you want enable only selected features.
CARGO_TARGET_DIR=target
tells
cargo
to place the output in
target
relative to the current directory. This avoids a common side effect of user preferences when not building in a chroot.
Note
The two environment variables are not needed for Arch repository packages because they are
always
built in chroot environments with default settings. They are included here for the convenience of AUR users that may not realize the consequences of changing their user default settings and not using chroots when building packages.
Check
Most Rust projects provide a simple way to run the test suite.
check() {
    export RUSTUP_TOOLCHAIN=stable
    cargo test --frozen --all-features
}
You should also check if the repository is a
cargo workspace
. Just open up
/Cargo.toml
and see if it contains a
[workspace]
section. If so, you should add the
--workspace
flag to
cargo test
to ensure that all tests of the workspace members are run too.
Note
Avoid using the
--release
flag when running tests, because it enables compiler optimizations and disables some features like integer overflow checks and
debug_assert!()
macro, so in theory you
could
end up catching less problems. Since the final binary artifacts being tested are not the same as the ones we package anyway and we are not testing the build toolchain, for most Rust projects there is nothing to gain by testing in release mode.
Package
Rust builds binaries in
target/release
and can simply be installed to
/usr/bin
.
package() {
    install -Dm0755 -t "$pkgdir/usr/bin/" "target/release/$pkgname"
}
If a package has more than one executable in
/usr/bin
you can use find command:
package() {
    find target/release \
        -maxdepth 1 \
        -executable \
        -type f \
        -exec install -Dm0755 -t "$pkgdir/usr/bin/" {} +
}
Notes about using cargo install
Some packages should install more files such as a man page or other assets. In the event that such a package does not have any other way to install these, one can use
cargo install
. In this case
build()
is unnecessary because
cargo install
forces rebuilding even if the package already has been built by using
cargo build
. The
prepare()
stage can still be used to fetch sources ahead of time:
package() {
    export RUSTUP_TOOLCHAIN=stable 
    cargo install --no-track --frozen --all-features --root "$pkgdir/usr/" --path .
}
The
--no-track
argument should always be used, otherwise
cargo install
will create unwanted files such as
/usr/.crates.toml
or
/usr/.crates2.json
.
Complete PKGBUILD template
# Maintainer: Firstname Lastname <email@example.org>

pkgname=
pkgver=
pkgrel=1
pkgdesc=''
url=''
license=()
makedepends=('cargo')
depends=()
arch=('i686' 'x86_64' 'armv6h' 'armv7h')
source=()
b2sums=()

prepare() {
    export RUSTUP_TOOLCHAIN=stable
    cargo fetch --locked --target host-tuple
}

build() {
    export RUSTUP_TOOLCHAIN=stable
    export CARGO_TARGET_DIR=target
    cargo build --frozen --release --all-features
}

check() {
    export RUSTUP_TOOLCHAIN=stable
    cargo test --frozen --all-features
}

package() {
    install -Dm0755 -t "$pkgdir/usr/bin/" "target/release/$pkgname"
    # for custom license, e.g. MIT
    # install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}
Example packages
Click
Package Actions > Source Files
in the package page to see its example PKGBUILD.
cbindgen
ripgrep