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

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

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

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

…well, not so fast :).

Remote URL for the current branch should be set first and that’s because the git_dir_name macro determines the value of package name from that URL.

For testing purposes, we can set it up by running:

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

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 variables to be set appropriately to our needs.

Let’s try again:

$ rpkg spec
Wrote: /tmp/rpkg/simple-template-2-ck20yI/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.wtree.zf4r62
Release:    1%{?dist}
Summary:    This is a test package.

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

Source: test-project-0.0.wtree.zf4r62.tar.gz

%description
This is a test package.

%prep
%setup -q -n test-project-0.0.wtree.zf4r62

%changelog

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

You can also notice that the same suffix is present in the Source filename as well as in the parameter given to %setup -n macro. That’s because the output of git_dir_version is remembered and the subsequent rpkg macro invocations may use it as part of their own output value.

The .wtree.* 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 add simple-template.spec.rpkg
git commit -m 'first commit'

Viewing the commit:

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

Regenerating the spec file:

rpkg spec

Finally getting:

Name:       test-project
Version:    0.0.git.1.dadef2a
Release:    1%{?dist}
Summary:    This is a test package.

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

Source: test-project-0.0.git.1.dadef2a.tar.gz

%description
This is a test package.

%prep
%setup -q -n test-project-0.0.git.1.dadef2a

%changelog

You can see that the .wtree.* suffix was replaced by .git.1.dadef2a. 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 dadef2a part is the first seven characters of the full commit hash and you can use this short hash to checkout the commit:

git checkout dadef2a

This is not 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
Wrote: /tmp/rpkg/simple-template-4-7Rh6hh/simple-template.spec
Created tag: test-project-0.1-1
Wrote: /tmp/rpkg/simple-template-4-7Rh6hh/simple-template.spec

You might wonder about the same two lines Wrote: /tmp/rpkg/simple-template-4-7Rh6hh/simple-template.spec. The first line is there because the tag name is derived from the Name:, Version:, and Release: field values in the generated spec file. The second line is there because the spec needs to be regenerated after the tag creation so that it contains the latest changelog record.

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 final spec file now:

$ cat /tmp/rpkg/simple-template-4-7Rh6hh/simple-template.spec
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#dadef2a8b9554e94797a7336261192e02d5d9351:

Source: test-project-0.1.tar.gz

%description
This is a test package.

%prep
%setup -q -n test-project-0.1

%changelog
* Wed Mar 07 2018 clime <clime@redhat.com> 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 the 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.wtree.zf4657
Release:    1%{?dist}
...

The .wtree.* suffix is telling us that our working tree is dirty again after adding lead=1 and not commiting yet.

Read more about the macros and their parameters in 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-5-H5AuzX/test-project-1.0.wtree.zgwsnr.tar.gz
Wrote: /tmp/rpkg/simple-template-5-H5AuzX/simple-template.spec
Wrote: /tmp/rpkg/simple-template-5-H5AuzX/test-project-1.0.wtree.zgwsnr-1.fc27.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: packing path /home/clime/test-project
git_dir_pack: Wrote: /tmp/rpkg/simple-template-6-gtzDQX/test-project-0.1.tar.gz
Wrote: /tmp/rpkg/simple-template-6-gtzDQX/simple-template.spec
Wrote: /tmp/rpkg/simple-template-6-gtzDQX/test-project-0.1-1.fc27.src.rpm

Three files were created. The spec (you can see the relevant log message on the second line). The tarball that packs all our application sources for the current commit (on the first line). And the source rpm that bundles the generated spec file and the tarball and can be used for building an rpm installation package (the third line).

Note that the names of the tarball and of the source srpm match the tag name of the currently checked out commit as they should.

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

$ rpkg spec --sources
git_dir_pack: packing path /home/clime/test-project
git_dir_pack: Wrote: /tmp/rpkg/simple-template-7-b9rah4/test-project-0.1.tar.gz
Wrote: /tmp/rpkg/simple-template-7-b9rah4/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 calling rpkg srpm) or when explicitly asked to do it (rpkg spec --sources).

This is a useful optimization.

By the way, there is also git_dir_archive rpkg macro (which uses git archive command instead of tar to do the packing), which behaves the same way in this regard. With this macro, however, all your changes need to be committed before you can generate a tarball or a source rpm.

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