openSUSE:Packaging Rust Software

Jump to: navigation, search


How to package software written in Rust for openSUSE Build Service.

Maintaining/Creating packages that use Rust code

Dependencies

You should install the following packages to help build rust packages for OpenSUSE.

zypper install obs-service-cargo osc obs-service-tar obs-service-obs_scm \
    obs-service-recompress obs-service-set_version obs-service-format_spec_file \
    cargo cargo-packaging sudo

Optionally, you can install cargo lock2rpmprovides to help generate the license string.

cargo install cargo-lock2rpmprovides

If you want to cache build artefacts, as of osc-0.173.0 you can use sccache to help reduce repeat build times.

# ~/.config/osc/oscrc
sccache = 1
sccache_uri = file:///var/cache/obs_sccache.tar

Alternately you can use a network sccache

# ~/.config/osc/oscrc
sccache = 1
sccache_uri = redis://127.0.0.1:6379

Setting up the redis cache is beyond the scope of this page.

Creating the Package

You should checkout your blank package with:

osc co home:<username>/package
cd home:<username>/package

A skeleton RPM spec file for a rust package is:

Name:           hellorust
#               This will be set by osc services, that will run after this.
Version:        0.0.0
Release:        0
Summary:        A hello world with a number of the day printer
#               If you know the license, put it's SPDX string here.
#               Alternately, you can use cargo lock2rpmprovides to help generate this.
License:        Unknown
Url:            https://github.com/Firstyear/hellorust
Source0:        %{name}-%{version}.tar.zst
Source1:        vendor.tar.zst
# Pull in the latest rust/cargo toolchain
BuildRequires:  cargo
# This contains rpm macros to assist with building
BuildRequires:  cargo-packaging
# Disable this line if you wish to support all platforms.
# In most situations, you will likely only target tier1 arches for user facing components.
ExclusiveArch:  %{rust_tier1_arches}

%description
A hello world with a number of the day printer.

%prep
# The number passed to -a (a stands for "after") should be equivalent to the Source tag number
# of the vendor tarball, 1 in this case (from Source1).
%autosetup -p1 -a1
# Remove exec bits to prevent an issue in fedora shebang checking. Uncomment only if required.
# find vendor -type f -name \*.rs -exec chmod -x '{}' \;

%build
%{cargo_build}

%install
# using cargo_install (only supports bindir)
%{cargo_install}
# manual process, this can place binaries in other directories like sbindin
# install -D -d -m 0755 %{buildroot}%{_bindir}
# install -m 0755 %{_builddir}/%{name}-%{version}/target/release/%{name} %{buildroot}%{_bindir}/%{name}
 
%check
%{cargo_test}

%files
%license LICENSE-FILE
%{_bindir}/%{name}

%changelog

There are a few commented areas you’ll need to fill in and check. Note that %{cargo_build} is redundant if you use %{cargo_install} as %{cargo_install} will rebuild and install at the same time.

Some projects may need custom rust flags as build arguments, which can be set with the following variable at the head of the spec file.

%define __rustflags --cfg tokio_unstable

But next we will create a service file that allows OBS to help get our sources and bundle them for us. This should go in a file called _service

<services>
  <service mode="disabled" name="obs_scm">
    <param name="url">https://github.com/Firstyear/hellorust.git</param>
    <param name="versionformat">@PARENT_TAG@~@TAG_OFFSET@</param>
    <param name="scm">git</param>
    <param name="revision">v0.1.1</param>
    <param name="match-tag">*</param>
    <param name="versionrewrite-pattern">v(\d+\.\d+\.\d+)</param>
    <param name="versionrewrite-replacement">\1</param>
    <param name="changesgenerate">enable</param>
    <param name="changesauthor"> YOUR EMAIL HERE </param>
  </service>
  <service mode="disabled" name="tar" />
  <service mode="disabled" name="recompress">
    <param name="file">*.tar</param>
    <param name="compression">zst</param>
  </service>
  <service mode="disabled" name="set_version"/>
  <service name="cargo_vendor" mode="disabled">
     <param name="src">hellorust</param>
     <param name="compression">zst</param>
     <param name="update">true</param>
  </service>
</services>

This service file does a lot of the work for us:

  • It will fetch the sources from git, based on the version we set.
  • It will turn them into a tar.zst for us.
  • It will update the changelog for the rpm, and set the correct version in the spec file.
  • It will download our rust dependencies, and then bundle them to vendor.tar.zst.
  • It scans our project for any known vulnerabilities These come from the RustSec advisory database.

You can run this with:

osc service ra

Optionally, you can now run the lock2rpmprovides:

cd hellorust
cargo lock2rpmprovides

This will generate a license string you can copy into the spec file.

You can then add the needed sources and commit to OBS

osc add _service _servicedata cargo_config hellorust-0.1.1~git0.db340ad.tar.zst hellorust.spec vendor.tar.zst
osc ci

If you run into any issues while building your package that mention crate dependency conflicts then you may disable the automatic update of dependencies like so:

<param name="update">false</param>

to see if that fixes it. Since crates on git forges will normally only run CI over their older Cargo.lock files rather than the newest semver compatible dependencies.

From here, you can follow the How to contribute to Factory guide.

Projects with Cargo workspaces

Workspaces is a Cargo feature to build multiple projects from the same git tree. You can make the following changes to your spec to build them in the same project:

BuildRequires:  cargo-packaging >= 1.2.0
[...]
%build
%{cargo_build} --all
[...]
%install
%{cargo_install -p project1}
%{cargo_install -p project2}

Updating your Package

Once your package has been accepted, you can then update it with the following steps:

osc bco devel:project/pkgname
cd home:username:branches:devel:project/pkgname
osc service ra
osc status

Then inspect the changes, and osc rm any old files, and osc add any updated source files.

From there you can then do a build and a ci/sr

osc build
osc ci
osc results
osc sr

Patching vendored crates

Sometimes it is necessary to patch vendored crates for one reason or another, usually because of CVEs that are not yet in an upstream release. Or if you want to fix CVEs in Leap which have older versions.

Even though, theoretically, you can modify and repackage your vendor-tarball, we try to leave the vendor-tarball untouched, and work with patch-files, to make it clear what was modified by us.

This is a bit more tricky, but can be done in the following manner:

First, extract your sources- and vendor-tarballs, either manually or with

 quilt setup your.spec

or, if you are using a service-file with

 osc service run obs_scm

But note that you have to extract your vendor-tarball manually, then.

Then cd into the source-dir.

Now you can run optionally an initial

 cargo tree | less

to identify which sub(-subsub)package you need to change/bump.

Depending on what you have to do, continue with the corresponding instructions below.

Forcing newer versions of direct dependency

Assume, we have a dependency-tree like this:

 your_package v1.0
 ├── dep_lvl_1 v4.4
 ...

and you want to bump dep_lvl_1 to v4.5

For this you need your top-level Cargo.toml-file, Cargo.lock if it exists, as well as vendor/dep_lvl_1/ under some kind of version control (quilt may be unsuitable for this, since you probably don't know in advance, which files will get modified by updating the crate).

Edit your Cargo.toml-file, to make cargo use v4.5 for dep_lvl_1.

Verify with

 cargo tree | less

then run

 cargo vendor

to update the crate dep_lvl_1.

Jump to Final steps for all.

Forcing newer versions of sub-dependency

Assume, we have a dependency-tree like this:

 your_package v1.0
 ├── dep_lvl_1 v4.4
 │   ├── dep_lvl_2 v1.3  
 ...

and you want to bump dep_lvl_2 to v1.4.

For this you need your top-level Cargo.toml-file, Cargo.lock if it exists, as well as vendor/dep_lvl_1/Cargo.toml and vendor/dep_lvl_2/ under some kind of version control (quilt may be unsuitable for this, since you probably don't know in advance, which files will get modified by updating the crate).

Edit the Cargo.toml-file you want to change:

 vi vendor/dep_lvl_1/Cargo.toml

and find where dep_lvl_2 is requested. Change the version accordingly to v1.4.

Add to the toplevel Cargo.toml-file

 [patch.crates-io]
 dep_lvl_1 = { path="vendor/dep_lvl_1" }

Run

 cargo vendor

to download the new version of dep_lvl_2

Jump to Final steps for all.

Actually modifying dependency-code

Assume, we have a dependency-tree like this:

 your_package v1.0
 ├── dep_lvl_1 v4.4
 │   ├── dep_lvl_2 v1.3  
 ...

and you want to modify the code of dep_lvl_2 directly.

For this you need your top-level Cargo.toml-file, Cargo.lock if it exists, as well as the files you need to edit, e.g. vendor/dep_lvl_2/src/lib.rs under some kind of version control.

Edit the source-file you want to change:

 vi vendor/dep_lvl_2/src/lib.rs

Add to the toplevel Cargo.toml-file

 [patch.crates-io]
 dep_lvl_2 = { path="vendor/dep_lvl_2" }

Jump to Final steps for all.

Final steps for all

If you ran cargo vendor before coming here, check if you have a .cargo/config.toml-file. If not, create the directory and copy the cargo_config-file from your additional sources there.

Then put it under version control, too, and write the output of cargo vendor to .cargo/config.toml (in most cases this file will not be modified, but in rare cases it can happen, when you play with the dependency-tree. So we want to also have this modification in our patch).

Verify you have the correct versions for everything with

 cargo tree | less

Create a patch from the modifications you did (using your version control, e.g. git diff > ../modifying_vendored_crate.patch or quilt refresh), and add it to your spec-file.

References