Spec templates from scratch

To setup the test environment, run the following commands:

mkdir test-project
cd test-project
cat > simple-template.spec.rpkg <<EOF
Name:       {{{ git_dir_name }}}
Version:    {{{ git_dir_version }}}
Release:    1%{?dist}
Summary:    This is a test package.

License:    GPLv2+
URL:        https://someurl.org
VCS:        {{{ git_dir_vcs }}}

Source: {{{ git_dir_pack }}}

%description
This is a test package.

%prep
{{{ git_dir_setup_macro }}}

%changelog
{{{ git_dir_changelog }}}
EOF
git init
git add simple-template.spec.rpkg

Those commands will create a local git repository called test-project containing our first spec template called simple-template.spec.rpkg.

The last git add command is important to remember. rpkg ignores files which are not tracked by git.

We can now try to invoke rpkg to generate a real spec file from the template:

$ rpkg spec
ERROR: git_dir_name: Could not get remote URL.
git_dir_name failed with value 1.

…well, not so fast :).

Remote URL for the current branch should be set first or at least the “origin” remote should be defined. That’s because the git_dir_name macro determines the value of package name from the branch remote URL (and falls back to “origin” URL if the current branch doesn’t have a directly associated remote yet).

For testing purposes, let’s create the origin remote:

git remote add origin ssh://git@pagure.io/test-project.git

Note that we don’t really need pagure.io/test-project.git project to exist publicly for the following parts to work. We only need the local Git repository to be configured appropriately to our needs.

Let’s try again:

$ rpkg spec
Wrote: /tmp/rpkg/simple-template-2-kz8cqans/simple-template.spec

Voilà! You can see that the real spec was generated into a directory under /tmp/rpkg, which is a default output path. Name of the directory is derived from the name of the spec file and count (plus one) of already existing directories under /tmp/rpkg for that particular spec file name. There is also a dynamic suffix appended to ensure directory name uniqueness. A new directory is created for each new .spec generating operation.

$ cat /tmp/rpkg/simple-template-2-ck20yI/simple-template.spec
Name:       test-project
Version:    0.0.dirty.02mnis
Release:    1%{?dist}
Summary:    This is a test package.

License:    GPLv2+
URL:        https://someurl.org
VCS:        git+ssh://git@pagure.io/test-project.git#:

Source: test-project-master-dirty.tar.gz

%description
This is a test package.

%prep
%setup -T -b 0 -q -n test-project

%changelog

In the resulting spec file, you can also see that the Version: field value is 0.0 followed by a curious .dirty.02mnis suffix. The fact that the suffix is present means that our working tree is dirty. The 02mnis substring encodes the time of the latest uncommitted file status change.

You can also notice the dirty suffix (this time without the encoded timestamp) denoting that the source was generated from a dirty repository.

The .dirty.* suffix will disappear once we make our first commit (see below). The version will stay at 0.0, however, and it will be incremented later to 0.1 when we make our first tag.

Now let’s make the first commit in our test project and see what happens:

git commit -m 'first commit'

Viewing the commit:

$ git log --oneline
d4e72ea (HEAD -> master) first commit

Regenerating the spec file (this time just to stdout with -p switch):

$ rpkg spec -p
Name:       test-project
Version:    0.0.git.1.d4e72eab
Release:    1%{?dist}
Summary:    This is a test package.

License:    GPLv2+
URL:        https://someurl.org
VCS:        git+ssh://git@pagure.io/test-project.git#d4e72eabf843e99f4cf30b30762582f59b02f6b0:

Source: test-project-d4e72eab.tar.gz

%description
This is a test package.

%prep
%setup -T -b 0 -q -n test-project

%changelog

You can see that the .dirty.* suffix was replaced by .git.1.d4e72eab. This suffix is based on the latest commit in the currently checked out branch. The number 1 is given by number of commits from the latest git tag created for our test-project package or from the repository initialization if no such tag exists yet (our case).

The d4e72eab part is the first eight characters of the full commit hash and you can use this short hash to checkout the commit:

git checkout d4e72eab

This is not very useful at the moment because we have just made the commit and we have it checked out already in our repository but it becomes useful when you build a source rpm from the generated spec file.

That’s because the value in the spec file’s version field gets propagated into the built srpm name, which means that if you store that srpm somewhere and you will later come back to it, you will know from which commit it was built and what it contains consequently.

Now let’s examine how tagging works with rpkg because tagging becomes actually very handy when your application reaches a certain stable (feature-complete) state that you would like to share with people. We will need to assume that we have already reached that state after just the first commit in our test-project :) (which is very rare).

rpkg tag

First thing that happens is that an editor window pops up where you can edit a tag message:

- first commit
#
# Write a changelog for tag:
#   test-project-0.1-1
# Lines starting with '#' will be ignored.

The intial content of the tag message gets populated from the first lines of all commit messages since the last tag. Because we don’t have any existing tag before this one, commit messages since the repository initialization are collected and used to populate the initial message content.

You can keep this pre-generated tag message as it is, edit it in any way, or just delete it and write your own stuff but note that this message constitutes the basic information about our application release (and what have changed since the previous realease), so it is quite important.

After you are finally satisfied with your message, you can save the content and close the editor. The new tag will be generated:

$ rpkg tag
Created tag: test-project-0.1-1

The tag is created now.

Note

EXPERT INFO: When tagging takes place, rpkg sets a special variable VERSION_BUMP into the spec preprocessing environment. git_dir_version macro is aware of this variable and if it is set, an incremented version value is generated. This incremented value then gets carried over into the tag name together with the package name and release.

Let’s view the spec file now:

$ rpkg spec -p
Name:       test-project
Version:    0.1
Release:    1%{?dist}
Summary:    This is a test package.

License:    GPLv2+
URL:        https://someurl.org
VCS:        git+ssh://git@pagure.io/test-project.git#d4e72eabf843e99f4cf30b30762582f59b02f6b0:

Source: test-project-d4e72eab.tar.gz

%description
This is a test package.

%prep
%setup -T -b 0 -q -n test-project

%changelog
* Thu Apr 29 2021 clime <clime@fedoraproject.org> 0.1-1
- first commit

You can see that no dynamic version suffix has been generated into the Version: field this time. This happens every time the currently checked out commit is a tagged one.

The reason is that if you know a tag name, you can just directly pass it to git checkout:

$ git checkout test-project-0.1

and you will get back to the respective commit for which the tag was created.

You can also see that there is VCS tag containing full pseudo-URL to the given commit from which the generated spec file comes. You can extract the VCS tag also from generated srpms and rpms (if you generate some) and it is the best way how to identify the source for them.

Finally, changelog has been automatically generated for us based on the currently existing tags and their content.

If you don’t like this and you prefer editing your changelog messages directly in the source spec file instead of having them generated from tags, no problem. Just don’t use the git_dir_changelog macro and instead do your edits directly in the spec file template. Any normal text (not enclosed in {{{, }}} brace triplets) will be copied verbatim into the resulting spec file.

If you like the git_dir_changelog macro but you don’t have every single changelog message stored in a tag, you can configure it to generate changelog entries only since a certain point in history by specifying its since_tag parameter (e.g. git_dir_changelog since_tag=test-project-0.1-1).

There are plenty of parameters also for the other macros to give you that same freedom you experience when you are dealing with just plain spec files.

For example, you can increment the major version number (the first number in the version string) by setting lead parameter of git_dir_version:

Name:       {{{ git_dir_name }}}
Version:    {{{ git_dir_version lead=1 }}}
Release:    1%{?dist}
...

For our project this will now generate something like:

Name:       test-project
Version:    1.0.git.1.d4e72eab.dirty.02nl5m
Release:    1%{?dist}
...

The version suffix is telling us from what commit it originates and also that it is git work tree dirty at the moment after adding lead=1 and not commiting yet.

Read more about the macros and their parameters in Rpkg macro reference.

And of course, you can fallback to using the plain spec files (without any rpkg macros) at any time, although you will lose the advantage of the certain spec parts being generated automatically for you.

So far, we have only seen generating spec from a template but what if we want to generate a whole source package.

We can do that with rpkg srpm:

$ rpkg srpm
git_dir_pack: packing path /home/clime/test-project
git_dir_pack: Wrote: /tmp/rpkg/simple-template-9-yuq_curi/test-project-d4e72eab-dirty.tar.gz
Wrote: /tmp/rpkg/simple-template-9-yuq_curi/simple-template.spec
setting SOURCE_DATE_EPOCH=1619654400
Wrote: /tmp/rpkg/simple-template-9-yuq_curi/test-project-1.0.git.1.d4e72eab.dirty.02nl5m-1.fc31.src.rpm

Now, actually we don’t usually want to build an srpm from a dirty tree because srpm is something, which eventually gets built into a final installable rpm package.

It should be always possible to track that rpm package back to a particular commit in a git history, so that you can look at the exact sources it was built from. This becomes impossible if we build an srpm from a dirty tree. It might be still useful for local development, however.

So let’s reset to the latest commit (which is also a tagged one) and continue from there:

$ git reset --hard test-project-0.1-1
HEAD is now at dadef2a first commit
$ rpkg srpm
git_dir_pack: archiving /home/clime/test-project:
commit d4e72eabf843e99f4cf30b30762582f59b02f6b0 (HEAD, tag: test-project-0.1-1, master)
Author: clime <clime@fedoraproject.org>
Date:   Thu Apr 29 22:21:17 2021 +0200

    first commit
git_dir_pack: Wrote: /tmp/rpkg/simple-template-10-xrg677vv/test-project-d4e72eab.tar.gz
Wrote: /tmp/rpkg/simple-template-10-xrg677vv/simple-template.spec
setting SOURCE_DATE_EPOCH=1619654400
Wrote: /tmp/rpkg/simple-template-10-xrg677vv/test-project-0.1-1.fc31.src.rpm

Three files were created (see all the “Wrote:” lines). The source tarball, the generated spec file and the source rpm that bundles the generated spec file and the tarball and can be used for building an rpm installation package.

The names of the tarball and of the source rpm each contain information that can be used to track them back to the original commit. In case of srpm, it is a tag name and in case of the tarball, there is a short commit hash.

You can generate just a spec and a tarball with rpkg spec --sources command:

git_dir_pack: archiving /home/clime/test-project:
commit d4e72eabf843e99f4cf30b30762582f59b02f6b0 (HEAD, tag: test-project-0.1-1, master)
Author: clime <clime@fedoraproject.org>
Date:   Thu Apr 29 22:21:17 2021 +0200

    first commit
git_dir_pack: Wrote: /tmp/rpkg/simple-template-11-stdtnrh4/test-project-d4e72eab.tar.gz
Wrote: /tmp/rpkg/simple-template-11-stdtnrh4/simple-template.spec

In the previous examples, we could see that git_dir_pack macro generates the source tarball only when needed (e.g. when rpkg srpm is called) or when explicitly asked to do it (rpkg spec --sources) but not when rpkg spec or rpkg spec -p commands are used. This is a useful optimization.

In this tutorial, we have learned:

  • how to create a simple spec file template

  • how to generate a real rpm spec from the template

  • how the spec is automatically following git history

  • how to tag a commit and create release notes

  • how to generate a source tarball and even source rpm