Getting Started Packaging A Rust CLI Tool in SUSE OBS

Getting Started Packaging A Rust CLI Tool in SUSE OBS

Distribution packaging always seems like something that is really difficult or hard to do, but the SUSE Open Build Service makes it really easy to not only build packages, but to then contribute them to Tumbleweed. Not only that, OBS can also build for Fedora, CentOS and more.

Getting Started

You'll need to sign up to service - there is a sign up link on the front page of OBS

To do this you'll need a SUSE environment. Docker is an easy way to create this without having to commit to a full virtual machine / install.

    docker run \
        --security-opt=seccomp:unconfined --cap-add=SYS_PTRACE --cap-add=SYS_CHROOT --cap-add=SYS_ADMIN \
        -i -t opensuse/tumbleweed:latest /bin/sh
bash

-   NOTE: We need these extra privileges so that the osc build command
    can work due to how it uses chroots/mounts.

Inside of this we\'ll need some packages to help make the process
easier.

```bash
    zypper install obs-service-cargo_vendor osc obs-service-tar obs-service-obs_scm \
        obs-service-recompress obs-service-set_version obs-service-format_spec_file \
        obs-service-cargo_audit cargo sudo

You should also install your editor of choice in this command (docker images tend not to come with any editors!)

You'll need to configure osc, which is the CLI interface to OBS. This is done in the file [~/.config/osc/oscrc]{.title-ref}. A minimal starting configuration is:

    [general]
    # URL to access API server, e.g. https://api.opensuse.org
    # you also need a section [https://api.opensuse.org] with the credentials
    apiurl = https://api.opensuse.org
    [https://api.opensuse.org]
    user = <username>
    pass = <password>

You can check this works by using the "whois" command.

    # osc whois
    firstyear: "William Brown" <email here>

Optionally, you may install cargo lock2rpmprovides to assist with creation of the license string for your package:

    cargo install cargo-lock2rpmprovides

Packaging A Rust Project

In this example we'll use a toy Rust application I created called hellorust. Of course, feel free to choose your own project or Rust project you want to package!

  • HINT: It's best to choose binaries, not libraries to package. This is because Rust can self-manage it's dependencies, so we don't need to package every library. Neat!

First we'll create a package in our OBS home project.

    osc co home:<username>
    cd home:<username>
    osc mkpac hellorust
    cd hellorust

OBS comes with a lot of useful utilities to help create and manage sources for our project. First we'll create a skeleton RPM spec file. This should be in a file named [hellorust.spec]{.title-ref}

    %global rustflags -Clink-arg=-Wl,-z,relro,-z,now -C debuginfo=2

    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
    #               Select a group from this link:
    #               https://en.opensuse.org/openSUSE:Package_group_guidelines
    Group:          Amusements/Games/Other
    Url:            https://github.com/Firstyear/hellorust
    Source0:        %{name}-%{version}.tar.xz
    Source1:        vendor.tar.xz
    Source2:        cargo_config

    BuildRequires:  rust-packaging
    ExcludeArch:    s390 s390x ppc ppc64 ppc64le %ix86

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

    %prep
    %setup -q
    %setup -qa1
    mkdir .cargo
    cp %{SOURCE2} .cargo/config
    # Remove exec bits to prevent an issue in fedora shebang checking
    find vendor -type f -name \*.rs -exec chmod -x '{}' \;

    %build
    export RUSTFLAGS="%{rustflags}"
    cargo build --offline --release

    %install
    install -D -d -m 0755 %{buildroot}%{_bindir}

    install -m 0755 %{_builddir}/%{name}-%{version}/target/release/hellorust %{buildroot}%{_bindir}/hellorust

    %files
    %{_bindir}/hellorust

    %changelog

There are a few commented areas you'll need to fill in and check. 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">
        <!-- ✨ URL of the git repo ✨ -->
        <param name="url">https://github.com/Firstyear/hellorust.git</param>
        <param name="versionformat">@PARENT_TAG@~git@TAG_OFFSET@.%h</param>
        <param name="scm">git</param>
        <!-- ✨ The version tag or branch name from git ✨ -->
        <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>
        <!-- ✨ Your email here ✨ -->
        <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">xz</param>
      </service>
      <service mode="disabled" name="set_version"/>
      <service name="cargo_audit" mode="disabled">
          <!-- ✨ The name of the project here ✨ -->
         <param name="srcdir">hellorust</param>
      </service>
      <service name="cargo_vendor" mode="disabled">
          <!-- ✨ The name of the project here ✨ -->
         <param name="srcdir">hellorust</param>
         <param name="compression">xz</param>
      </service>

    </services>

Now this service file does a lot of the heavy lifting for us:

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

So our current work dir should look like:

    # ls -1 .
    .osc
    _service
    hellorust.spec

Now we can run [osc service ra]{.title-ref}. This will run the services in our [_service]{.title-ref} file as we mentioned. Once it's complete we'll have quite a few more files in our directory:

    # ls -1 .
    _service
    _servicedata
    cargo_config
    hellorust
    hellorust-0.1.1~git0.db340ad.obscpio
    hellorust-0.1.1~git0.db340ad.tar.xz
    hellorust.obsinfo
    hellorust.spec
    vendor.tar.xz

Inside the [hellorust]{.title-ref} folder ([home:username/hellorust/hellorust]{.title-ref}), is a checkout of our source. If you cd to that directory, you can run [cargo lock2rpmprovides]{.title-ref} which will display your license string you need:

    License: ( Apache-2.0 OR MIT ) AND ( Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT ) AND

Just add the license from the project, and then we can update our [hellorust.spec]{.title-ref} with the correct license.

    License: ( Apache-2.0 OR MIT ) AND ( Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT ) AND MPL-2.0
  • HINT: You don't need to use the emitted "provides" lines here. They are just for fedora rpms to adhere to some of their policy requirements.

Now we can build our package on our local system to test it. This may take a while to get all its build dependencies and other parts, so be patient :)

    osc build

If that completes successfully, you can now test these rpms:

    # zypper in /var/tmp/build-root/openSUSE_Tumbleweed-x86_64/home/abuild/rpmbuild/RPMS/x86_64/hellorust-0.1.1~git0.db340ad-0.x86_64.rpm
    (1/1) Installing: hellorust-0.1.1~git0.db340ad-0.x86_64  ... [done]
    # rpm -ql hellorust
    /usr/bin/hellorust
    # hellorust
    Hello, Rust! The number of the day is: 68

Next you can commit to your project. Add the files that we created:

    # osc add _service cargo_config hellorust-0.1.1~git0.db340ad.tar.xz hellorust.spec vendor.tar.xz
    # osc status
    A    _service
    ?    _servicedata
    A    cargo_config
    ?    hellorust-0.1.1~git0.db340ad.obscpio
    A    hellorust-0.1.1~git0.db340ad.tar.xz
    ?    hellorust.obsinfo
    A    hellorust.spec
    A    vendor.tar.xz
  • HINT: You DO NOT need to commit _servicedata OR hellorust-0.1.1~git0.db340ad.obscpio OR hellorust.obsinfo
    osc ci

From here, you can use your packages from your own respository, or you can forward them to OpenSUSE Tumbleweed (via Factory). You likely need to polish and add extra parts to your package for it to be accepted into Factory, but this should at least make it easier for you to start!

For more, see the how to contribute to Factory document. To submit to Leap, the package must be in Factory, then you can request it to be submitted to Leap as well.

Happy Contributing! 🦎🦀