From f05cf4d31c88bf48869b190dc570e86f31883030 Mon Sep 17 00:00:00 2001 From: Fabio Valentini Date: Oct 11 2023 23:22:48 +0000 Subject: Rewrite Rust Packaging Guidelines (and drop outdated example specs) --- diff --git a/guidelines/modules/ROOT/examples/rust-ripgrep.spec b/guidelines/modules/ROOT/examples/rust-ripgrep.spec deleted file mode 100644 index ab20cff..0000000 --- a/guidelines/modules/ROOT/examples/rust-ripgrep.spec +++ /dev/null @@ -1,82 +0,0 @@ -# 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 diff --git a/guidelines/modules/ROOT/examples/rust-serde.spec b/guidelines/modules/ROOT/examples/rust-serde.spec deleted file mode 100644 index 9f7e445..0000000 --- a/guidelines/modules/ROOT/examples/rust-serde.spec +++ /dev/null @@ -1,145 +0,0 @@ -# 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 diff --git a/guidelines/modules/ROOT/examples/rust-yubibomb.spec b/guidelines/modules/ROOT/examples/rust-yubibomb.spec deleted file mode 100644 index cdd5476..0000000 --- a/guidelines/modules/ROOT/examples/rust-yubibomb.spec +++ /dev/null @@ -1,85 +0,0 @@ -# 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 diff --git a/guidelines/modules/ROOT/pages/Rust.adoc b/guidelines/modules/ROOT/pages/Rust.adoc index db66eff..09ee8a0 100644 --- a/guidelines/modules/ROOT/pages/Rust.adoc +++ b/guidelines/modules/ROOT/pages/Rust.adoc @@ -1,374 +1,870 @@ = Rust Packaging Guidelines -:last-reviewed: 2022-08-23 -== rust2rpm - -Note that the https://pagure.io/fedora-rust/rust2rpm[*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). - -NOTE: 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 xref:Naming.adoc[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. - -NOTE: `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 - -NOTE: As stated in the general xref:index.adoc#bundling[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+`. - -NOTE: 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 +:last-reviewed: 2023-10-12 + +https://www.rust-lang.org[Rust] is a strongly and statically typed, +compiled programming language +that supports concepts from both imperative and functional programming. + +Because there is not yet a stable Rust ABI, +and because conditional compilation is a widely used feature in the Rust ecosystem, +Rust libraries ("crates") can not be distributed in compiled form, +and are instead distributed as source code. + +This document covers how to handle Rust code in packages, +specific to the different ways in which projects can be set up: + +* link:#_rust_crates[Rust "crates"]: + packages that are individually published on https://crates.io[crates.io], + the official package registry for Rust + (primarily libraries, but also "single-crate" applications) +* Rust link:#_rust_applications_non_crates_io_crates[applications that are single crates but not published on crates.io] +* Rust projects that are organized as link:#_rust_applications_cargo_workspace_projects[cargo "workspaces"]: + usually larger projects with "internal" crates, + which are often not separately published on https://crates.io[crates.io] +* link:#_python_projects[Python projects] with a "native" component implemented in Rust: + usually built with https://github.com/PyO3/setuptools-rust[setuptools_rust] or https://github.com/PyO3/maturin[maturin] +* link:_mixed_rust_cc_projects[mixed Rust / C/C++ projects] where parts of the project are implemented in Rust: + either built by wrapping cargo, + or by utilizing https://mesonbuild.com/Rust.html[meson]’s limited support + for directly building Rust code + +For the first three of these cases, +the https://pagure.io/fedora-rust/rust2rpm[rust2rpm] tool can be used +to generate spec files from cargo / crate metadata. +It is designed to produce spec files +that are in line with the (Rust) Packaging Guidelines. + +There are also guidelines for link:#_building_shared_libraries_with_cargo_c[packaging shared libraries] +that are implemented in Rust +(usually built and installed with https://crates.io/crates/cargo-c[cargo-c]). + +== Generic rules + +This section covers rules that apply to _all_ packages that ship Rust code. + +=== Compiler flags + +Similarly to other language ecosystems in Fedora, +there is a standardized set of compiler flags +that *MUST* be passed to the Rust compiler. + +The defaults for Rust are defined in the `%build_rustflags` macro +from the `rust-srpm-macros` package. +It is part of the default buildroot on Fedora 39+, +where the `%set_build_flags` macro automatically sets the `$RUSTFLAGS` environment variable +based on this macro. + +For compatibility with older releases, +this environment variable can be set manually at the start of `%build` and `%check` +in package’s spec files: + +[source,shell] +---- +export RUSTFLAGS="%build_rustflags" +---- -Rust packages SHOULD use automatic generation of `BuildRequires` -by including this scriptlet between `+%prep+` and `+%build+`: +This is not necessary for packages that use the `%cargo_prep`, `%cargo_build`, and `%cargo_test` macros, +which configure `cargo` to use the default `%build_rustflags` directly. + +=== Mandatory `BuildRequires` + +The RPM macros that provide basic functionality +for building Rust code are included in `rust-rpm-macros`, +which is part of the default buildroot in Fedora, +as it is a dependency of `redhat-rpm-config`. +When building for ELN or EPEL8, this is not the case, +and packages need to use `BuildRequires: rust-toolset`. + +Packages that build Rust code with cargo – directly or indirectly – +or which call any of the `%cargo_*` macros, +*MUST* add `BuildRequires: cargo-rpm-macros >= 24`, +which provides the implementations of all `%cargo_*` macros. +This package is not part of the default buildroot, +since it pulls in additional dependencies +(i.e. a Python interpreter). + +For backwards compatibility, +packages *MAY* instead depend on earlier versions of the `%cargo_*` macros, +which were provided in the `rust-packaging` package, +if they do not depend on any of the features or macros +that are only available with newer versions. +When generating a spec file for a crate with https://pagure.io/fedora-rust/rust2rpm[rust2rpm], +it will automatically detect usage of these features, +and include the necessary `BuildRequires` +for the RPM macro package automatically. + +=== License tags + +Similar to other languages that produce statically linked binaries, +Rust executables (and shared libraries) contain code +that originates in other packages +(i.e. packages for other Rust crates), +which in turn are covered by different license terms. + +This needs to be taken into account +by maintaining a separate `License` tag +for the subpackage that contains these binaries. +More information about `License` tags is available from the link:[Fedora Legal docs]. + +The `cargo-rpm-macros` package provides two RPM macros +that help with filling the `License` tag correctly: + +- `%cargo_license_summary` + +This macro determines and prints a summary +of all the licenses of the Rust crates +that end up statically linked into final binaries +(properly excluding build-only or test-only dependencies). +This summary can then be copied from the build log +into the spec file as a comment. +The actual contents of the `License` tag can then be obtained +by constructing a conjunction of these individial licenses (with SPDX `AND` operators). + +- `%cargo_license` + +This macro determines and prints a complete breakdown of all Rust crates +that end up statically linked into final binaries +(according to the same logic as in the `%cargo_license_summary` macro), +their versions, _and_ their individual license expressions. +Generating this list dynamically at build-time +ensures that its contents always match the actual dependencies. + +Both macros accept the same arguments +as all other `%cargo_*` macros (`-a`, `-n`, `-f`), +and for their output to match the actual binaries, +the same flags need to be passed to them and `%cargo_build`. + +=== Vendored dependencies + +https://docs.fedoraproject.org/en-US/packaging-guidelines/#bundling[In general], +packages *SHOULD NOT* use bundled crate dependencies, +whenever possible. + +Whenever vendored / bundled crate dependencies _are_ used +(no matter which mechanism is used for the purpose), +all bundled crate dependencies *MUST* be declared +with virtual `Provides` in the format `Provides: bundled(crate($crate)) = $version` +in the subpackage that contains the Rust component. +For example, these virtual `Provides` are used to determine +the impact of security vulnerabilities on packages +that use vendored Rust dependencies. + +Building exclusively from vendored dependencies +by using a tarball that was generated by running `cargo vendor` +*SHOULD* only be a last resort. +However, there are also two _rare_ situations +in which bundling at least _some_ Rust crates is likely unavoidable. + +==== Replacing git dependencies + +One of the types of dependencies cargo supports are git snapshots, +which are usually used to reference either a specific commit, +or a reference to a downstream fork of a crate. + +The project *SHOULD* be patched to use a version of this crate +that is available on https://crates.io[crates.io] instead, +if that is possible. +If it turns out that depending on a git snapshot is no longer necessary, +this patch *SHOULD* be submitted to the upstream project. + +If the dependency is not published on https://crates.io[crates.io], +or if the versions published there are not a suitable replacement, +a git snapshot of the crate can be bundled. +This can be achieved by creating and supplying a tarball +with the git snapshot as a separate source, +unpacking the tarball in `%prep`, +and patching `Cargo.toml` to replace the git-based dependency +with a path-based dependency. + +==== Replacing patched crate sources + +Another way in which cargo supports specifying modified dependencies is +by "patching" a crate source, +specifying an alternative source for specific crates – +which will likely be either git references or path-based dependencies +that are present to override a crate +that is published on https://crates.io[crates.io] with a (modified) local copy, +or a git repository that points to a (modified) fork of the crate. + +These replacements *SHOULD* be dropped +in favor of using only published versions of crates. +If that is not possible, +they must be replaced by path-based dependencies, +similar to the process described for link:#_replacing_git_dependencies[git-type dependencies]. + +==== Using vendor tarballs + +Official support for building with vendored dependencies +was added in version 25 of cargo-rpm-macros and rust2rpm. + +* The `%cargo_prep` macro accepts a `-v $VENDOR` argument, + where `$VENDOR` is the path to the directory + that contains the vendored crate dependencies. + When this argument is passed, + the generated cargo configuration is set up + for building against dependencies in this directory + instead of dependencies from the system-wide registry. +* The `%cargo_generate_buildrequires` macro *MUST NOT* be called + when using vendored dependencies. +* The `%cargo_vendor_manifest` macro generates a manifest (`cargo-vendor.txt`) + that lists the names and versions of all crates in the vendor tarball. + This macro *MUST* be called (for example, in the `%build` scriptlet), + and the generated file *MUST* be added as a `%license` file + in the appropriate package’s list of `%files`. + An RPM generator parses this file + and generates appropriate virtual `Provides` for all bundled crates, + as is required for any bundled dependencies. + +Typically, the `%prep` scriptlet will look like this +when using vendored dependencies +(assuming `Source1` is the vendor tarball, +and it contains a top-level `vendor` directory): + +[source,rpm] +---- +%prep +%autosetup -%{crate}-%{version} -p1 -a1 +%cargo_prep -v vendor +---- -.... -%generate_buildrequires -%cargo_generate_buildrequires -.... +The necessary spec file adaptations +and the generation of the vendor tarball itself +happen automatically when running rust2rpm in "vendor" mode. -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. +== rust2rpm -If using `+%cargo_generate_buildrequires+` is not possible, -`BuildRequires` MUST be specified manually and kept up-to-date with each package update: +The recommended way to write spec files for Rust projects +is to use https://pagure.io/fedora-rust/rust2rpm[rust2rpm], +and apply any necessary modifications on top of the generated spec file. + +There are a few common situations +in which automatically generated spec files +need manual changes: + +* invalid `Summary` / `%description`: + The heuristics for generating the `Summary` or `%description` for the package + from the crate metadata can fail to produce valid values + (i.e. `Summary` tag that is too long). + In this case, the `Summary` needs to be shortened manually. + This can also be overridden + in the package-specific rust2rpm configuration file. +* unwanted dependencies / subpackages: + Some crates provide non-default / optional features + that are either unnecessary + (i.e. only applicable to non-Linux systems), + or have additional dependencies + that are not packaged for Fedora. + These features and unavailable optional dependencies *MUST* + be removed from crate metadata – otherwise, + the package will either fail to build, + or produce subpackages with broken dependencies. +* nightly-only / unstable features: + Some crates provide features + that are only available on a nightly version of the Rust compiler, + or features that are unstable and require an opt-in + by passing environment variables. + Features like these *SHOULD* be removed from crate metadata, + since they either cannot work + (Fedora ships only the stable Rust toolchain) + or are not feasible to support. +* unwanted / unnecessary files ("bloat"): + Some projects include files + that are not required for the crate to function properly + (files for CI settings, development / helper scripts, etc.). + Files like these *SHOULD* be prevented from being installed + (by adding / modifying the `package.include` + or `package.exclude` settings in the crate metadata). + It is recommended to submit changes like this to the upstream project. +* incompatible compiler flags: + Some crates include custom settings for the `release` profile + that are incompatible with RPM packaging. + These settings *MUST* be removed from the `release` profile + by patching `Cargo.toml`. + +Crates that provide Rust bindings for C libraries +usually require some additional changes (if possible): + +* linking against system libraries: + This often requires making some dependencies non-optional and / or + modifying `build.rs` scripts to unconditionally link against system libraries + instead of building and statically linking a bundled copy of the library. +* regenerating Rust bindings (and tests for them) at build-time: + This too often requires making the `bindgen` dependency non-optional and / or + modifying `build.rs` to cause regeneration of Rust bindings at build-time. + +Note that patching `Cargo.toml` files +(especially changing the set of optional dependencies and features) +*MUST* be done by running `rust2rpm -p`, +since changes like these affect spec file generation +(i.e. the list of generated subpackages), +which is only correctly taken into account +if the patch is created _before_ generation of the spec file. + +== Rust crates + +A large part of the process of packaging Rust crates +can (and _should_) be automated by using https://pagure.io/fedora-rust/rust2rpm[rust2rpm]. +It is designed to generate spec files that are compliant +with both the general and the Rust Packaging Guidelines. + +Additionally, due to some properties of packages for Rust crates +(i.e. subpackages that correspond to crate features / optional dependencies), +it is necessary to re-run https://pagure.io/fedora-rust/rust2rpm[rust2rpm] +for every new version of a crate +to ensure that generated feature subpackages +stays in sync with crate metadata. + +=== Package naming + +The canonical source of Rust crates is https://crates.io[crates.io]. + +Rust crates that are published on https://crates.io[crates.io] +*MUST* be packaged with `rust-$crate` as the name of the source package +(where `$crate` is the name of the project on https://crates.io[crates.io]). +Packages for Rust crates that are generated with https://pagure.io/fedora-rust/rust2rpm[rust2rpm] +follow to this pattern. + +On the other hand, +projects from other sources *MUST NOT* use the `rust-` prefix for source package names, +and *MUST* follow the general Naming Guidelines instead. +They also *MUST NOT* have any virtual `Provides` for Rust crates +(i.e. `Provides: crate(%{crate}) = %{version}`). + +When generating a package for a Rust crate +that also (or exclusively) contains an application, +the convention followed by https://pagure.io/fedora-rust/rust2rpm[rust2rpm] is +to generate a subpackage with a name that matches the crate’s name +(i.e. the `rust-$crate` source package will have a `$crate` subpackage). +If this name does not match expectations, +it is recommended to either change the name of this subpackage, +or to add virtual `Provides` for the expected name. + +=== Package versioning + +Projects that are built with cargo and / or published on https://crates.io[crates.io] +follow Semantic Versioning (with small cargo-specific tweaks). +Since SemVer strings can contain characters that are invalid in RPM version strings, +they *MUST* be translated to be RPM-compatible. + +For example, pre-releases are denoted by a `-
` suffix in SemVer,
+but the `-` character is invalid in RPM Versions.
+This can be solved by replacing `-` with the `~` character,
+which denotes pre-releases in RPM version strings.
+This translation happens automatically for the `Version` tag
+when generating a spec file with https://pagure.io/fedora-rust/rust2rpm[rust2rpm],
+and the "upstream" version is stored in a separate macro
+that can be used to refer to the "original" version string.
+
+Additionally, some Rust crates carry extra "build" metadata in their versions
+(a `+` suffix).
+This format is primarily used to carry information
+about the version of a bundled C library.
+This `+` suffix *MUST* be removed from crate metadata with a patch,
+since it can interfere with RPM dependency / version resolution.
+This happens automatically when using rust2rpm version 25 or newer.
+
+=== Package sources
+
+Projects from https://crates.io[crates.io] *MUST* be packaged from the sources
+that are published there (i.e. by using the `%{crates_source}` macro).
+
+If the sources published on https://crates.io[crates.io]
+do not contain all files that are necessary for creating the package
+(for example, missing `.desktop` file or man pages),
+the upstream sources can be used as an _additional_ source,
+but they *MUST NOT* be used for building the crate itself.
+It is recommended to file an issue with the upstream project
+about including these additional files in published crates.
+
+If a crate is also part of a larger project
+and it is not feasible to package the Rust crate separately,
+the Rust crate *MAY* be packaged as `rust-$crate` from different sources
+(i.e. an upstream tarball) _if and only if_ the crate is also published on https://crates.io[crates.io]
+under the same name and with matching versions.
+In this case, the subpackage(s) corresponding to Rust crates
+*MUST* have correct virtual `Provides` for the Rust crate.
+
+=== Crate license
+
+Most tooling support for determining licenses
+requires accurate metadata about licenses in crate metadata,
+including the `%cargo_license*` macros,
+and other third-party tools like `cargo-license` and `cargo-deny`.
+
+For this reason, the license metadata for all Rust crates packaged for Fedora
+*MUST* match the license tag of the Fedora package itself.
+Any crates that set `package.license-file` in their metadata
+(which is reserved for non-standard / non-SPDX licenses)
+*MUST* be patched to set `package.license` in their metadata instead
+in cases where this is _not_ appropriate
+and an accurate SPDX expression can be provided.
+Patches like this *SHOULD* be submitted upstream.
+
+=== RPM macros
+
+The process of building and installing Rust crates
+is almost entirely automated with several RPM macros:
+
+* `%cargo_prep`:
+  This macro *MUST* be called in the `%prep` scriptlet
+  after sources have been unpacked.
+  It sets up the build environment for cargo
+  and injects a cargo configuration file,
+  which sets the default compiler flags
+  and configures the local crate registry
+  as a replacement for https://crates.io[crates.io].
+* `%cargo_generate_buildrequires`:
+  This macro *MUST* be called in the `%generate_buildrequires` scriptlet,
+  except when building with vendored dependencies.
+  This is the mechanism that automatically generates depepdencies on other Rust crates
+  based on the metadata in `Cargo.toml`.
+* `%cargo_build`:
+  This macro *MUST* be called in the `%build` scriptlet.
+  It runs `cargo build` with the appropriate command line arguments.
+  Calling this macro *MAY* be skipped
+  if the crate is not supported on the current CPU architecture.
+* `%cargo_install`:
+  This macro *MUST* be called in the `%install` scriptlet
+  for crates that provide a library interface.
+  It runs `cargo package` and installs the resulting directory tree
+  into `%{buildroot}/%{crate_instdir}`
+  (i.e. `%{buildroot}/%{cargo_registry}/%{crate}-%{version}/`).
+  For crates that provide `bin` targets,
+  it installs all built executables into `%{buildroot}/%{_bindir}`.
+  If any built executables need to be installed in a different location,
+  they can be moved after calling `%cargo_install`,
+  or `%cargo_install` can be replaced
+  with manual installation steps.
+* `%cargo_test`:
+  This macro *MUST* be called in the `%check` scriptlet.
+  It runs `cargo test` with the appropriate command line arguments.
+  Calling this macro *MAY* be skipped
+  if the crate is not supported on the current CPU architecture
+  or if tests are disabled in general.
+* `%cargo_license` / `%cargo_license_summary`:
+  These macros *MUST* be called in the `%build` scriptlet after `%cargo_build`
+  when building crates that include binary targets.
+  They can be used to print the list of the licenses
+  of the crates that are statically linked into any built executable
+  or shared library (see link:#_license_tags[License tags]).
+
+All packages for Rust crates *MUST* set
+either `%bcond_without check` or `%bcond_with check`.
+The value of this macro affects the behaviour of `%cargo_generate_buildrequires`.
+
+All `%cargo_*` macros (except `%cargo_prep`)
+accept a set of optional flags / arguments
+that can be used to control the feature flags that are passed to cargo
+(usually to enable optional / non-default features):
+
+* `-a`:
+  Causes the `--all-features` flag to be passed to cargo,
+  and the `%cargo_generate_buildrequires` macro
+  to resolve dependencies with all optional features enabled.
+* `-n`:
+  Causes the `--no-default-features` flags to be passed to cargo,
+  and the `%cargo_generate_buildrequires` macro
+  to resolve dependencies with all default and optional features disabled.
+* `-f foo,bar`:
+  Causes the `--features foo,bar` argument to be passed to cargo,
+  and the `%cargo_generate_buildrequires` macro
+  to resolve dependencies with the additional features `foo` and `bar` enabled.
+  This argument accepts a comma-separated list of feature names
+  (or names of optional dependencies).
+
+The `-a` and `-n` flags are mutually exclusive
+and cannot be passed together.
+The `-a` flag and `-f` arguments are also incompatible,
+since passing `-a` already enables all features.
+However, using the `-n` flag and specifically enabling _some_ features
+with the `-f` argument is valid.
+
+There are some common situations
+in which passing these flags or arguments is necessary:
+
+* It can be necessary to enable additional features and / or optional dependencies
+  to build and run the test suite of a crate.
+  In this case, the required features *MUST* be enabled
+  by passing the corresponding flags to all `%cargo_*` macros,
+  unless the required optional dependencies are not packaged yet.
+* Some applications support additional / non-default features
+  by passing feature flags.
+  If it is desirable to build applications with these features enabled,
+  the required features need to be enabled
+  by passing the corresponding flags to all `%cargo_*` macros
+  (including `%cargo_license` and `%cargo_license_summary`).
+
+Note that the `-n` flag should only be used in exceptional circumstances,
+and *MUST NOT* be used to avoid missing dependencies
+that are part of the `"default"` feature set of a crate.
+
+=== Dynamically generated `BuildRequires` for crate dependencies
+
+With Semantic Versioning (_"SemVer"_) being
+the only supported versioning scheme for Rust crates,
+dependencies on Rust libraries are almost exclusively specified as
+_"this version or any newer version that is API-compatible with it"_,
+i.e. a range of supported versions.
+
+These ranges of supported versions
+need to be correctly translated into RPM dependencies,
+otherwise a wrong version of a dependency might get pulled in for builds,
+causing unhelpful error messages about missing dependencies.
+
+Since dependencies of Rust projects often change with every new release,
+and keeping a list of `BuildRequires` up-to-date manually is tedious and error-prone,
+packages for projects that build Rust code with cargo
+*MUST* use dynamically generated `BuildRequires`
+by calling the `%cargo_generate_buildrequires` macro
+in the `%generate_buildrequires` scriptlet.
+
+For example, a dependency on `serde = "1.0.100"`
+specified in a project's `Cargo.toml` metadata
+(a dependency on the crate named "serde",
+with version "1.0.100" or any version API-compatible with "1.0.100",
+with default features enabled)
+would cause a dependency like this to be generated for RPM:
 
 ....
-[dependencies]
-atty = "0.2.2"
-[build-dependencies]
-clap = "2.24.1"
+BuildRequires:  (crate(serde/default) >= 1.0.100 with crate(serde/default) < 2.0.0~)
 ....
 
-should become
+Refer to the section about link:#_rpm_macros[RPM macros]
+for how to pass feature flags to this macro.
+
+Issues with the `%cargo_generate_buildrequires` macro
+that prevent it from being used for a package
+should be reported against https://pagure.io/fedora-rust/cargo2rpm[cargo2rpm],
+the tool that provides the functionality of this macro.
+
+=== Subpackages for crate features
+
+Optional features / dependencies of Rust crates are translated into RPM subpackages
+to support resolving dependencies for features and optional dependencies of crates.
+The list of crate "features"
+(including any implicitly defined features for optional dependencies)
+*MUST* be kept in sync with the list of subpackages,
+i.e. for every feature `$foo` of the crate `$crate`,
+there must be a subpackage with name `rust-$crate+$foo-devel`,
+and vice-versa.
+This is required for RPM generators for `Provides` and `Requires`
+for these optional features / dependencies to work correctly.
+
+If optional features that are _not_ part of the default feature set
+are unused and would pull in additional (possibly unavailable) dependencies,
+the package *MAY* omit subpackages for these specific feature names.
+However, care needs to be taken
+that the features corresponding to the ommitted subpackages
+are not "reachable" via subpackages that have _not_ been omitted,
+since this would result in packages with unsatisfiable dependencies.
+Disabling optional features sometimes cannot be handled correctly
+simply by omitting subpackages for specific features.
+In these cases, the crate metadata in `Cargo.toml`
+needs to be patched accordinly instead.
+
+Beware that the "default" feature is always implicitly defined by cargo,
+even if the crate metadata does not contain a `[features]` table
+or an explicitly defined "default" feature,
+so the subpackage for the "default" feature
+will be present in all packages for Rust crates with a library interface.
+
+=== RPM generators for `Provides` and `Requires`
+
+The cargo-rpm-macros package includes RPM generators
+for automatically generating `Provides` and `Requires`
+for Rust crates that comply with the Packaging Guidelines
+(i.e. install their files into the correct location, `%{crate_instdir}`).
+
+It is recommended to verify
+that the generated `Provides` and `Requires` are sane – for example,
+the following `Provides` and `Requires`
+must be present to ensure correct inter-subpackage dependencies:
+
+* the main `rust-$crate-devel` subpackage
+  *MUST* provide `crate($crate) = %{version}`
+* the `rust-$crate+$feature-devel` subpackages
+  *MUST* provide `crate($crate/$feature) = %{version}`
+  and require `crate($crate) = %{version}` (i.e. `rust-$crate-devel`)
+
+Additionally, dependencies on external Rust crates must be as expected:
+
+* the main `rust-$crate-devel` subpackage
+  *MUST* require the virtual `Provides` for all non-optional crate dependencies
+* the `rust-$crate+$feature-devel` subpackages
+  *MUST* require the virtual `Provides` for the optional crate dependencies
+  and features that are listed as the feature’s dependencies in crate metadata
+
+=== Packaging multiple versions
+
+In most circumstances,
+the latest version of a crate *SHOULD* be packaged,
+and – if possible – packagers *SHOULD* port crates
+to use the latest available version of their dependencies,
+and submit these patches to upstream
+to limit divergence between the upstream project and the Fedora package.
+
+However, there are two common scenarios
+in which it is often necessary to provide packages
+for multiple versions of a library crate simultaneously:
+
+* It is not feasible to port a crate
+  to the version of a crate dependency in Fedora
+  due to large API changes between the required and the packaged version.
+* The number of packages affected
+  by a required SemVer-incompatible library update is very large.
+
+In these cases, a "compat package" can be created for the older version
+(i.e. usually the current version),
+and the suffix-less package can be updated to the newer version.
+https://pagure.io/fedora-rust/rust2rpm[rust2rpm]
+supports automatically creating "compat packages"
+with names that are compliant with the
+https://docs.fedoraproject.org/en-US/packaging-guidelines/Naming/#multiple[Naming Guidelines for this case]
+_and_ compatible with the restrictions of Semantic Versioning
+by using the `rust2rpm --compat` flag.
+
+All "compat packages" for Rust crates
+*MUST* follow the guidelines for Rust crates,
+and two additional rules apply when creating them:
+
+* For crates that also includes an executable,
+  only the package for the _latest_ version can include this executable,
+  and it *MUST NOT* be built and included in any older versions,
+  to prevent both the name of the executable under `/usr/bin`
+  and the name of the subpackage would conflict
+  between the old and the new version of the package.
+* The packager *SHOULD* check
+  whether running tests in the old version of the crate
+  would cause additional, potentially undesirable dependencies,
+  for example, older versions of other dependencies
+  that would require creating additional "compat packages" –
+  in this case, tests *SHOULD* be disabled
+  (i.e. by flipping the `check` bcond).
+
+=== The `check` bcond
+
+The behaviour of some RPM macros
+depends on the presence and value of the `_with_check` macro,
+i.e. if `%bcond_without check` or `%bcond_with check`
+are used in the spec file – notably,
+the `%cargo_generate_buildrequires` macro only includes `dev-dependencies`
+(i.e. dependencies that are only used
+for compiling and / or running a project’s test suite with cargo)
+if the `check` bconf is enabled.
+
+Additionally, packages for Rust crates or workspace projects
+that are generated by https://pagure.io/fedora-rust/rust2rpm[rust2rpm]
+use the value of this macro to determine if the `%check` scriptlet is run.
+
+Packages *MUST* set this bcond
+to avoid unexpected behaviour of the `%cargo_*` macros,
+by either explicitly _enabling_ or _disabling_ tests.
+
+=== Running tests
+
+Rust crates can have three different kinds of tests in their test suites:
+
+* _"unit tests"_:
+  These tests are included
+  alongside library / application source code in the `src/` directory,
+  and can reference private APIs
+  (similar to "glass-box" tests).
+* _"integration tests"_:
+  These tests are usually separate files under the `tests/` directory,
+  and they can only rely on public API of the tested crate
+  (similar to "black-box" tests).
+* _"doctests"_:
+  These tests are automatically extracted
+  from code blocks in Markdown documentation,
+  which is often used as a mechanism
+  to ensure that code snippets in documentation for public methods
+  are correct and continue to compile.
+
+By default, running `cargo test` (i.e. by calling the `%cargo_test` macro),
+all three kinds of tests are run.
+They can also be invoked separately
+(for example, because parts of the test suite or large data files are
+not included in published sources)
+by passing through filtering arguments
+to the underlying `cargo test` command:
+
+* `%cargo_test -- --lib`: only run _"unit tests"_ for the library interface
+* `%cargo_test -- --bin`: only run _"unit tests"_ for binaries
+* `%cargo_test -- --doc`: only run _"doctests"_
+* `%cargo_test -- --tests`: only run _"integration tests"_
+
+This can be combined with additional flags
+to skip tests with specific names
+(or that contain a specific string in their name)
+by passing the `--skip` argument through to the test harness
+(can be specified multiple times):
 
 ....
-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)
+%cargo_test -- --lib -- --skip foo::bar::tests::test1
 ....
 
-== 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.
-
-CAUTION:    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 xref:Naming.adoc#multiple[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`.
-
-NOTE: 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.
+By default, cargo uses substring matching
+to match `--skip` arguments and actual names of tests,
+which can be turned off by using the `--exact` flag.
 
-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.
+If any tests are skipped or disabled,
+the package *SHOULD* include comments
+that explain why this is the case,
+and include links to upstream issues, if available.
 
-NOTE: 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.
+== Rust applications (non-crates.io crates)
+
+Most rules that apply to Rust crates
+that are published in https://crates.io[crates.io]
+also apply to Rust projects that _are_ built with cargo
+but _not_ published on https://crates.io[crates.io].
 
-== License for binary packages
+https://pagure.io/fedora-rust/rust2rpm[rust2rpm]
+has basic support for generating spec files for this type of package
+by running `rust2rpm path/to/Cargo.toml` in unpacked upstream sources.
 
-See xref:legal::license-field.adoc#_rust_packages[License: field in Spec file] guidance specific to Rust.
- 
+Packages that fall into this category *MUST NOT* ship crate sources in `%{cargo_registry}`,
+i.e. they cannot ship `-devel` subpackages
+that contain crate sources or have subpackages
+that have virtual provides for `crate(...) = %{version}`.
 
-== Miscellaneous
+=== Package naming
 
-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.).
+Rust applications that are "crates"
+but which are not published on https://crates.io[crates.io]
+*MUST* be named according to the generic
+https://docs.fedoraproject.org/en-US/packaging-guidelines/Naming/[Naming Guidelines],
+i.e. they *MUST NOT* use a `rust-` prefix for the source package name.
 
-=== Excluding unnecessary files
+=== Package sources
 
-* Packagers SHOULD exclude files which are not used by anything (things like `appveyor.yml` and CI scripts).
-* Packagers SHOULD use the https://doc.rust-lang.org/cargo/reference/manifest.html#the-exclude-and-include-fields-optional[exclude field in Cargo.toml] instead of using `+%exclude+` in `+%files+`
-* Packagers SHOULD forward such patches to upstream
+The generic guidelines for
+https://docs.fedoraproject.org/en-US/packaging-guidelines/SourceURL/[referencing sources] apply.
+Notably, the `%{crates_source}` macro cannot be used for packages like this.
 
-Example:
+== Rust applications (cargo workspace projects)
 
-[source,diff]
-----
---- 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
- 
-----
+Some Rust projects are organized as "cargo workspaces",
+which are collections of Rust crates
+that are often considered "internal implementation details".
+In most cases, these "internal" crates
+are not published individually on https://crates.io[crates.io].
 
-=== Nightly features and dependencies for other platforms
+Projects that use this setup
+can be handled similarly to Rust projects
+that are organized as a single crate
+but not published on https://crates.io[crates.io].
 
-Packagers MUST NOT package crates which do not work on Fedora, this includes:
+https://pagure.io/fedora-rust/rust2rpm[rust2rpm]
+has basic support for generating spec files for this type of package
+by running `rust2rpm path/to/Cargo.toml` in unpacked upstream sources,
+(where `./Cargo.toml` must be the path to the "workspace root",
+i.e. the `Cargo.toml` file that contains the `[workspace]` table).
 
-* crates depending on nightly-only features of the Rust compiler
-* crates with (non-Linux) platform-specific dependencies
+Packages that fall into this category
+*MUST NOT* ship crate sources in `%{cargo_registry}`,
+i.e. they cannot ship `-devel` subpackages that contain crate sources
+or have any subpackages that have virtual provides for `crate(...) = %{version}`.
+
+=== Package naming
 
-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:
+Rust projects that are organized as "cargo workspaces"
+*MUST* be named according to the generic
+https://docs.fedoraproject.org/en-US/packaging-guidelines/Naming/[Naming Guidelines],
+i.e. they *MUST NOT* use a `rust-` prefix for the source package name.
 
-[source,diff]
-----
---- 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"
- 
-----
+=== Package sources
 
-NOTE: 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).
+The generic guidelines for
+https://docs.fedoraproject.org/en-US/packaging-guidelines/SourceURL/[referencing sources]
+apply.
 
-== Update process
+=== RPM macros
 
-Source-only Rust packages were granted a general exception to the
-https://docs.fedoraproject.org/en-US/fesco/Updates_Policy/[Updates policy],
-so they can be freely updated in release branches in addition to rawhide
-(announced in the corresponding FESCo ticket:
-https://pagure.io/fesco/issue/2474[F34 System-Wide Change: Rust Crate Packages For Release Branches]).
+All `%cargo_*` macros have support for cargo workspaces
+as of `cargo-rpm-macros >= 24`.
+Any unexpected results that occur when using these macros for projects
+that are set up as a cargo workspace
+should be reported against https://pagure.io/fedora-rust/cargo2rpm[cargo2rpm].
 
-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:
+Note that currently, any `-a` and `-n` flags or `-f` arguments
+that are passed to `%cargo_generate_buildrequires`
+are applied to _all_ workspace members during dependency resolution.
 
-* 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)
+== Python projects
 
-== Examples
+Python packages that use https://github.com/PyO3/setuptools-rust[setuptools_rust]
+or https://github.com/PyO3/maturin[maturin]
+to build a "native" Python extension
+also need to apply the link:#_generic_rules[generic rules] for  Rust packages
+in addition to following the
+https://docs.fedoraproject.org/en-US/packaging-guidelines/Python/[Python Packaging Guidelines].
 
-=== Library crate
+Both https://github.com/PyO3/setuptools-rust[setuptools_rust]
+and https://github.com/PyO3/maturin[maturin]
+build the native Python extension by calling cargo internally,
+so the basic setup for projects that build with cargo
+is required for packages like this as well.
 
-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.
+This includes calling `%cargo_prep` in `%prep`
+to set up the build environment for cargo,
+and using `%cargo_generate_buildrequires` to dynamically generate
+the appropriate `BuildRequires` for Rust crate dependencies.
 
-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.
+Additionally, `%cargo_license` and / or `%cargo_license_summary`
+*MUST* be used to determine the licenses
+that apply to the statically linked Python extension.
+
+The packager also *MUST* ensure
+that the default link:#_compiler_flags[compiler flags] are passed to rustc.
 
-.rust-serde.spec
-[source]
-----
-include::{examplesdir}/rust-serde.spec[]
-----
+== Mixed Rust / C/C++ projects
+
+Handling of projects that include both C/C++ and Rust code
+depends on how building the Rust code is integrated
+into the project’s build system.
+
+Independent of the specific setup,
+the correct link:#_compiler_flags[compiler flags] *MUST* be passed to rustc,
+and the License tag of the package that contains the Rust component
+*MUST* take the licenses of statically linked crates into account.
 
-=== Binary crate
+=== Building with cargo internally
+
+Projects with build systems that call cargo internally to build Rust components
+*MUST* follow the same guidelines
+as other projects that build Rust code with cargo.
 
-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.
+Packages *MUST* ensure that the cargo calls
+that are internal to the project’s build system
+do not pass flags or arguments that are incompatible
+with either the default link:#_compiler_flags[compiler flags] or cargo options
+that are set in the `%cargo_build` macro or configured by `%cargo_prep`.
 
-.rust-ripgrep.spec
-[source]
-----
-include::{examplesdir}/rust-ripgrep.spec[]
-----
+=== Building with meson directly
+
+Upcoming versions of https://mesonbuild.com/Rust.html[meson]
+will have support for building crate dependencies
+by reading `Cargo.toml` files directly,
+with meson supporting a similar mechanism
+for overriding https://crates.io[crates.io] sources with a local registry.
 
-=== Library + Binary
+== Building shared libraries with cargo-c
+
+While it is not currently possible
+to build Rust crates as shared libraries,
+Rust projects can define a C-compatible public API
+so that they can be built as standard shared libraries with a C ABI.
+
+In most cases, libraries like this are built with
+https://crates.io/crates/cargo-c[cargo-c],
+which provides convenient wrappers (`cargo-cbuild` and `cargo-cinstall`)
+for both building and installing shared libraries
+(including support for generating and installing header files
+and and pkg-config files).
 
-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.
+The `cargo-c` package includes RPM macros for this functionality
+(`%cargo_cbuild` and `%cargo_cinstall`),
+which accept the same arguments as their cargo counterparts.
 
-.rust-yubibomb.spec
-[source]
-----
-include::{examplesdir}/rust-yubibomb.spec[]
-----