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 include BuildRequires: rust-packaging >= 21
.
However, if the package is for a Rust crate
whose Cargo.toml
metadata uses syntax for feature dependencies
which was added with Rust 1.60,
i.e. namespaced dependencies ("dep:foo"
)
or weak dependency features ("foo/bar"
),
it MUST include BuildRequires: cargo-rpm-macros >= 24
instead,
since support for this syntax was only added
as part of a complete rewrite of rust-packaging
that was shipped as cargo-rpm-macros
version 24.
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
The RPM packaging machinery for Rust crates
automatically generates 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.
# 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.
# 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.
# 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
Want to help? Learn how to contribute to Fedora Docs ›