Rust Packaging Guidelines

The workflow for Rust packaging on Fedora will be different starting with Fedora 34. Older releases (up until Fedora 33) do not ship source-only packages for Rust crates, but only application packages created via a special build process. In general, the Rust Packaging Guidelines apply to all current Fedora releases, only the build process differs depending on the targeted Fedora release.

rust2rpm

Note that the rust2rpm tool (packaged as python3-rust2rpm for Fedora) automates most of the steps that are necessary for creating .spec files for Rust crates.

It is advisable to try rust2rpm $crate first, and to only modify its output for your needs, before attempting to write a .spec file by hand.

Most of the time, rust2rpm output can be used almost without modifications, except for adding LICENSE files with %license to the appropriate %files listings, or sometimes trimming the Summary if the upstream description text is too long.

Package Naming

Packages for Rust crates MUST be named rust-$crate.

Any library crates that get packaged MUST be published on crates.io, since this enforces a certain standard in how crates are packaged and built, and also ensures that all crate dependencies are available from crates.io as well.

Crates that only contain an application — but no library — may be packaged even if they are not published on crates.io, because those packages are leaves and no other Rust packages can depend on them.

Rust applications that are not published to crates.io MUST follow the default Naming Guidelines.

Package sources

The primary Source for library crates — that MUST be published on crates.io — MUST be specified as %{crates_source}, which uses the %{crate} and %{version} macros to calculate the download URL.

Application crates do not have to be published on crates.io, so only the general Guidelines for Sources apply.

In the case that crates published to crates.io are missing files (.desktop files for GUI applications, manual pages, default configuration files, etc.), they MAY be included from the upstream sources, but the packager SHOULD query upstream to include those missing files in their published crates.

Package Dependencies

All Rust packages MUST have BuildRequires: rust-packaging.

Bundled Dependencies

As stated in the general Packaging Guidelines, packages MUST be built against system libraries, if that is possible. For Rust, this means that packages MUST NOT use dependencies from a "vendor tarball" (e.g. crated by running cargo vendor), but package all library dependencies separately.

However, two common reasons that can make building a package against system libraries (crates) basically impossible are if a crate applies downstream patches on top of its dependencies, or if it depends on "internal" crates that are not published on crates.io. In these circumstances, packagers MAY use bundled dependencies, but - if possible - SHOULD work with upstream to, for example, make their downstream patches unnecessary, or publish internal crates.

If bundled dependencies are used to build a binary package, the subpackage that will contain the compiled binary MUST have Provides: bundled(crate(foo)) = version for each bundled crate and the packager MUST keep this list of crates and their versions up-to-date every time the package or its bundled dependencies are updated.

Automatic Dependency Generation

rust-packaging automatically creates Requires and Provides based on cargo metadata in %{cargo_registry}/*/Cargo.toml files.

The Provides generator creates:

  • crate($name) = $version for base package (rust-$name-devel)

  • crate($name/$feature) = $version for feature subpackages (rust-$name+$feature-devel)

The Provides / Requires generator relies on empty subpackages with special names for encoding the dependency information of optional features. These subpackages MUST be named rust-%{crate}+$FEATURE-devel for all features that are present in Cargo.toml after patching this file in %prep.

These feature subpackage definitions are automatically correctly generated when using rust2rpm (if necessary, with the -p flag to apply any initial changes).

The dependency generator then creates Requires for all features that a crate depends on. For example, a dependency on syn with the visit and extra-traits features

syn = { version = "0.15", features = ["visit", "extra-traits"] }

will be encoded as

Requires:       (crate(syn/default) >= 0.15.0 with crate(syn/default) < 0.16.0)
Requires:       (crate(syn/extra-traits) >= 0.15.0 with crate(syn/extra-traits) < 0.16.0)
Requires:       (crate(syn/visit) >= 0.15.0 with crate(syn/visit) < 0.16.0)

where the default feature of syn is also included, because the default-features=false option was not specified.

BuildRequires

Rust packages SHOULD use automatic generation of BuildRequires by including this scriptlet between %prep and %build:

%generate_buildrequires
%cargo_generate_buildrequires

This will automatically generate the necessary BuildRequires based on the Cargo.toml file. This step runs after %prep, so any modification of Cargo.toml (for example, after applying patches to remove dependencies or to modify versions of dependencies) will be taken into account.

If using %cargo_generate_buildrequires is not possible, BuildRequires MUST be specified manually and kept up-to-date with each package update:

[dependencies]
atty = "0.2.2"
[build-dependencies]
clap = "2.24.1"

should become

BuildRequires:  (crate(atty/default) >= 0.2.2 with crate(atty/default) < 0.3.0)
BuildRequires:  (crate(clap/default) >= 2.24.1 with crate(clap/default) < 3.0.0)

Crate Versions

  • Packagers SHOULD package the latest version of a crate.

  • Packagers SHOULD patch crates to use the latest version of their dependencies, to reduce the downstream maintenance burden and the need for compat packages.

  • When introducing patches to bump the version of dependencies, packagers SHOULD forward these patches to the upstream project to keep the divergence between downstream and upstream small over time.

Compatibility packages for older crate versions

If it is not possible to port a crate to the version of a dependency that is available in Rawhide, a compatibility ("compat") package for the older version of a crate can be introduced. No package reviews for such compatibility packages are necessary, but they must follow the Naming Guidelines for compatibility packages.

For example, the latest rust-nix package might ship the latest version of nix, but some packages still require the older 0.14.1 version of the crate — in this case, the compatibility package would be called rust-nix0.14.

Compatibility packages for older versions of library crates are always parallel-installable with each other, since all files in them are namespaced by both the crate’s name and version.

However, compatibility packages for older crate versions MUST NOT ship application binaries. Neither the names of the subpackages that contain those binaries, nor the binaries themselves, are namespaced by the crate version. The package for the older version MUST be adapted to remove its binary subpackage, so it only contains -devel subpackage(s) but no %{crate} subpackage, which would conflict with the corresponding package from the newer version of the crate.

When introducing a compatibility package, the packager SHOULD check if keeping the test suite enabled causes additional unwanted dependencies, for example, on other compatibility packages, or on old versions of other packages. If that is the case, the test suite SHOULD be disabled to lower the overall maintenance burden.

ExclusiveArch

All rust packages MUST have ExclusiveArch: %{rust_arches}.

License for binary packages

Since Rust applications are statically linked and contain code from all their dependencies, the effective license for the subpackage containing the built binary must be calculated from the individual licenses of all dependencies.

The rust2rpm project provides a helper script that can be used to obtain a list of Licenses of all real crate dependencies, excluding dependencies that are only required at build-time or for running tests.

Both the list of individual licenses and the License tag with the effective license MUST be included in the subpackage that contains the statically linked binary.

Miscellaneous

Packagers MUST run %cargo_prep after unpacking the crate’s sources in %prep, which sets up configuration for cargo (compilation flags, location of system crates, etc.).

Excluding unnecessary files

  • Packagers SHOULD exclude files which are not used by anything (things like appveyor.yml and CI scripts).

  • Packagers SHOULD use the exclude field in Cargo.toml instead of using %exclude in %files

  • Packagers SHOULD forward such patches to upstream

Example:

--- csv-1.0.1/Cargo.toml	1970-01-01T01:00:00+01:00
+++ csv-1.0.1/Cargo.toml	2018-09-25T07:14:47.639840+02:00
@@ -22,6 +22,7 @@
 categories = ["encoding", "parser-implementations"]
 license = "Unlicense/MIT"
 repository = "https://github.com/BurntSushi/rust-csv"
+exclude = ["/.travis.yml", "/appveyor.yml", "/ci/*", "/scripts/*"]
 [profile.bench]
 debug = true

Nightly features and dependencies for other platforms

Packagers MUST NOT package crates which do not work on Fedora, this includes:

  • crates depending on nightly-only features of the Rust compiler

  • crates with (non-Linux) platform-specific dependencies

If such features and/or platform-specific dependencies are optional and can be removed, the Cargo.toml file MUST be patched to remove them, for example:

--- memmap-0.7.0/Cargo.toml	1970-01-01T00:00:00+00:00
+++ memmap-0.7.0/Cargo.toml	2019-03-18T19:59:43.683403+00:00
@@ -23,9 +23,6 @@
 version = "0.3"
 [target."cfg(unix)".dependencies.libc]
 version = "0.2"
-[target."cfg(windows)".dependencies.winapi]
-version = "0.3"
-features = ["basetsd", "handleapi", "memoryapi", "minwindef", "std", "sysinfoapi"]
 [badges.appveyor]
 repository = "danburkert/mmap"

Notes on the build process

The build process for Rust packages has evolved over time, especially for binary packages. Packages for source-only library crates were historically only built on Rawhide, but will also start being available on stable branches starting with Fedora 34.
  • Originally, binary packages for stable releases were built using Modularity, leveraging its buildroot-only packages filtering capabilities, but this approach was dropped in the Fedora 31 timeframe.

  • From Fedora 31 to Fedora 33, binary packages are built with a special process (leveraging koji side tags), using base packages from the relevant stable Fedora release, except for Rust crates, which are included from Rawhide.

  • Starting with Fedora 34, the workflow for building Rust packages will no longer be special — packages for both library crates and applications will be available and built normally, without having to rely on a special procedure for release branches.

Source-only Rust packages were granted a general exception to the Updates policy, so they can be freely updated in release branches in addition to rawhide (announced in the corresponding FESCo ticket: F34 System-Wide Change: Rust Crate Packages For Release Branches).

Examples

Library crate

Rust library crates are packaged as source-only packages because Rust does not (yet) support shared libraries due to the lack of a stabilized ABI for Rust.

The source code is shipped in a -devel subpackage, with separate subpackages for all features specified in Cargo.toml, which encode the dependency information for all features and dependencies.

rust-serde.spec
# Generated by rust2rpm 15
# * RUSTC_BOOTSTRAP breaks tests
%bcond_with check
%global debug_package %{nil}

%global crate serde

Name:           rust-%{crate}
Version:        1.0.116
Release:        1%{?dist}
Summary:        Generic serialization/deserialization framework

# Upstream license specification: MIT OR Apache-2.0
License:        MIT or ASL 2.0
URL:            https://crates.io/crates/serde
Source:         %{crates_source}

ExclusiveArch:  %{rust_arches}
%if %{__cargo_skip_build}
BuildArch:      noarch
%endif

BuildRequires:  rust-packaging

%global _description %{expand:
Generic serialization/deserialization framework.}

%description %{_description}

%package        devel
Summary:        %{summary}
BuildArch:      noarch

%description    devel %{_description}

This package contains library source intended for building other packages
which use "%{crate}" crate.

%files          devel
%license LICENSE-MIT LICENSE-APACHE
%doc README.md crates-io.md
%{cargo_registry}/%{crate}-%{version_no_tilde}/

%package     -n %{name}+default-devel
Summary:        %{summary}
BuildArch:      noarch

%description -n %{name}+default-devel %{_description}

This package contains library source intended for building other packages
which use "default" feature of "%{crate}" crate.

%files       -n %{name}+default-devel
%ghost %{cargo_registry}/%{crate}-%{version_no_tilde}/Cargo.toml

%package     -n %{name}+alloc-devel
Summary:        %{summary}
BuildArch:      noarch

%description -n %{name}+alloc-devel %{_description}

This package contains library source intended for building other packages
which use "alloc" feature of "%{crate}" crate.

%files       -n %{name}+alloc-devel
%ghost %{cargo_registry}/%{crate}-%{version_no_tilde}/Cargo.toml

%package     -n %{name}+derive-devel
Summary:        %{summary}
BuildArch:      noarch

%description -n %{name}+derive-devel %{_description}

This package contains library source intended for building other packages
which use "derive" feature of "%{crate}" crate.

%files       -n %{name}+derive-devel
%ghost %{cargo_registry}/%{crate}-%{version_no_tilde}/Cargo.toml

%package     -n %{name}+rc-devel
Summary:        %{summary}
BuildArch:      noarch

%description -n %{name}+rc-devel %{_description}

This package contains library source intended for building other packages
which use "rc" feature of "%{crate}" crate.

%files       -n %{name}+rc-devel
%ghost %{cargo_registry}/%{crate}-%{version_no_tilde}/Cargo.toml

%package     -n %{name}+serde_derive-devel
Summary:        %{summary}
BuildArch:      noarch

%description -n %{name}+serde_derive-devel %{_description}

This package contains library source intended for building other packages
which use "serde_derive" feature of "%{crate}" crate.

%files       -n %{name}+serde_derive-devel
%ghost %{cargo_registry}/%{crate}-%{version_no_tilde}/Cargo.toml

%package     -n %{name}+std-devel
Summary:        %{summary}
BuildArch:      noarch

%description -n %{name}+std-devel %{_description}

This package contains library source intended for building other packages
which use "std" feature of "%{crate}" crate.

%files       -n %{name}+std-devel
%ghost %{cargo_registry}/%{crate}-%{version_no_tilde}/Cargo.toml

%package     -n %{name}+unstable-devel
Summary:        %{summary}
BuildArch:      noarch

%description -n %{name}+unstable-devel %{_description}

This package contains library source intended for building other packages
which use "unstable" feature of "%{crate}" crate.

%files       -n %{name}+unstable-devel
%ghost %{cargo_registry}/%{crate}-%{version_no_tilde}/Cargo.toml

%prep
%autosetup -n %{crate}-%{version_no_tilde} -p1
%cargo_prep

%generate_buildrequires
%cargo_generate_buildrequires

%build
%cargo_build

%install
%cargo_install

%if %{with check}
%check
%cargo_test
%endif

%changelog

Binary crate

Rust applications are compiled to statically linked binaries, which are put into a subpackage that matches the name of the crate (without rust- prefix), e.g. the rust-ripgrep source package produces a ripgrep binary package, which contains the ripgrep binary.

rust-ripgrep.spec
# Generated by rust2rpm 15
%bcond_without check
%global __cargo_skip_build 0

%global crate ripgrep

Name:           rust-%{crate}
Version:        12.1.1
Release:        1%{?dist}
Summary:        Line oriented search tool using Rust's regex library

# Upstream license specification: Unlicense OR MIT
License:        Unlicense or MIT
URL:            https://crates.io/crates/ripgrep
Source:         %{crates_source}
# Initial patched metadata
# * No simd
# * No jemalloc
Patch0:         ripgrep-fix-metadata.diff

ExclusiveArch:  %{rust_arches}

BuildRequires:  rust-packaging

%global _description %{expand:
Line-oriented search tool that recursively searches your current directory for
a regex pattern while respecting your gitignore rules. ripgrep has first class
support on Windows, macOS and Linux.}

%description %{_description}

%package     -n %{crate}
Summary:        %{summary}
# * ASL 2.0 or Boost
# * ASL 2.0 or MIT
# * MIT
# * MIT or ASL 2.0
# * Unlicense or MIT
License:        MIT and (Boost or ASL 2.0)

%description -n %{crate} %{_description}

%files       -n %{crate}
%license LICENSE-MIT UNLICENSE COPYING
%doc README.md CHANGELOG.md
%{_bindir}/rg
%{_mandir}/man1/rg.1*
%dir %{_datadir}/bash-completion
%dir %{_datadir}/bash-completion/completions
%{_datadir}/bash-completion/completions/rg.bash
%dir %{_datadir}/fish
%dir %{_datadir}/fish/vendor_completions.d
%{_datadir}/fish/vendor_completions.d/rg.fish
%dir %{_datadir}/zsh
%dir %{_datadir}/zsh/site-functions
%{_datadir}/zsh/site-functions/_rg

%prep
%autosetup -n %{crate}-%{version_no_tilde} -p1
%cargo_prep

%generate_buildrequires
%cargo_generate_buildrequires -a
echo '/usr/bin/asciidoctor'

%build
%cargo_build -a

%install
%cargo_install -a
install -Dpm0644 -t %{buildroot}%{_mandir}/man1 \
  target/release/build/%{crate}-*/out/rg.1
install -Dpm0644 -t %{buildroot}%{_datadir}/bash-completion/completions \
  target/release/build/%{crate}-*/out/rg.bash
install -Dpm0644 -t %{buildroot}%{_datadir}/fish/vendor_completions.d \
  target/release/build/%{crate}-*/out/rg.fish
install -Dpm0644 -t %{buildroot}%{_datadir}/zsh/site-functions \
  complete/_rg

%if %{with check}
%check
%cargo_test -a
%endif

%changelog

Library + Binary

Some crates ship both a compiled binary and a reusable library component, in this case, both the -devel subpackage(s) and the subpackage containing the binary are built.

When building a package like this for a stable Fedora release (up to and including Fedora 33), the .spec file SHOULD be modified to remove all -devel subpackages, so no dangling broken dependencies on other (unavailable) library crates are introduced.

rust-yubibomb.spec
# Generated by rust2rpm 13
%bcond_without check

%global crate yubibomb

Name:           rust-%{crate}
Version:        0.2.1
Release:        1%{?dist}
Summary:        Rust command line tool that prints out a 6-digit random number

# Upstream license specification: GPL-3.0
License:        GPLv3
URL:            https://crates.io/crates/yubibomb
Source:         %{crates_source}

ExclusiveArch:  %{rust_arches}
%if %{__cargo_skip_build}
BuildArch:      noarch
%endif

BuildRequires:  rust-packaging

%global _description %{expand:
Don't you love when you accidentally tap your Yubikey when you have your IRC
client in focus and you send 987947 into Freenode? Want to be able to have that
experience without having to reach all the way over to your laptop's USB port?
Now you can!.}

%description %{_description}

%if ! %{__cargo_skip_build}
%package     -n %{crate}
Summary:        %{summary}

%description -n %{crate} %{_description}

%files       -n %{crate}
%license LICENSE
%doc README.md
%{_bindir}/yubibomb
%endif

%package        devel
Summary:        %{summary}
BuildArch:      noarch

%description    devel %{_description}

This package contains library source intended for building other packages
which use "%{crate}" crate.

%files          devel
%license LICENSE
%doc README.md
%{cargo_registry}/%{crate}-%{version_no_tilde}/

%package     -n %{name}+default-devel
Summary:        %{summary}
BuildArch:      noarch

%description -n %{name}+default-devel %{_description}

This package contains library source intended for building other packages
which use "default" feature of "%{crate}" crate.

%files       -n %{name}+default-devel
%ghost %{cargo_registry}/%{crate}-%{version_no_tilde}/Cargo.toml

%prep
%autosetup -n %{crate}-%{version_no_tilde} -p1
%cargo_prep

%generate_buildrequires
%cargo_generate_buildrequires

%build
%cargo_build

%install
%cargo_install

%if %{with check}
%check
%cargo_test
%endif

%changelog