Rust Packaging Guidelines

This document details best practices for packaging Rust crates. Note that the rust2rpm tool, available as a Fedora package or at https://pagure.io/fedora-rust/rust2rpm, automates many of these steps. It is advisable to try rust2rpm $crate first before attempting to write a specfile by hand.

This document is applicable only for Fedora Rawhide. Stable releases do not contain any crates. However, this document can be used to build modules with Rust applications (with crates filtered out).

Naming

Rust crates MUST be named rust-$crate. The crates are expected to be from crates.io. Rust applications that aren’t from crates.io MUST follow the main guidelines for package names.

At this time, Rust libraries MUST be from crates.io, as this enforces a certain standard in how they are packaged and built.

Dependencies

Packages MUST have BuildRequires: rust-packaging.

Automatic Dependency Generation

rust-packaging automatically creates Requires/Provides based on %{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 automatic requirement generator takes this into account and creates the appropriate rich dependencies to ensure that the code works.

For example:

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

becomes

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

BuildRequires

Packagers MUST specify all BuildRequires according to the definition in Cargo.toml, for example:

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

Versions

  • Packagers SHOULD use latest version of dependent crates.

  • Packagers SHOULD patch crates to use the latest version of dependent crates to reduce maintenance burden.

  • When doing so, packagers SHOULD forward these upstream so that the upstream software is fixed to support the latest versions of their dependencies.

ExclusiveArch

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

Others

Packagers MUST run %cargo_prep to prepare configuration for further cargo invocations (sets up RUSTFLAGS and all other stuff).

Exclude unnecessary files

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

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

  • 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, Other Platforms, etc. crates

Packagers MUST NOT package crates which do not work with the distribution. That is, if the crate depends on nightly-only features or works only for non-Linux platforms, the crate is not suitable for inclusion in Fedora.

If the crate can be made usable, packagers MUST patch packages which use such dependencies, 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"

Such patches SHOULD be forwarded upstream.

Examples

Library

Rust library packages are packaged as source-only packages, as we do not build dynamic link libraries at this time due to the lack of a stabilized ABI for Rust.

rust-serde.spec
# Generated by rust2rpm
%bcond_without check
%global debug_package %{nil}

%global crate serde

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

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

ExclusiveArch:  %{rust_arches}

BuildRequires:  rust-packaging
%if %{with check}
BuildRequires:  (crate(serde_derive/default) >= 1.0.0 with crate(serde_derive/default) < 2.0.0)
%endif

%global _description \
A 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}/

%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}/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}/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}/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}/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}/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}/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}/Cargo.toml

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

%build
%cargo_build

%install
%cargo_install

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

%changelog
* Mon Mar 18 20:55:02 CET 2019 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 1.0.89-1
- Initial package

Binary

Application packages are compiled into binaries, with the application name used as the package name for the output binary package.

rust-ripgrep.spec
# Generated by rust2rpm
%bcond_without check

%global crate ripgrep

Name:           rust-%{crate}
Version:        0.10.0
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 windows
# * No simd
# * Use jit_if_available, https://github.com/BurntSushi/ripgrep/commit/eb18da04506b959c0251099eae83e16d22ce8bcb
Patch0:         ripgrep-fix-metadata.diff
# Really use jit_if_available
Patch0001:      0001-pcre2-use-jit_if_available.patch

ExclusiveArch:  %{rust_arches}

BuildRequires:  rust-packaging
BuildRequires:  (crate(clap/suggestions) >= 2.32.0 with crate(clap/suggestions) < 3.0.0)
BuildRequires:  (crate(grep/default) >= 0.2.3 with crate(grep/default) < 0.3.0)
BuildRequires:  (crate(grep/pcre2) >= 0.2.3 with crate(grep/pcre2) < 0.3.0)
BuildRequires:  (crate(ignore/default) >= 0.4.4 with crate(ignore/default) < 0.5.0)
BuildRequires:  (crate(lazy_static/default) >= 1.1.0 with crate(lazy_static/default) < 2.0.0)
BuildRequires:  (crate(log/default) >= 0.4.5 with crate(log/default) < 0.5.0)
BuildRequires:  (crate(num_cpus/default) >= 1.8.0 with crate(num_cpus/default) < 2.0.0)
BuildRequires:  (crate(regex/default) >= 1.0.5 with crate(regex/default) < 2.0.0)
BuildRequires:  (crate(serde_json/default) >= 1.0.23 with crate(serde_json/default) < 2.0.0)
BuildRequires:  (crate(termcolor/default) >= 1.0.3 with crate(termcolor/default) < 2.0.0)
%if %{with check}
BuildRequires:  (crate(serde/default) >= 1.0.77 with crate(serde/default) < 2.0.0)
BuildRequires:  (crate(serde_derive/default) >= 1.0.77 with crate(serde_derive/default) < 2.0.0)
%endif
BuildRequires:  %{_bindir}/a2x

%global _description \
Line oriented search tool using Rust's regex library.\
Combines the raw performance of grep with the usability of the silver searcher.

%description %{_description}

%package     -n %{crate}
Summary:        %{summary}

%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_functions.d
%{_datadir}/fish/vendor_functions.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

%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_functions.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
* Mon Mar 18 21:02:51 CET 2019 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 0.10.0-1
- Initial package

Library + Binary

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

%global crate yubibomb

Name:           rust-%{crate}
Version:        0.2.0
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}
# Initial patched metadata
# * Bump regex to 1, https://gitlab.com/bowlofeggs/yubibomb/commit/301cb10cd056cd33c4736b87aaad333f77c6c252
Patch0:         yubibomb-fix-metadata.diff

ExclusiveArch:  %{rust_arches}

BuildRequires:  rust-packaging
BuildRequires:  (crate(rand/default) >= 0.4.0 with crate(rand/default) < 0.5.0)
%if %{with check}
BuildRequires:  (crate(regex/default) >= 1.0.0 with crate(regex/default) < 2.0.0)
%endif

%global _description \
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}

%package     -n %{crate}
Summary:        %{summary}

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

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

%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}/

%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}/Cargo.toml

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

%build
%cargo_build

%install
%cargo_install

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

%changelog
* Mon Mar 18 21:09:53 CET 2019 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 0.2.0-1
- Initial package