Quick start

New project

Let’s say you have decided to create a new mind-blowing application and you would like to out-source the building process from your own machine or you would like to make the latest installable version of the app always available somewhere for you and your teammates to download and debug on it.

This is what rpkg can do for you among other things thanks to its rpkg build command that can send your sources for build in a public build system called Copr (stands for “Community projects”).

To make this command work, you need to write an rpm spec file template and place it along-side your sources. If you don’t know what spec file is or you know what it is but not exactly how to write one, you can optionally read some details here but you can also just continue reading on to get the quick start you came for.

rpkg enriches the basic rpm spec files with its own syntax. Such enriched spec file or “spec file template” is then used to generate the actual rpm spec file that is then used to build an installable rpm package but this whole process is already automated for you.

The long story short, below is an example spec file template called hello_rpkg.spec.rpkg with comments included that you can use as a starting point for your own Git project. {{{ ... }}} expressions (git_dir_pack/git_dir_archive macros are the only one that have a “side effect” of building a tarball) are the above mentioned rpkg’s extensions to the basic rpm spec file syntax that make it possible to derive certain spec file parts dynamically from Git repository metadata.

hello_rpkg.spec.rpkg

# The following tag is to get correct syntax highlighting for this file in vim text editor
# vim: syntax=spec

# git_dir_name returns repository name derived from remote Git repository URL
Name:       {{{ git_dir_name }}}

# git_dir_version returns version based on commit and tag history of the Git project
Version:    {{{ git_dir_version }}}

# This can be useful later for adding downstream patches
Release:    1%{?dist}

# Basic description of the package
Summary:    Hello rpkg package.

# License. We assume GPLv2+ here.
License:    GPLv2+

# Home page of the project. Can also point to the public Git repository page.
URL:        https://pagure.io/hello_rpkg

# Detailed information about the source Git repository and the source commit
# for the created rpm package
VCS:        {{{ git_dir_vcs }}}

# git_dir_pack macro places the repository content (the source files) into a tarball
# and returns its filename. The tarball will be used to build the rpm.
Source:     {{{ git_dir_pack }}}

# More detailed description of the package
%description
This is a hello world package.

# The following four sections already describe the rpm build process itself.
# prep will extract the tarball defined as Source above and descend into it.
%prep
{{{ git_dir_setup_macro }}}

# This will invoke `make` command in the directory with the extracted sources.
%build
make

# This will copy the files generated by the `make` command above into
# the installable rpm package.
%install
make install root=%{buildroot}

# This lists all the files that are included in the rpm package and that
# are going to be installed into target system where the rpm is installed.
%files
/usr/bin/hello_rpkg

# Finally, changes from the latest release of your application are generated from
# your project's Git history. It will be empty until you make first annotated Git tag.
%changelog
{{{ git_dir_changelog }}}

For this example spec file template to be any useful, you also need the Makefile so that the make commands in %build and %install sections work correctly and some application sources from which the hello_rpkg binary will be compiled. Let’s use the following two files for that purpose:

Makefile

CC=gcc
ARGS=-Wall -g

all: main.o
        ${CC} ${ARGS} -o main main.o

main.o: main.c
        ${CC} ${ARGS} -c main.c

install:
        mkdir -p $(root)/usr/bin
        cp main $(root)/usr/bin/hello_rpkg

clean:
        rm main *.o

main.c

#include <stdio.h>

int main()
{
    printf("%s", "Hello rpkg!\n");
    return 0;
}

If you have those three files (hello_rpkg.spec.rpkg, Makefile, main.c) placed in a plain (i.e. not git-initialized) directory, then you need to invoke the following commands in that directory to properly initialize the Git repository for further rpkg operations:

git init
git remote add origin https://pagure.io/hello_rpkg.git
git add -A

But you can also just clone the repository at https://pagure.io/hello_rpkg.git where everything is already prepared for you ;).

git clone https://pagure.io/hello_rpkg.git
cd hello_rpkg

Either way, you are now ready to invoke your first rpkg command given that you have rpkg installed (see Installation).

$ rpkg local
git_dir_pack: packing path /home/clime/hello_rpkg_test
git_dir_pack: Wrote: /tmp/rpkg/hello_rpkg-7-a5z13rvo/hello_rpkg-master-dirty.tar.gz
Wrote: /tmp/rpkg/hello_rpkg-7-a5z13rvo/hello_rpkg.spec
... many lines later ...
Wrote: /tmp/rpkg/hello_rpkg-7-a5z13rvo/hello_rpkg-0.0.dirty.02lx51-1.fc31.src.rpm
Wrote: /tmp/rpkg/hello_rpkg-7-a5z13rvo/x86_64/hello_rpkg-debugsource-0.0.dirty.02lx51-1.fc31.x86_64.rpm
Wrote: /tmp/rpkg/hello_rpkg-7-a5z13rvo/x86_64/hello_rpkg-0.0.dirty.02lx51-1.fc31.x86_64.rpm
Wrote: /tmp/rpkg/hello_rpkg-7-a5z13rvo/x86_64/hello_rpkg-debuginfo-0.0.dirty.02lx51-1.fc31.x86_64.rpm
... some more no longer interesting lines ...

So this output file:

Wrote: /tmp/rpkg/hello_rpkg-7-a5z13rvo/x86_64/hello_rpkg-0.0.dirty.02lx51-1.fc31.x86_64.rpm

is the installable rpm package we were looking for.

Note that the dynamic suffixes in the file path will be different for you and if you have cloned https://pagure.io/hello_rpkg.git and ran the rpkg local command on it, you won’t even see the .dirty.<suffix> indicating that the build was done from a dirty working Git tree.

If you see .dirty.<suffix> in the rpm filename, you can optionally get rid of it by committing your changes:

$ git add -A
$ git commit # you can also use rpkg commit

If you call rpkg local now, you will get a different filename without the .dirty.<suffix> part:

Wrote: /tmp/rpkg/hello_rpkg-8-3ffakz0_/x86_64/hello_rpkg-0.0.git.1.028966b2-1.fc31.x86_64.rpm

That’s because package version generated by {{{ git_dir_version }}} rpkg macro in the spec file has changed to indicate that we are building from clean content of a git commit.

Anyway, if you want to finally install the resulting package to try your application out, you can do so with:

$ sudo rpm -i /tmp/rpkg/hello_rpkg-8-3ffakz0_/x86_64/hello_rpkg-0.0.git.1.028966b2-1.fc31.x86_64.rpm

You can now launch the application binary being placed at /usr/bin/hello_rpkg.

$ hello_rpkg
Hello rpkg!

You can uninstall the package with:

$ sudo rpm -e hello_rpkg

To actually use the public Copr service for building, we will need to additionally install copr-cli package. On Fedora systems, that would be:

$ sudo dnf install copr-cli

Then you can follow this guide to create an account and the first project on Fedora Copr instance.

Afterwards, you can send your build from your project directly into Fedora Copr with:

$ rpkg build <your-project-name>

rpkg build is a very useful command to verify that something successfully builds and to share product of your work publicly in a very easy manner but there are other features that makes rpkg useful as well.

Note

TIP: You can specify rpkg.copr_project configuration variable to be able to type only rpkg build to build a project.

For example, you can use it’s tagging functionality to create an annotated Git tag on a commit, which can, for example, signify a release-ready state of your application:

$ rpkg tag

Such annotated tag (together with any new commits) can be then pushed into remote repository with:

$ rpkg push

You can also, for example, check if the generated spec file conform to certain rpm standards with:

$ rpkg lint

Or you can also use rpkg as a DistGit client, which will allow you to store large binary files (that are not suitable to be directly tracked by Git) only as links to an external storage from which they can be pulled on demand. See rpkg sources and rpkg upload commands for that in rpkg manual pages (man rpkg).

Existing project

For an already existing project, the situation is very similar to a new project. You still need a spec file template or at least a plain spec file (yes, rpkg can work with plain spec files as well) placed somewhere in your source tree. But additionally, if you already have some Git tags created in your project and you maintain a certain format in their names, you need to decide whether to keep that format or switch to the format that rpkg natively supports, which is:

<name>-<version>-<release>

Let’s illustrate the situation on https://pagure.io/hello_rpkg_existing.git repository where already some development has happened and where some tags already exists.

git clone https://pagure.io/hello_rpkg_existing.git
cd hello_rpkg_existing

If you now display the tags, you get:

$ git tag
0.1
0.2
0.3
0.4

If you look at https://pagure.io/hello_rpkg_existing/releases, you can see that each tag signifies an application release and the name of the tag is the version for each release.

The most recent tag 0.4 is attached to the latest commit:

$ git log -1
commit 437de4913bd38b37a0a462d65e5497d9c516df5d (HEAD -> master, tag: 0.4)
Author: clime <clime@redhat.com>
Date:   Tue Jul 31 13:04:41 2018 +0200

    fix project URL

which means the latest commit in the project corresponds to version 0.4 of your application and if you generate an rpm file from that particular commit, it should contain 0.4 in its name so that later anyone can immediatelly tell from what particular code that rpm was generated.

Yet, if you invoke rpkg nvr to find out what rpkg thinks the application version for the commit is (and what it will put into the rpm name as well), you get:

$ rpkg nvr
hello_rpkg_existing-0.0.git.11.437de491-1.fc31

rpkg thinks the current version is 0.0.git.11.437de491 (see git_version on how exactly the version string is constructed) and that’s because it wasn’t able to read the current version out from just 0.4, which is the most recent tag name. rpkg can only parse tag names of the form <name>-<version>-<release> as mentioned above, which would be hello_rpkg_existing-0.4-1 in this particular case. Tag names of any other format are simply ignored.

To make rpkg start recognizing the version correctly, you can do:

$ git tag -a hello_rpkg_existing-0.4-1

This will create a tag on the latest commit (next to the already existing tag 0.4) from which rpkg can parse out the current version. -a switch specifies that the created tag should be “annotated”, which means you will be able to attach a message to it, e.g. “Initial tag for rpkg”. If you call rpkg nvr now, you will finally get:

$ rpkg nvr
hello_rpkg_existing-0.4-1.fc31

Note that you can also create tags on past commits, e.g.:

$ git tag -a hello_rpkg_existing-0.3-1 38b4e21204042b1fae03b2ff96474d00614f1010

which is useful if the latest commit in your project is not in a release-ready-yet state.

So by creating the initial tag in the format recognized by rpkg, you can make rpkg immediatelly work for your project. You will be able to create future tags simply with rpkg tag command and rpkg will keep incrementing the version for you.

But you can also decide you don’t want to adopt the rpkg’s native tag format but at the same time, you would like to use rpkg tag command for tagging. Regrettably, this is not possible at the moment, although it could be implemented in a future release.

It shouldn’t matter that much, however, because instead of rpkg tag, you can just use git tag or git tag -a to create tags in your own format. Of course, there is still the problem with git_dir_version not being able to recognize that format but you can solve it by writing your own rpkg versioning macro. See User-defined macros and you might also want to look at Version and tag generation to get a bit of theory behind versioning.

Finally, you can pick a fully manual approach where instead of using git_dir_version in an input spec file template, you will be bumping (incrementing) version in the template manually with each new release. In other words, you will need to increment value of the Version: field, commit that change, and create a tag on that commit. This is how rpkg project also did its versioning in the early times.

Multi-package project

By a multi-package project we mean a project with multiple spec files. Typical setup is to have those spec files placed in separate directories together with the associated source files. You can find an example project here: https://pagure.io/hello_rpkg_multipackage.

If you clone the project:

$ git clone https://pagure.io/hello_rpkg_multipackage

and investigate the content:

$ cd hello_rpkg_multipackage
$ tree
.
├── package1
│   ├── hello_rpkg.spec.rpkg
│   ├── main.c
│   └── Makefile
├── package2
│   ├── hello_rpkg.spec.rpkg
│   ├── main.c
│   └── Makefile
└── README.md

you will find out that apart from the README.md file, there are two subdirectories package1 and package2, each one containing a spec file, thus each forming a subpackage.

In this example, package1 and package2 are just copies of each other, which won’t be typically the case, but even though they are just copies, each subpackage will display a little bit different behavior due to the fact that they are placed in different directories and git_dir_* rpkg macros are being used in each of the spec files.

While git_dir_* macros are also recommended to be used just for simple “flat” projects as shown in the New project section in this tutorial, their main use-case is exactly the multi-package projects. They take into account their placement inside the repository tree structure and use the parent directory names to form the final package name. Also, the source archive creation by git_dir_archive and git_dir_pack macros is limited in scope to the spec file’s directory. Let us show the behavior on concrete rpkg commands:

Switching to package1 subdirectory:

$ cd package1

Getting <name>-<version>-<release>:

$ rpkg nvr
hello_rpkg_multipackage-package1-0.0.git.1.a7d38743-1.fc31

Creating source rpm:

$ rpkg srpm
git_dir_pack: archiving /home/clime/hello_rpkg_multipackage/package1:
commit a7d38743b24d1a79bcd0c513d628732890d18162 (HEAD -> master, origin/master, origin/HEAD)
Author: clime <clime@redhat.com>
Date:   Wed Aug 1 09:17:53 2018 +0200

    Initial commit
git_dir_pack: Wrote: /tmp/rpkg/hello_rpkg-9-qzsyjd8s/hello_rpkg_multipackage-package1-a7d38743.tar.gz
Wrote: /tmp/rpkg/hello_rpkg-9-qzsyjd8s/hello_rpkg.spec
Wrote: /tmp/rpkg/hello_rpkg-9-qzsyjd8s/hello_rpkg_multipackage-package1-0.0.git.1.a7d38743-1.fc31.src.rpm

Tagging package1:

$ rpkg tag
...
Created tag hello_rpkg_multipackage-package1-0.1-1

Getting <name>-<version>-<release> after tagging package1:

$ rpkg nvr
hello_rpkg_multipackage-package1-0.1-1.fc31

Switching to package2:

$ cd ../package2

Getting <name>-<version>-<release> for package2:

$ rpkg nvr
hello_rpkg_multipackage-package2-0.0.git.1.a7d38743-1.fc31

You can see how rpkg actions are scoped by the subpackages. We have tagged package1 with tag name hello_rpkg_multipackage-package1-0.1-1, which will influence names of any future generated rpms for package1 accordingly to the output of rpkg nvr. But when rpkg nvr is invoked again in the package2 subdirectory, it returns <name>-<version>-<release> completely unaffected by the tag created for package1.

To summarize, this is how you can maintain a multi-package project with rpkg - just by placing the spec files for each package into appropriately named directories and then let git_dir_* macros do their job when spec files are being renderred. Note that if you are unhappy with default way, the git_dir_* macros operate, you can tweak their behavior for each individual subpackage by passing parameters to them.

For example, let’s say that the resulting rpm names seem too long to you and you would like to shorten them by leaving out the word “multipackage”. Let’s show it on package1 only for simplicity. Open package1/hello_rpkg.spec.rpkg and replace the:

Name:       {{{ git_dir_name }}}

line with:

Name:       {{{ git_dir_name name=hello_rpkg-package1 append= }}}

Now if you save and close the file and invoke rpkg nvr, you will see that the generated name is different than before:

hello_rpkg-package1-0.0.git.1.a7d38743.dirty.02ly99-1.fc31

See Rpkg macro reference for more information about the macros and their parameters.

If you already maintain a multi-package project and you have your own tagging scheme, you can either adopt the rpkg’s native tagging scheme (<name>-<version>-<release>) by creating an initial tag manually by using git tag -a command for each subpackage or you can create your own variant of git_dir_version macro which is able to recognize your custom scheme. See Existing project section for more information on this topic.

Packed content

You can also use rpkg on a project content consisting just of spec file and tarballs, which are data the underlying rpmbuild command operates on. In this case, instead of using an archiving macro to generate an rpm source, you just use the tarball filename directly as a value of Source rpm field. It’s also good to combine this with storing the tarballs not in the Git repository directly but instead in a so-called dist-git lookaside cache that rpkg can work with (read more about dist-git).

Let’s see a concrete example of this:

$ rpkg clone prunerepo
$ cd prunerepo
$ ls
prunerepo.spec  sources

Here, we have cloned a package called prunerepo from https://src.fedoraproject.org, which is a dist-git instance rpkg operates on by default (you can change this by modifying /etc/rpkg.conf).

In the package repository, a plain spec file prunerepo.spec is present and the sources file which maintains links to the individual tarballs stored in the dist-git lookaside cache and which are also being referenced from the spec file:

$ cat sources
SHA512 (prunerepo-1.20.tar.gz) = f467f0bf70197a5caacf6245a598ea1153d0f34ac75fa81d7c0db597d90244a78e78bce35453e35833a2b69f5efa8aa089a53e30094dbb3191f9e65283286f63
$ grep '^Source' prunerepo.spec
Source0: %{name}-%{version}.tar.gz

Note

%{name}-%{version}.tar.gz is translated into final resulting filename prunerepo-1.20.tar.gz by rpm. Both %{name} and %{version} are rpm macros which reference values from Name: and Version: spec fields. These are not rpkg macros but rpm macros.

The source files are currently not present in our directory so we need to download them first before we will be able to do anything useful with them (e.g. build a source rpm).

$ rpkg sources
Downloading prunerepo-1.20.tar.gz from rpms/prunerepo at clime@pkgs.fedoraproject.org:
######################################################################## 100.0%
$ ls
prunerepo-1.20.tar.gz  prunerepo.spec  sources

Now we can use the downloaded source file prunerepo-1.20.tar.gz and, for example, build a source rpm:

$ rpkg srpm
Wrote: /tmp/rpkg/prunerepo-1-_aja0zfg/prunerepo.spec
setting SOURCE_DATE_EPOCH=1619481600
Wrote: /tmp/rpkg/prunerepo-1-_aja0zfg/prunerepo-1.20-1.fc31.src.rpm

You can add new tarballs into dist-git lookaside cache by using rpkg upload command but in case of src.fedoraproject.org, the request would need to be authenticated with Fedora (e.g. by Kerberos with kinit).

Plain directory content

In case you would like to maintain your project just locally in a plain directory (without Git), you can still use rpkg for building, linting, or verifying rpms. It is also possible to use rpkg for downloading and uploading large binary files from/to dist-git like this but you need to use rpkg --repo-path parameter to specify the file location in the dist-git’s lookaside cache.