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