Rust Packaging Guidelines

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 -s $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.

However, there are a few common situations in which manual edits of the generated .spec file or patches for upstream Cargo.toml files (with rust2rpm -p) are required, for example:

  • trimming a generated Summary tag that is too long

  • dropping non-linux platform-specific dependencies (automated by rust2rpm v22+)

  • dropping features that are only supported by nightly builds of the Rust compiler

  • dropping unstable features that require manually overriding compiler flags

  • dropping unused optional features with missing dependencies (can be automated by using a rust2rpm.conf configuration file)

Package Sources

Only library crates that are published on crates.io MAY be packaged for Fedora, since this enforces a certain standard of how crates are packaged and built, ensures that all the crate’s dependencies are available from crates.io as well, and that there are no package name collisions due to the unique crate names in the crates.io namespace.

Crates that contain only an application but no library interface MAY be packaged even if they are not published on crates.io, because those packages are leaves and cannot be depended on by other Rust crates.

Complex projects with multiple components (i.e. bindings for other languages), which cannot be packaged from sources that are published on crates.io alone, MAY include subpackages for their Rust library interface (rust-$crate-devel, rust-$crate+feature-devel, etc.) ONLY if this crate is also published on crates.io under the same name.

Package Naming

Library crates

Source packages for Rust crates which contain a library with a public API MUST be named rust-$crate.

Mixed library / application crates

Source packages for Rust crates which contain both a library with a public API and an application MUST be named rust-$crate (i.e. follow the rules for library crates).

The convention set by rust2rpm for the name of the subpackage that contains the application binary (or binaries) is to use the name of the crate itself. However, in some cases, the crate name does not match the name of the built application(s), and in this case the subpackage name SHOULD be adjusted to match expectations.

Application-only crates

Packages for application-only crates that are published on crates.io MUST NOT drop the rust- prefix from their source package names, because a crate can start to include a public library API at any time, in which case the "Mixed library / application crates" rules would apply, and the package would need to be renamed to include the rust- prefix.

Application-only Rust projects which are not packaged from crates.io must follow the general Naming Guidelines (i.e. drop the rust- prefix from the source package name) so that they cannot introduce potential source package name collisions with crates from the crates.io namespace.

rust2rpm does not support generating .spec files for packages without a rust- name prefix yet. In this case, .spec files written by rust2rpm will need to be manually edited after each update.

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. created 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.

Some crates are published with additional information encoded into their version string. For example, Rust bindings for native libraries might add a +x.y.z-style suffix to indicate which version of the native library is required or supported. Suffixes like these MUST be removed from the crate’s Cargo.toml file and dropped from the package Version, because they might introduce dependency resolution problems when this suffix leaks into RPM Provides metadata.

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.

License for binary packages

See License: field in Spec file guidance specific to Rust.

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"
The patch to remove platform-specific dependencies for non-linux platforms is automatically generated by rust2rpm versions 22 and later. Additionally, a configuration file for rust2rpm (rust2rpm.conf) can be added to the package’s dist-git repository to disable generation of subpackages for specific features, (for example, nightly-only features).

Update process

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).

If possible, packages for Rust library crates SHOULD be updated to the same version across all currently supported branches of Fedora. Exceptions for this rule might be:

  • system dependencies are too old on older branches (for example, libgit2)

  • pushing API-incompatible versions would result in broken packages on stable branches (for example, packages for the gtk-rs project crates)

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 Apache-2.0-or-later
URL:            https://crates.io/crates/serde
Source:         %{crates_source}

%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

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

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}
# * Apache-2.0-or-later or BSL-1.0
# * Apache-2.0-or-later or MIT
# * MIT
# * MIT or Apache-2.0-or-later
# * Unlicense or MIT
License:        MIT and (BSL-1.0 OR Apache-2.0-or-later)

%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.

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

License:        GPL-3.0-only
URL:            https://crates.io/crates/yubibomb
Source:         %{crates_source}

%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