How to Publish your Software on Copr, Fedora’s User Repository

This is a short tutorial on how to create and maintain a Copr repository for your software in an automated fashion. It assumes some basic familiarity with Git & how to create a RPM package.

In this guide, we’ll

  • create a RPM package for a program

  • create a Copr repository and publish the program to it

  • set up automatic management of program version, package release and package changelog

  • set up automatic building of new package versions

The aim is to let you keep your software up-to-date in Copr without ever having to interact with anything other than your software’s git repository.

You can set up similar automation when packaging someone else’s program, i.e. building from a downloaded source tarball. The needed modifications are described at the end of the tutorial.

Prerequisites

The following is needed:

  1. Our program’s source in a publicly available git repository somewhere. This tutorial uses a simple example program - hellocopr - to demonstrate the process. The program and all files referenced in this guide can be found in the project’s git repository. It’s a very simple (& pointless) python program with a setuptools installer:

    requirements.txt setup.py src
    
    user@host ~/copr-tito-quickdoc % ls src/hellocopr colors.py hellocopr.py
    __init__.py ```
    +
    . A Fedora (FAS) account in order to be able to create repositories on
      Copr. This tutorial's demo repository can be found
      link:https://copr.fedorainfracloud.org/coprs/lcts/hellocopr/[here].
    
    . `tito` installed on your
      system. link:https://github.com/rpm-software-management/tito[Tito] is
      capable of a lot of advanced automation for package creation, most of which
      we won't need here. Check out its documentation to learn more.
    
    . A specfile for our program. For more information on how to create one, refer
      to xref:creating-rpm-packages.adoc[Creating RPM packages] and
      xref:create-hello-world-rpm.adoc[How to Create a GNU Hello World RPM
      Package] or adapt this tutorial's
      link:https://pagure.io/copr-tito-quickdoc/blob/master/f/doc/hellocopr.spec.annotated[annotated
      example specfile].
    
    TIP: You can follow along with this tutorial by cloning or forking the repository
    and checking out the `initial` tag. This will put the repository in the
    state just before the next step. The repo's commit history matches the steps
    followed in this tutorial.
    
    == Step 1: Creating the package using tito
    
    Copy
    link:https://pagure.io/copr-tito-quickdoc/c/00963ac9339a13eefd2ab1ca42b1f72af12d3cac?branch=master[the
    spec file] into the project's base directory. A few changes should be made
    before proceeding:
    
    . The values of `Version:` and `Release:` do not matter, since these will be
      managed by tito. It makes sense to set them to `Version: 0.0.0` and
      `Release: 0%{?dist}` to mark that this package hasn't been built yet.
    
    . tito will also handle the creation of the source tarball from the git
      repository, so change the `Source0:` URL to the filename
      `%{name}-%{version}.tar.gz` & add a comment to tell users how to get the
      tarball
    
    . The changelog can be left empty.
    +
    ``` user@host ~/copr-tito-quickdoc % cat hellocopr.spec ...  Version: 0.0.0
    Release: 0%{?dist}
    ...	  # Sources can be obtained by # git clone
        https://pagure.io/copr-tito-quickdoc # cd copr-tito-quickdoc # tito build
        --tgz Source0: %{name}-%{version}.tar.gz
    ...	  %changelog ```
    
    Commit the changes.
    
    Next, we initialize the project for use with tito.
    
    ``` user@host ~/copr-tito-quickdoc % tito init Creating tito metadata in:
    ~/copr-tito-quickdoc/.tito
       - created ~/copr-tito-quickdoc/.tito
       - wrote tito.props
       - created ~/copr-tito-quickdoc/.tito/packages
       - wrote ~/copr-tito-quickdoc/.tito/packages/.readme
    committed to git Done! ```
    
    This creates
    link:https://pagure.io/copr-tito-quickdoc/c/7a6919d3dd56943bb988a755f8233157965aa9bb?branch=master[a
    subdirectory `.tito` with some default configuration], which can be left
    unchanged for now.
    
    We can now do a test build of the package using `tito build`. Usually, tito
    will build from a tag, which we haven't created yet. However, using the
    `--test` flag, we can build from the most recent commit instead, which will
    be written to `/tmp/tito`:
    
    ``` user@host ~/copr-tito-quickdoc % tito build --rpm --test Creating output
    directory: /tmp/tito WARNING: unable to lookup latest package tag, building
    untagged test project WARNING: .tito/packages/hellocopr doesn't exist in
    git, using current directory Building package [hellocopr-0.0.0-0] Wrote:
    /tmp/tito/hellocopr-git-11.7a6919d.tar.gz ...
    
    Successfully built: /tmp/tito/hellocopr-0.0.0-0.git.11.7a6919d.fc32.src.rpm
    /tmp/tito/noarch/hellocopr-0.0.0-0.git.11.7a6919d.fc32.noarch.rpm ```
    
    Once we've fixed any issues with the package that might crop up, we can let
    tito create a package release using `tito tag`. Since we haven't set a
    proper version yet, we need to pass it to tito for the first tag:
    
    ``` user@host ~/copr-tito-quickdoc % tito tag --use-version 1.0.0 ```
    
    This will open the editor & display a pre-formatted changelog entry build up
    from all commits since the last release, which we can edit as needed. Since
    there have been none so far, the entry will just contain "- new package
    built with tito". Save the file,
    link:https://pagure.io/copr-tito-quickdoc/c/f44e81d695df669bcdb7237612baf41b80da98e0?branch=master[and
    tito will]
    
    . set the Version in the specfile to 1.0.0
    
    . set the Release in the specfile to 1
    
    . append the changelog entry to the specfile's `%changelog` section
    
    . commit the result and tag it with `<name>-<version>-<release>`,
      i.e. `hellocopr-1.0.0-1`
    +

    user@host ~/copr-tito-quickdoc % tito tag --use-version 1.0.0 Creating output directory: /tmp/tito Tagging new version of hellocopr: untagged → 1.0.0-1 Created tag: hellocopr-1.0.0-1 View: git show HEAD Undo: tito tag -u Push: git push --follow-tags origin

Push to the commits & tags to the remote using `git push --follow-tags`, and
we're ready to release the package on Copr.

== Step 2: Publishing the package in a Copr repository

. Go to https://copr.fedorainfracloud.org/ and log in. Once done, click on
  _New Project_ to start creating a repository for our program. On the
  following input mask,

.. Under _1. Project information_ -> _Project name_ set the name to what you
   want your repo to be called - since this will only contain a single package,
   it makes sense to use projectname = packagename, i.e. _hellocopr_. This is
   the only settings that cannot be changed later.

.. Under _2. Build options_ tick all distributions you want to create
   repositories for - usually all Fedora versions & maybe EPEL versions as well

.. Under _4. Other Options_ make sure that _Follow Fedora branching_ is ticked,
   this will ensure that your repository will automatically update for new
   Fedora release.

. Go to _Packages_ -> _New Package_

.. Under _1. Provide the source_, set the package name & the URL of your git
   repository

.. Under _2. How to build SRPM from the source_ select _tito_

.. Under _3. Generic package setup_ tick the box for _Auto-rebuild_

. Your package will appear in the list of packages. Hit _Rebuild_ to trigger a
  build. The following page lets you change any build options if necessary,
  we'll just use the defaults, i.e. the options we set in the previous
  step. Hit _Submit_ and Copr will build the package from the tito tag we
  created in Step 1.

Once the build has finished, you can test installing the package from Copr
by activating your repository.  ``` user@host ~/copr-tito-quickdoc % sudo
dnf copr enable <username>/hellocopr

user@host ~/copr-tito-quickdoc % sudo dnf install hellocopr ```

== Step 3: Automate package (re)-builds

Next, we want to set up Copr to automatically build a new package version
whenever we create one, so that we no longer need to log in and trigger one
manually. To achieve this, we simply need to trigger a build whenever we
push a new tag to the repository.

This requires some configuration both of your Git repository and of the Copr
project.

Configuration can be found under _Settings_ -> _Integrations_, the page also
explains the steps to configure your git repository for all common Git
forges (Pagure, Github, Gitlab & Bitbucket).

Now, to test this, let's make some changes to our program that will come in
handy for the final layer of automation and create a new release for our
software.

Currently, the example program has its version hardcoded at multiple
places.
link:https://pagure.io/copr-tito-quickdoc/c/61abf1cdf622d8c9fb4f03eb6b06c4ddc1677362?branch=master[Let's
change this] so that the version string is sourced from a single file. Which
file this is doesn't matter, but ideally the version variable should be the
only thing in it that is likely to change. In this case, we use the
previously empty `src/hellocopr/pass:[__]initpass:[__].py`. We name this new
version '1.0.1'.

Commit the changes, and create a new release with tito

user@host ~/copr-tito-quickdoc % tito tag Creating output directory: /tmp/tito Tagging new version of hellocopr: 1.0.0-1 → 1.0.1-1 Created tag: hellocopr-1.0.1-1 View: git show HEAD Undo: tito tag -u Push: git push --follow-tags origin

Note that by ommiting the `--use-version` option, tito now updates the version automatically. It does so by

. Increasing the Version's final digit by 1 - `1.0.0` -> `1.0.1`
. Resetting the Release to 1 it it isn't already.

If you want to bump to a different version, say `1.1.0`, you can do so again
by passing `--use-version`.

Push the resulting commit & tag, and if you now check your projects page on
Copr, you'll see that a new build of `hellocopr-1.0.1-1` has been triggered
by our pushing a tag.

== Step 4: Let tito manage the program version

If you check the git log, you'll find that I actually forgot to update
hellocopr's version variable to 1.0.1. We don't want that to happen
again. Luckily, since we single-source our version, we can let tito
automatically generate this file from a template.

First, copy the version source file
`src/hellocopr/pass:[__]initpass:[__].py` to
`.tito/templates/pass:[__]initpass:[__].py.template`. Then, open the
template file and replace the version string with `$version`. It also makes
sense to add a note that the file is managed by tito and should not be
edited manually.  ``` user@host ~/copr-tito-quickdoc % cat
.tito/templates/__init__.py.template ...  # This file is automatically
created from a template by tito. Do not edit it manually.

__version__ = '$version' ```

Next, add the following to `.tito/tito.props` ```
[version_template]
destination_file = src/hellocopr/__init__.py template_file =
.tito/templates/__init__.py.template ```
https://pagure.io/copr-tito-quickdoc/c/28600f6e41d5a4b60f2e47cf077f2fe2d9224e1d?branch=master[Commit
the changes]. Now, when we tag a new release, tito will take the template,
replace `$version` with whatever version was tagged, and copy the resulting
file to `src/hellocopr/pass:[__]initpass:[__].py` before updating the spec
file and commiting the changes.

We can test this by tagging a new release:

user@host ~/copr-tito-quickdoc % % tito tag Creating output directory: /tmp/tito Tagging new version of hellocopr: 1.0.1-1 → 1.0.2-1 Created tag: hellocopr-1.0.2-1 View: git show HEAD Undo: tito tag -u Push: git push --follow-tags origin

user@host ~/copr-tito-quickdoc % cat src/hellocopr/init.py …​ # This file is automatically created from a template by tito. Do not edit it manually.

version = '1.0.2' ` If you again push the tag to the remote repo, Copr will again automatically trigger a rebuild.

Release procedure in brief

From now on, updating your software in the Copr repository is as simple as

  1. Commit all changes for your new version.

  2. Perform a test build using tito build --test

  3. Tag the release with tito tag (add --use-version if necessary)

  4. Push the tag to your git repo using git push --follow-tags

and Copr will take care of the rest.

Packaging from source tarballs

You can use a similar process to manage someone elses software on Copr, i.e. build from a tarball downloaded from upstream.

To do so, the following changes need to be made to the procedure described above:

  1. Instead of the unpacked sources, download & commit the source tarball you want to package to your repository

  2. Instead of modifying the source directly, add any changes you need to make in the form of patch files. List these as PatchX: in the spec file

  3. Also in the spec file, set the Version: back to whatever version the program is at and Source0: back to the tarball URL. You can use macros like %{version} for the latter to automatically follow version changes.

  4. Modify tito’s .tito/tito.props to, one, not try to build a source tarball and two, bump the Release: instead of the Version: when tagging

    [buildconfig]
    builder = tito.builder.NoTgzBuilder tagger = tito.tagger.ReleaseTagger ```
    +
    . Don't do any tito templating
    
    The rest of the procedure stays the same. If you make changes to the package
    without changing the source, you can just tag a new release with tito. If
    you do update the source tarball, you need to update the `Version:` field
    and reset `Release:` to `0%{?dist}` before tagging.
    
    TIP: The tarball-adapted version of the project can be found in the
    `https://pagure.io/copr-tito-quickdoc/tree/foreign-sources[foreign-sources]`
    branch of the git repo.