Pautas de empaquetado de Rust

Rust es un tipado fuerte y estático, lenguaje de programación compilado que admite conceptos desde ambas programaciones imperativas y funcionales.

Debido a que todavía no existe una ABI estable para Rust, y debido a que la compilación condicional es una característica ampliamente utilizada en el ecosistema de Rust, las bibliotecas de Rust ("crates") no se pueden distribuir en forma compilada, sino que se distribuyen como código fuente.

Este documento cubre como manipular código Rust en paquetes, especificado para las diferentes maneras en las cuales pueden ser instalados los proyectos:

  • link: #_rust_crates[Rust "crates"]: paquetes que se publican individualmente en crates.io, el registro oficial de paquetes de Rust (principalmente bibliotecas, pero también aplicaciones de "caja única")

  • Proyectos de Rust sin cajas: Proyectos de Rust que no están publicados en crates.io, o proyectos más grandes que se componen de muchos cajas (espacio de trabajo de carga)

  • Proyectos de Python con un componente "nativo" implementado en Rust: generalmente construido con setuptools_rust o maturin

  • enlace: #_mixed_rust_cc_projects[proyectos mixtos Rust / C/C++] donde partes del proyecto se implementan en Rust: ya sea compiladas envolviendo cargo o utilizando soporte limitado de meson para compilar directamente código Rust

The rust2rpm tool can be used to generate spec files for Rust crates 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 packaging shared libraries that are implemented in Rust (usually built and installed with cargo-c).

Reglas genéricas

Esta sección cubre reglas que se aplican a todos los paquetes que contienen código de Rust.

Indicadores del compilador

Similarmente a otro ecosistema del lenguaje en Fedora, hay un conjunto estándar de indicadores de compilador que DEBE ser pasado al compilador de Rust.

Los valores predeterminados de Rust se definen en la macro %build_rustflags del paquete rust-srpm-macros. Forma parte del directorio raíz de compilación predeterminado en Fedora 39+, donde la macro %set_build_flags auto-establece la variable de entorno $RUSTFLAGS en función de esta macro.

Para compatibilidad con versiones anteriores o EPEL, esta variable de entorno se puede configurar manualmente al inicio de %build y %check en los archivos de especificaciones del paquete:

export RUSTFLAGS="%build_rustflags"

Esto no es necesario para paquetes que utilizan las macros %cargo_prep, %cargo_build, y %cargo_test, las cuales configuran cargo para utilizar directamente el %build_rustflags por defecto.

BuildRequires obligatorio

Las macros RPM que proporcionan funcionalidad básica para compilar código Rust están incluidas en rust-rpm-macros, la cual es parte de la raíz de construcción por defecto, como es una dependencia de redhat-rpm-config. Cuando compile para ELN o EPEL8, esto no es el caso, y los paquetes necesitan utilizar BuildRequires: rust-toolset.

Los paquetes que compilan código Rust con cargo, directa o indirectamente, o que invocan cualquiera de las macros %cargo_*, DEBEN agregar BuildRequires: cargo-rpm-macros, lo cual proporciona las implementaciones de todas las macros %cargo_*. Este paquete no forma parte del buildroot predeterminado, ya que incorpora dependencias adicionales (p.ej., un intérprete de Python).

BuildRequires generado dinámicamente para dependencias de caja

Dado que el control de versiones semántico ("SemVer") es el único esquema de control de versiones compatible con los paquetes Rust, las dependencias de las bibliotecas Rust se especifican casi exclusivamente como "esta versión o cualquier versión más nueva que sea compatible con la API con ella", es decir, un rango de versiones compatibles.

Estos intervalos de versiones compatibles deben traducirse correctamente en dependencias de RPM; de lo contrario, es posible que se incluya una versión incorrecta de una dependencia para las compilaciones, lo que provocaría mensajes de error inútiles sobre dependencias ausentes.

Ya que las dependencias de los proyectos Rust cambian con frecuencia, los paquetes para proyectos que compilan código Rust con cargo DEBEN usar BuildRequires generados dinámicamente invocando a la macro %cargo_generate_buildrequires en el scriptlet %generate_buildrequires.

Por ejemplo, una dependencia en serde = "1.0.100" especificada en los metadatos Cargo.toml de un proyecto (una dependencia en el paquete nombrado “serde”, con la versión "1.0.100" o cualquier versión compatible con API con "1.0.100", con funciones predeterminadas habilitadas) haría que se genere una dependencia como esta para RPM:

BuildRequires:  (crate(serde/default) >= 1.0.100 con crate(serde/default) < 2.0.0~)

Refiérete a la sección sobre macros de RPM para saber como pasar indicadores de características a esta macro.

Etiquetados de licencia

Al igual que otros lenguajes que producen binarios enlazados estáticamente, los ejecutables de Rust (y bibliotecas compartidas) contienen código que se origina en otros paquetes (es decir, paquetes para otras cajas de Rust), que a su vez están cubiertos por diferentes términos de licencia.

Esto debe tenerse en cuenta manteniendo una etiqueta Licencia independiente para el subpaquete que contiene estos binarios. Puede encontrar más información sobre las etiquetas Licencia en Documentos legales de Fedora.

El paquete cargo-rpm-macros proporciona dos macros RPM que ayudan con rellenar la etiqueta License correctamente para proyectos que sean compilados con cargo:

  • %cargo_license_summary

Esta macro determina e imprime un resumen de todas las licencias de los crates de Rust que se enlazan estáticamente en los binarios finales (excluyendo correctamente las dependencias de solo compilación o de solo prueba). Este resumen puede copiarse del registro de compilación al archivo de especificaciones como comentario. El contenido real de la etiqueta License puede obtenerse mediante la construcción de una conjunción de estas licencias individuales (con los operadores AND de SPDX).

  • %cargo_license

Esta macro determina e imprime un desglose completo de todos los crates de Rust que terminan enlazados estáticamente a los binarios finales (según la misma lógica que en la macro %cargo_license_summary), sus versiones y sus expresiones de licencia individuales. Generar esta lista dinámicamente durante la compilación garantiza que su contenido siempre coincida con las dependencias reales.

Ambas macros aceptan los mismos argumentos que otras macros %cargo_* (-a, -n, -f), y para que su salida coincida con los binarios reales, DEBEN pasarseles a ellas y a %cargo_build los mismos indicadores.

Dependencias de proveedor

En general, los paquetes NO UTILIZARÍAN dependencias de cajas incluidas, siempre que sea posible.

Siempre que se utilicen dependencias de caja (independientemente del mecanismo utilizado), todas las dependencias de caja DEBEN declararse con Provides virtuales con el formato Provides: bundled(crate($crate)) = $version en el subpaquete que contiene el componente de Rust. Por ejemplo, estos Provides virtuales se utilizan para determinar el impacto de las vulnerabilidades de seguridad en los paquetes que utilizan dependencias de caja.

También hay dos situaciones en las que agrupar al menos algunas cajas de Rust suele ser inevitable:

Reemplazo de dependencias de Git

Uno de los tipos de dependencias que admite cargo son las instantáneas de git, que generalmente se utilizan para hacer referencia a una confirmación específica o a una referencia a una bifurcación posterior de un paquete.

El proyecto DEBERÍA parchearse para usar una versión de este crate disponible en crates.io, si es posible. Si resulta que ya no es necesario depender de una instantánea de Git, este parche DEBERÍA enviarse al proyecto original.

Si la dependencia no está publicada en crates.io, o si las versiones publicadas allí no son un reemplazo adecuado, se puede empaquetar una instantánea de Git del crate. Esto se puede lograr creando y proporcionando un archivo tar con la instantánea de Git como fuente independiente, descomprimiendo el archivo tar en %prep y parcheando Cargo.toml para reemplazar la dependencia basada en Git con una dependencia basada en ruta. Añadir este subproyecto como miembro del espacio de trabajo garantiza que sus dependencias se incluyan durante la resolución de dependencias.

Reemplazo de fuentes de cajas parcheadas

Otra forma en la que cargo permite especificar dependencias modificadas es "parcheando" una fuente de caja, especificando una fuente alternativa para cajas específicas, que probablemente serán referencias de Git o dependencias basadas en rutas que estén presentes para anular una caja publicada en crates.io con una copia local (modificada), o un repositorio de Git que apunte a una bifurcación (modificada) de la caja.

Estos reemplazos DEBERÍAN descartarse y optar por usar solo versiones publicadas de los crates. Si esto no es posible, deben reemplazarse por dependencias basadas en rutas, similar al proceso descrito para el enlace:#_replacing_git_dependencies[git-type dependencies].

Uso de archivos tar de proveedores

Se agregó soporte para compilar con dependencias proporcionadas en la versión 25 de cargo-rpm-macros y rust2rpm.

  • La macro %cargo_prep acepta el argumento -v $VENDOR, donde $VENDOR es la ruta al directorio que contiene las dependencias de la caja vendida. Al pasar este argumento, la configuración de cargo generada se configura para compilar con las dependencias de este directorio, en lugar de las del registro del sistema.

  • La macro %cargo_generate_buildrequires NO DEBE llamarse cuando se utilizan dependencias suministradas.

  • La macro %cargo_vendor_manifest genera un manifiesto (cargo-vendor.txt) que lista los nombres y versiones de todos los crates en el archivo tar del proveedor. Esta macro DEBE ser llamada (por ejemplo, en el scriptlet %build), y el archivo generado DEBE añadirse como un archivo %license en la lista de %files del paquete correspondiente. Un generador de RPM analiza este archivo y genera los Provides virtuales apropiados para todos los crates incluidos, como se requiere para cualquier dependencia incluida.

  • Paquetes que se compilan con dependencias proporcionadas NO DEBE proporcionar una interfaz de biblioteca Rust (es decir, en subpaquetes -devel), porque los paquetes resultantes tendrían dependencias rotas.

Típicamente, el scriptlet %prep se verá así cuando se usan dependencias proporcionadas por el proveedor (asumiendo que Source1 es el archivo tar del proveedor y contiene un directorio vendor de nivel superior):

%prep
%autosetup -%{crate}-%{version} -p1 -a1
%cargo_prep -v vendor

Las adaptaciones necesarias del archivo de especificaciones y la generación del archivo tar del proveedor se realizan automáticamente cuando se ejecuta rust2rpm en modo "proveedor".

Crates de Rust

Gran parte del proceso de empaquetado de cajas de Rust se puede (y debería) automatizar mediante rust2rpm. Está diseñado para generar archivos de especificaciones que cumplen con las Pautas de Empaquetado generales y de Rust.

Además, debido a algunas propiedades de los paquetes para los crates de Rust (es decir, subpaquetes que corresponden a características del crate/dependencias opcionales), rust2rpm DEBE volver a ejecutarse para cada nueva versión de un crate para garantizar que los subpaquetes de características generados permanezcan sincronizados con los metadatos del crate.

rust2rpm

La forma recomendada de escribir archivos de especificaciones para los paquetes Rust es usar rust2rpm y aplicar cualquier modificación necesaria sobre el archivo de especificaciones generado.

Hay algunas situaciones comunes en las que los archivos de especificaciones generados automáticamente necesitan cambios manuales:

  • Summary / %description no válidos: La heurística para generar Summary o %description del paquete a partir de los metadatos del crate puede no generar valores válidos (por ejemplo, la etiqueta Summary es demasiado larga). En este caso, es necesario acortar el Summary manualmente. Esto también se puede anular en el archivo de configuración de rust2rpm específico del paquete.

  • Dependencias/subpaquetes no deseados: Algunos crates ofrecen características no predeterminadas/opcionales que son innecesarias (es decir, solo aplicables a sistemas que no sean Linux) o tienen dependencias adicionales que no están empaquetadas para Fedora. Estas características y dependencias opcionales no disponibles DEBEN eliminarse de los metadatos del crate; de lo contrario, el paquete no se compilará o generará subpaquetes con dependencias defectuosas.

  • Características exclusivas de la versión nocturna/inestables: Algunas cajas ofrecen características que solo están disponibles en la versión nocturna del compilador de Rust, o características inestables que requieren una autorización mediante variables de entorno. Características como estas DEBERÍAN eliminarse de los metadatos de la caja, ya que no funcionan (Fedora solo incluye la cadena de herramientas estable de Rust) o no es viable su compatibilidad.

  • Archivos no deseados o innecesarios ("sobrecarga"): Algunos proyectos incluyen archivos innecesarios para el correcto funcionamiento del crate (archivos de configuración de CI, scripts de desarrollo o de ayuda, etc.). Se DEBERÍA evitar la instalación de este tipo de archivos (añadiendo o modificando los ajustes package.include o package.exclude en los metadatos del crate). Se recomienda enviar estos cambios al proyecto original.

  • Indicadores de compilación incompatibles: Algunas cajas incluyen configuraciones personalizadas para el perfil release que son incompatibles con el empaquetado RPM. Estas configuraciones DEBEN eliminarse del perfil release mediante la aplicación de un parche a Cargo.toml.

Las cajas que proporcionan enlaces de Rust para bibliotecas de C generalmente requieren algunos cambios adicionales (si es posible):

  • vinculación con bibliotecas del sistema: esto a menudo requiere hacer que algunas dependencias no sean opcionales y/o modificar los scripts build.rs para vincularse incondicionalmente con bibliotecas del sistema en lugar de construir y vincular estáticamente una copia incluida de la biblioteca.

  • regenerar enlaces de Rust (y pruebas para ellos) en el momento de la compilación: esto a menudo requiere hacer que la dependencia bindgen no sea opcional y/o modificar build.rs para provocar la regeneración de enlaces de Rust en el momento de la compilación.

Tenga en cuenta que la aplicación de parches a los archivos Cargo.toml (especialmente el cambio del conjunto de dependencias y características opcionales) DEBE realizarse ejecutando rust2rpm -p, ya que cambios como estos afectan la generación del archivo de especificaciones (es decir, la lista de subpaquetes generados), que solo se tiene en cuenta correctamente si el parche se crea antes de la generación del archivo de especificaciones.

Se recomienda rastrear los archivos de configuración rust2rpm.toml que no estén vacíos en el repositorio de paquetes junto con el archivo .spec generado.

Nombrado de paquete

Los paquetes para las cajas de Rust de crates.io DEBEN usar rust-$crate como el nombre del paquete fuente (donde $crate es el nombre del proyecto en crates.io).

Esto garantiza que no haya colisiones de nombres entre los paquetes de Rust publicados en crates.io y los paquetes de Rust empaquetados para Fedora.

Al generar un paquete para una caja de Rust que contiene destinos ejecutables, la convención que sigue rust2rpm es generar un subpaquete con un nombre que coincida con el nombre de la caja (es decir, el paquete fuente rust-$crate tendrá un subpaquete $crate). Si es necesario, este subpaquete se puede renombrar, por ejemplo, si el nombre no cumple con las expectativas o si entra en conflicto con otro paquete ya existente.

Control de versiones de paquetes

Los proyectos creados con cargo o publicados en crates.io siguen el control de versiones semántico (con pequeños ajustes específicos de cargo). Dado que las cadenas SemVer pueden contener caracteres no válidos en las cadenas de versión RPM, DEBEN traducirse para que sean compatibles con RPM.

Por ejemplo, las versiones preliminares se indican con el sufijo -<pre> en SemVer, pero el carácter - no es válido en las versiones RPM. Esto se puede solucionar reemplazando - por el carácter ~, que indica versiones preliminares en las cadenas de versiones RPM. Esta traducción se realiza automáticamente para la etiqueta Version al generar un archivo de especificaciones con rust2rpm, y la versión original se almacena en una macro independiente que puede usarse para referirse a la cadena de versión original.

Además, algunas cajas de Rust incluyen metadatos de compilación adicionales en sus versiones (un sufijo <build>`). Este formato se usa principalmente para incluir información sobre la versión de una biblioteca C incluida. Este sufijo `<build> DEBE eliminarse de los metadatos de la caja con un parche, ya que puede interferir con la resolución de dependencias/versiones de RPM. Esto ocurre automáticamente al usar rust2rpm versión 25 o posterior.

Fuentes de Paquete

La fuente canónica de los crates de Rust es crates.io. Los proyectos de crates.io DEBEN empaquetarse desde las fuentes que se publican allí (es decir, utilizando la macro %{crates_source}).

Si las fuentes publicadas en crates.io no contienen todos los archivos necesarios para crear el paquete (por ejemplo, si falta el archivo .desktop o las páginas de manual), las fuentes originales pueden usarse como fuente adicional, pero NO DEBEN usarse para compilar el paquete. Se recomienda informar al proyecto original sobre la inclusión de estos archivos adicionales en los paquetes publicados.

Como alternativa, si el proyecto en cuestión no proporciona una interfaz de biblioteca Rust, se puede empaquetar como un enlace: #_non_crate_rust_projects[Proyecto Rust que no es un paquete] utilizando las fuentes ascendentes en su lugar.

Licencia de caja

La mayoría de las herramientas de soporte para determinar licencias requieren metadatos precisos sobre las licencias en los metadatos de la caja, incluidas las macros %cargo_license* y otras herramientas de terceros como cargo-license y cargo-deny.

Por esta razón, los metadatos de licencia de todos los crates de Rust empaquetados para Fedora DEBEN coincidir con la etiqueta de licencia del propio paquete de Fedora. Cualquier crate que configure package.license-file en sus metadatos (reservado para licencias no estándar/no SPDX) DEBE ser parcheado para que configure package.license en sus metadatos, y DEBE proporcionarse una expresión SPDX precisa en su lugar. Parches como este DEBEN enviarse al servidor original.

Subpaquetes para funciones de caja

Las características/dependencias opcionales de los crates de Rust se traducen en subpaquetes RPM para facilitar la resolución de dependencias de características y dependencias opcionales de los crates. La lista de "características" del crate (incluidas las características definidas implícitamente para dependencias opcionales) DEBE mantenerse sincronizada con la lista de subpaquetes; es decir, para cada característica $foo del crate $crate, debe haber un subpaquete con el nombre rust-$crate+$foo-devel, y viceversa. Esto es necesario para que los generadores RPM de Provides y Requires funcionen correctamente.

Si las características opcionales que no forman parte del conjunto de características predeterminado no se utilizan y requieren dependencias adicionales (posiblemente no disponibles), el paquete PUEDE omitir subpaquetes para estos nombres de características específicos. Sin embargo, es importante asegurarse de que las características correspondientes a los subpaquetes omitidos no sean accesibles a través de subpaquetes que no se han omitido, ya que esto generaría paquetes con dependencias insatisfechas. La deshabilitación de características opcionales a veces no se puede gestionar correctamente simplemente omitiendo subpaquetes para características específicas. En estos casos, los metadatos de la caja en Cargo.toml deben parchearse como corresponde.

Tenga en cuenta que la característica "predeterminada" siempre está definida implícitamente por cargo, incluso si los metadatos del paquete no contienen una tabla [features] o una característica "predeterminada" definida explícitamente, por lo que el subpaquete para la característica "predeterminada" estará presente en todos los paquetes de paquetes de Rust con una interfaz de biblioteca.

Generadores de RPM para Provides y Requires

El paquete cargo-rpm-macros incluye generadores de RPM para generar automáticamente Provides y Requires para las cajas de Rust que cumplen con las Pautas de empaquetado (es decir, instalan sus archivos en la ubicación correcta, %{crate_instdir}).

Se recomienda verificar que los Provides y Requires generados sean correctos; por ejemplo, los siguientes Provides y Requires deben estar presentes para garantizar dependencias correctas entre subpaquetes:

  • El subpaquete principal rust-$crate-devel DEBE proporcionar crate($crate) = %{version}

  • Los subpaquetes rust-$crate+$feature-devel DEBEN proporcionar crate($crate/$feature) = %{version} y requieren crate($crate) = %{version} (es decir, rust-$crate-devel)

Además, las dependencias de los paquetes externos de Rust deben ser las esperadas:

  • el subpaquete principal rust-$crate-devel DEBE requerir el Provides virtual para todas las dependencias de caja no opcionales

  • los subpaquetes rust-$crate+$feature-devel DEBEN requerir el Provides virtual para las dependencias y características opcionales del cajón que se enumeran como dependencias de la característica en los metadatos del cajón

Empaquetar múltiples versiones

En la mayoría de las circunstancias, la última versión de un paquete DEBERÍA estar empaquetada y, si es posible, los empaquetadores DEBERÍAN portar los paquetes para usar la última versión disponible de sus dependencias y enviar estos parches al proyecto original para limitar la divergencia entre el proyecto original y el paquete de Fedora.

Sin embargo, hay dos escenarios comunes en los que a menudo es necesario proporcionar paquetes para múltiples versiones de un paquete de biblioteca simultáneamente:

  • No es posible portar un crate a la versión de una dependencia de crate en Fedora debido a los grandes cambios de API entre la versión requerida y la versión empaquetada.

  • La cantidad de paquetes afectados por una actualización de biblioteca incompatible con SemVer requerida es muy grande.

En estos casos, se puede crear un "paquete de compatibilidad" para la versión anterior (es decir, generalmente la versión actual) y el paquete sin sufijo se puede actualizar a la versión más nueva. rust2rpm admite la creación automática de "paquetes de compatibilidad" con nombres que cumplen con las Pautas de nomenclatura para este caso y compatibles con las restricciones del control de versiones semántico mediante el uso del indicador rust2rpm --compat.

Todos los "paquetes compatibles" para las cajas de Rust DEBEN seguir las pautas para las cajas de Rust, y se aplican dos reglas adicionales al crearlas:

  • Para los paquetes que también incluyen un ejecutable, solo el paquete de la última versión puede incluir este ejecutable, y NO DEBE compilarse e incluirse en ninguna versión anterior, para evitar que tanto el nombre del ejecutable en /usr/bin como el nombre del subpaquete entre la versión anterior y la nueva del paquete entren en conflicto.

  • El empaquetador DEBERÍA verificar si ejecutar pruebas en la versión anterior del paquete causaría dependencias adicionales, potencialmente indeseables, por ejemplo, versiones anteriores de otras dependencias que requerirían la creación de "paquetes de compatibilidad" adicionales - en este caso, las pruebas DEBERÍAN estar deshabilitadas (es decir, invirtiendo el segundo check).

El 'cheque' bcond

El comportamiento de algunas macros RPM depende de la presencia y el valor de la macro _with_check, es decir, si se utilizan %bcond_without check o %bcond_with check en el archivo de especificaciones; en particular, la macro %cargo_generate_buildrequires solo incluye dev-dependencies (es decir, dependencias que solo se utilizan para compilar y/o ejecutar el conjunto de pruebas de un proyecto con cargo) si el bcond check está habilitado.

Además, los paquetes para cajas Rust generados por rust2rpm usan el valor de esta macro para determinar si se ejecuta el scriptlet %check.

Los paquetes para cajas Rust DEBEN establecer este bcond para evitar un comportamiento inesperado de las macros %cargo_*, ya sea habilitando o deshabilitando explícitamente las pruebas.

Ejecutar pruebas

Las cajas de Rust pueden tener tres tipos diferentes de pruebas en sus conjuntos de pruebas:

  • "pruebas unitarias": estas pruebas se incluyen junto con el código fuente de la biblioteca/aplicación en el directorio src/ y pueden hacer referencia a API privadas (similares a las pruebas de "caja de vidrio").

  • "pruebas de integración": estas pruebas suelen ser archivos separados en el directorio tests/ y solo pueden confiar en la API pública del paquete probado (similar a las pruebas de "caja negra").

  • "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 foo: only run "unit tests" for binary "foo"

  • %cargo_test — --doc: only run "doctests"

  • %cargo_test — --test bar: only run "integration test" "bar"

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

%cargo_test -- --lib -- --skip foo::bar::tests::test1

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.

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.

Non-crate Rust projects

This section applies to Rust projects that are not packaged from sources on crates.io.

The most common cases are

  • applications that do not provide a library interface,

  • projects comprised of multiple crates (cargo workspaces) that may or may not be published (or useful) separately, and

  • Python packages implemented in Rust (covered below).

Packages like this 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}.

Nombrado de paquete

Packages for non-crate Rust projects MUST be named according to the generic Naming Guidelines, i.e. they MUST NOT use a rust- prefix for the source package name.

Fuentes de Paquete

The generic guidelines for referencing sources apply.

The %{crates_source} macro SHOULD NOT be used for packages like this, even when packaging a Rust crate that is also published on crates.io.

Macros de RPM

Any -a and -n flags or -f arguments that are passed to %cargo_generate_buildrequires are applied to all workspace members during dependency resolution.

The %cargo_install macro SHOULD NOT be used for packages like this. Instead, use install or cp to copy built executables or shared libraries from target/rpm/* into the buildroot explicltly, as needed.

Python projects

Python packages that use setuptools_rust or maturin to build a "native" Python extension also need to apply the generic rules for Rust packages in addition to following the Python Packaging Guidelines.

Both setuptools_rust and 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.

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.

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 compiler flags are passed to rustc, and that debuginfo is not stripped during the build process due to settings in the setuptools-rust or maturin configuration.

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

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.

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 compiler flags or cargo options that are set in the %cargo_build macro or configured by %cargo_prep.

Building with meson directly

Recent versions of meson have limited support for building crate dependencies without cargo. It is necessary to manually override crate sources with the local registry to use packaged crate dependencies.

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

The cargo-c package includes RPM macros for this functionality (%cargo_cbuild and %cargo_cinstall), which accept the same arguments as their cargo counterparts.

Macros de RPM

The process of building and installing Rust projects 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 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 only be called in the %build scriptlet unless the build is handled in another way, i.e. "cargo build" is called internally by build scripts. 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 only 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 (copying from target/rpm/*). To prevent installation of executables by this macro, the %cargo_install_bin macro can be defined to 0. To prevent installation of library sources by this macro, the %cargo_install_lib macro can be defined to 0.

  • %cargo_test: This macro MUST only 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 License tags).

  • %cargo_vendor_manifest: This macro MUST be called in the %build scriptlet after %cargo_build when building Rust projects with vendored dependencies. It writes a machine-readable list of all vendored dependencies to cargo-vendor.txt, which MUST be included as a %license file in the package that contains the statically linked executable(s).

All packages for Rust crates MUST set either %bcond check 1 or %bcond check 0. The value of this macro affects whether %cargo_generate_buildrequires includes dependencies that are required for building and running tests.

Non-crate packages can either use the %bcond check or pass the -t flag to the %cargo_generate_buildrequires macro to include test-only dependencies in BuildRequires generation.

All %cargo_* macros (except %cargo_prep and %cargo_vendor_manifest) 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, for example when enabling a different backend than the one enabled by default, and MUST NOT be used to avoid missing dependencies that are part of the "default" feature set of a crate.

When passing any of the -a or -n flags or an -f argument to a %cargo_build and / or %cargo_install macro, the same flags MUST also be passed to %cargo_license and %cargo_license_summary (if present). Otherwise, the list of generated licenses and the generated license summary will not match what is used when the application or library is compiled.

Plantillas

Non-crate Rust project

Name:           my-awesome-project
Version:        25.11.26
Release:        %autorelease
Summary:        My Awesome Rust Project

SourceLicense:  WTFPL
# FIXME: paste output of %%cargo_license_summary here
License:        %{shrink:
    WTFPL AND
    ...
}
# LICENSE.dependencies contains a full license breakdown

URL:            https://forge.example/me/my-awesome-project
Source:         %{url}/archive/v%{version}.tar.gz

BuildRequires:  cargo-rpm-macros

%description
My Awesome Rust Project.

%prep
%autosetup -p1
%cargo_prep

%generate_buildrequires
%cargo_generate_buildrequires -t

%build
%cargo_build
%{cargo_license_summary}
%{cargo_license} > LICENSE.dependencies

%install
install -Dpm 0755 target/rpm/my-awesome-cli -t %{buildroot}%{_bindir}

%check
%cargo_test

%files
%license LICENSE
%license LICENSE.dependencies
%doc README.md
%{_bindir}/my-awesome-cli

%changelog
%autochangelog

Python project

Name:           python-rustypackage
Version:        25.11.26
Release:        %autorelease
Summary:        Rusty Python Package

License:        WTFPL
URL:            https://forge.example/me/python-rustypackage
Source:         %{url}/archive/v%{version}/rustypackage-%{version}.tar.gz

BuildRequires:  python3-devel
BuildRequires:  cargo-rpm-macros

%global _description %{expand:
My Rusty Python Package.}

%description %_description

%package     -n python3-rustypackage
Summary:        %{summary}

# FIXME: paste output of %%cargo_license_summary here
License:        %{shrink:
    WTFPL AND
    ...
}
# LICENSE.dependencies contains a full license breakdown

%description -n python3-rustypackage %_description

%prep
%autosetup -n rustypackage-%{version} -p1
%cargo_prep

%generate_buildrequires
# maturin requires all dependencies to be available,
# even those for tests and features that are not enabled
%cargo_generate_buildrequires -a -t
%pyproject_buildrequires

%build
%pyproject_wheel
%{cargo_license_summary}
%{cargo_license} > LICENSE.dependencies

%install
%pyproject_install
%pyproject_save_files -l rustypackage

%check
%pyproject_check_import
# %%pytest
# %%cargo_test

%files -n python3-rustypackage -f %{pyproject_files}
%doc README.md

%changelog
%autochangelog

Verify that project license file(s) and the LICENSE.dependencies file are included in built packages as expected (rpm -qL -p <path to RPM>) when using %pyproject_save_files -l.