Tagger: Add SUSETagger to support SUSE-style detached changelogs

SUSE-style RPM packaging generally prefers having the RPM changelog
detached from the main RPM spec file as a changes file. This file is
typically named <pkg>.changes (corresponding to <pkg>.spec).

At package build time in the Open Build Service, the content of the
changes file is reformatted to the RPM changelog structure and
appended to the spec file by the build system before rpmbuild
processes it.

This change adds support for this particular workflow, which is
common in communities using an instance of the Open Build Service,
such as (open)SUSE.

This was originally authored by Michael Calmer. The code was ported
to work on Python 3 and adapted for the current tito codebase
by Neal Gompa.

Co-authored-by: Michael Calmer <mc@suse.de>
Co-authored-by: Neal Gompa <ngompa13@gmail.com>
This commit is contained in:
Neal Gompa 2018-09-16 12:31:59 -04:00
parent 0942baa121
commit 9494cc13b7
4 changed files with 211 additions and 0 deletions

View file

@ -8,3 +8,4 @@ from tito.tagger.main import \
from tito.tagger.rheltagger import RHELTagger
from tito.tagger.zstreamtagger import zStreamTagger
from tito.tagger.cargobump import CargoBump
from tito.tagger.susetagger import SUSETagger

View file

@ -0,0 +1,202 @@
# Copyright (c) 2012-2016 SUSE Linux Products GmbH
# Copyright (c) 2018 Neal Gompa.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
"""
Code for tagging packages in SUSE Style.
"""
import os
import re
import shutil
import subprocess
import tempfile
import textwrap
from tito.common import (debug, run_command, get_latest_tagged_version)
from tito.compat import write, StringIO
from tito.tagger import VersionTagger
from time import strftime
class SUSETagger(VersionTagger):
"""
Tagger which is based on VersionTagger and use SUSE format of Changelog
and SUSE specific changes file:
If you want it put in tito.pros (global) or localy in build.py.props:
[buildconfig]
tagger = tito.susetagger.SUSETagger
"""
def __init__(self, config=None, keep_version=False, offline=False, user_config=None):
VersionTagger.__init__(self, config=config, keep_version=keep_version,
offline=offline, user_config=user_config)
self.today = strftime("%a %b %d %T %Z %Y")
self.changes_file_name = self.spec_file_name.replace('.spec', '.changes')
self.changes_file = os.path.join(self.full_project_dir,
self.changes_file_name)
self._new_changelog_msg = "Initial package release"
self.changelog_regex = re.compile('^\s%s\s%s(\s<%s>)?' % (self.today,
self.git_user, self.git_email.replace("+", "\+").replace(".", "\.")))
def _make_changelog(self):
"""
Create a new changelog entry in the changes, with line items from git
"""
if self._no_auto_changelog:
debug("Skipping changelog generation.")
return
# Attempt to open the file if it exists, but create it if it doesn't
try:
in_f = open(self.changes_file, 'r')
except FileNotFoundError:
new_in_f = open(self.changes_file, 'w')
new_in_f.close()
in_f = open(self.changes_file, 'r')
out_f = open(self.changes_file + ".new", 'w')
old_version = get_latest_tagged_version(self.project_name)
# don't die if this is a new package with no history
if old_version is not None:
last_tag = "%s-%s" % (self.project_name, old_version)
output = self._generate_default_changelog(last_tag)
else:
output = self._new_changelog_msg
fd, name = tempfile.mkstemp()
write(fd, "# Create your changelog entry below:\n")
header = "-------------------------------------------------------------------\n"
if self.git_email is None or (('HIDE_EMAIL' in self.user_config) and
(self.user_config['HIDE_EMAIL'] not in ['0', ''])):
header = header + "%s - %s\n\n" % (self.today, self.git_user)
else:
header = header + "%s - %s <%s>\n\n" % (self.today, self.git_user,
self.git_email)
write(fd, header)
for cmd_out in output.split("\n"):
write(fd, "- ")
write(fd, "\n ".join(textwrap.wrap(cmd_out, 77)))
write(fd, "\n")
write(fd, "\n")
if not self._accept_auto_changelog:
write(fd, "###################################################\n")
write(fd, "# These are the already existing changelog entries:\n")
write(fd, "###################################################\n")
for line in in_f.readlines():
write(fd, "#" + line)
in_f.seek(0, 0)
# Give the user a chance to edit the generated changelog:
editor = 'vi'
if "EDITOR" in os.environ:
editor = os.environ["EDITOR"]
subprocess.call([editor, name])
os.lseek(fd, 0, 0)
file = os.fdopen(fd)
for line in file.readlines():
if not line.startswith("#"):
out_f.write(line)
output = file.read()
file.close()
os.unlink(name)
for line in in_f.readlines():
out_f.write(line)
in_f.close()
out_f.close()
shutil.move(self.changes_file + ".new", self.changes_file)
def _update_changelog(self, new_version):
"""
Update the changelog with the new version.
"""
# Not thrilled about having to re-read the file here but we need to
# check for the changelog entry before making any modifications, then
# bump the version, then update the changelog.
f = open(self.changes_file, 'r')
buf = StringIO()
found_match = False
done = False
empty_line_regex = re.compile('^\s*$')
for line in f.readlines():
if not done and not found_match and self.changelog_regex.match(line):
buf.write(line)
found_match = True
elif not done and found_match and empty_line_regex.match(line):
buf.write("\n- version %s\n" % new_version)
done = True
else:
buf.write(line)
f.close()
# Write out the new file contents with our modified changelog entry:
f = open(self.changes_file, 'w')
f.write(buf.getvalue())
f.close()
buf.close()
def _update_package_metadata(self, new_version):
"""
We track package metadata in the rel-eng/packages/ directory. Each
file here stores the latest package version (for the git branch you
are on) as well as the relative path to the project's code. (from the
git root)
"""
self._clear_package_metadata()
suffix = ""
# If global config specifies a tag suffix, use it:
if self.config.has_option("globalconfig", "tag_suffix"):
suffix = self.config.get("globalconfig", "tag_suffix")
new_version_w_suffix = "%s%s" % (new_version, suffix)
# Write out our package metadata:
metadata_file = os.path.join(self.rel_eng_dir, "packages",
self.project_name)
f = open(metadata_file, 'w')
f.write("%s %s\n" % (new_version_w_suffix, self.relative_project_dir))
f.close()
# Git add it (in case it's a new file):
run_command("git add %s" % metadata_file)
run_command("git add %s" % os.path.join(self.full_project_dir,
self.spec_file_name))
if not self._no_auto_changelog:
run_command("git add %s" % os.path.join(self.full_project_dir,
self.changes_file_name))
run_command('git commit -m "Automatic commit of package ' +
'[%s] %s [%s]."' % (self.project_name, self.release_type(),
new_version_w_suffix))
tag_msg = "Tagging package [%s] version [%s] in directory [%s]." % \
(self.project_name, new_version_w_suffix,
self.relative_project_dir)
new_tag = self._get_new_tag(new_version)
run_command('git tag -m "%s" %s' % (tag_msg, new_tag))
print
print("Created tag: %s" % new_tag)
print(" View: git show HEAD")
print(" Undo: tito tag -u")
print(" Push: git push origin HEAD && git push origin %s" % new_tag)
print("or Push: git push origin HEAD && git push origin --tags")

View file

@ -65,6 +65,10 @@ patches applied within it), you can change the tagger class in
branch; if you'd prefer to do this on a per-package basis you can do so in a
package specific `tito.props`.
If you work in a OBS style git with a separate .changes file for the changelog,
change the tagger class to `tito.susetagger.SUSETagger`. The SUSETagger is based
on the VersionTagger.
-h, --help::
show this help message and exit

View file

@ -231,6 +231,10 @@ And it will push package into ruby193-rubygem-simple-navigation dist-git despite
fact that it is in /rubygem-simple-navigation directory. And project name (as taken
from spec file) is rubygem-simple-navigation.
tito.susetagger.SUSETagger::
Tagger which is based on VersionTagger and deal with SUSE / OBS specific
separate changes file and format.
EXAMPLE
-------
[buildconfig]