From e7251f84bc17d585e260091b9efe2dade073982c Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Thu, 12 Jan 2012 15:29:03 +0200 Subject: Introduce rpm helpers Implements a new gbp.rpm module that contains functionality for e.g. parsing and editing spec files, reading src.rpm files rpm-specific packaging policy etc. Signed-off-by: Markus Lehtonen Signed-off-by: Ed Bartosh Signed-off-by: Zhang Qiang Signed-off-by: Huang Hao --- gbp/rpm/__init__.py | 962 +++++++++++++++++++++ gbp/rpm/git.py | 105 +++ gbp/rpm/lib_rpm.py | 47 + gbp/rpm/linkedlist.py | 214 +++++ gbp/rpm/policy.py | 72 ++ tests/20_test_rpm.py | 383 ++++++++ tests/data/rpm/rpmbuild/SOURCES/bar.tar.gz | Bin 0 -> 177 bytes tests/data/rpm/rpmbuild/SOURCES/foo.txt | 3 + .../data/rpm/rpmbuild/SOURCES/gbp-test-1.0.tar.bz2 | Bin 0 -> 383 bytes .../rpm/rpmbuild/SOURCES/gbp-test-native-1.0.zip | Bin 0 -> 656 bytes .../data/rpm/rpmbuild/SOURCES/gbp-test2-3.0.tar.gz | Bin 0 -> 328 bytes tests/data/rpm/rpmbuild/SOURCES/my.patch | 9 + tests/data/rpm/rpmbuild/SOURCES/my2.patch | 7 + tests/data/rpm/rpmbuild/SOURCES/my3.patch | 7 + tests/data/rpm/rpmbuild/SPECS/gbp-test-native.spec | 34 + .../data/rpm/rpmbuild/SPECS/gbp-test-native2.spec | 35 + tests/data/rpm/rpmbuild/SPECS/gbp-test.spec | 42 + tests/data/rpm/rpmbuild/SPECS/gbp-test2.spec | 60 ++ tests/data/rpm/specs/gbp-test-native.spec | 1 + tests/data/rpm/specs/gbp-test-native2.spec | 1 + tests/data/rpm/specs/gbp-test-quirks.spec | 30 + tests/data/rpm/specs/gbp-test-reference.spec | 43 + tests/data/rpm/specs/gbp-test-reference2.spec | 47 + tests/data/rpm/specs/gbp-test-tags.spec | 74 ++ .../data/rpm/specs/gbp-test-updates-reference.spec | 44 + tests/data/rpm/specs/gbp-test-updates.spec | 49 ++ tests/data/rpm/specs/gbp-test.spec | 1 + tests/data/rpm/specs/gbp-test2-reference.spec | 61 ++ tests/data/rpm/specs/gbp-test2-reference2.spec | 68 ++ tests/data/rpm/specs/gbp-test2.spec | 1 + 30 files changed, 2400 insertions(+) create mode 100644 gbp/rpm/__init__.py create mode 100644 gbp/rpm/git.py create mode 100644 gbp/rpm/lib_rpm.py create mode 100644 gbp/rpm/linkedlist.py create mode 100644 gbp/rpm/policy.py create mode 100644 tests/20_test_rpm.py create mode 100644 tests/data/rpm/rpmbuild/SOURCES/bar.tar.gz create mode 100644 tests/data/rpm/rpmbuild/SOURCES/foo.txt create mode 100644 tests/data/rpm/rpmbuild/SOURCES/gbp-test-1.0.tar.bz2 create mode 100644 tests/data/rpm/rpmbuild/SOURCES/gbp-test-native-1.0.zip create mode 100644 tests/data/rpm/rpmbuild/SOURCES/gbp-test2-3.0.tar.gz create mode 100644 tests/data/rpm/rpmbuild/SOURCES/my.patch create mode 100644 tests/data/rpm/rpmbuild/SOURCES/my2.patch create mode 100644 tests/data/rpm/rpmbuild/SOURCES/my3.patch create mode 100644 tests/data/rpm/rpmbuild/SPECS/gbp-test-native.spec create mode 100644 tests/data/rpm/rpmbuild/SPECS/gbp-test-native2.spec create mode 100644 tests/data/rpm/rpmbuild/SPECS/gbp-test.spec create mode 100644 tests/data/rpm/rpmbuild/SPECS/gbp-test2.spec create mode 120000 tests/data/rpm/specs/gbp-test-native.spec create mode 120000 tests/data/rpm/specs/gbp-test-native2.spec create mode 100644 tests/data/rpm/specs/gbp-test-quirks.spec create mode 100644 tests/data/rpm/specs/gbp-test-reference.spec create mode 100644 tests/data/rpm/specs/gbp-test-reference2.spec create mode 100644 tests/data/rpm/specs/gbp-test-tags.spec create mode 100644 tests/data/rpm/specs/gbp-test-updates-reference.spec create mode 100644 tests/data/rpm/specs/gbp-test-updates.spec create mode 120000 tests/data/rpm/specs/gbp-test.spec create mode 100644 tests/data/rpm/specs/gbp-test2-reference.spec create mode 100644 tests/data/rpm/specs/gbp-test2-reference2.spec create mode 120000 tests/data/rpm/specs/gbp-test2.spec diff --git a/gbp/rpm/__init__.py b/gbp/rpm/__init__.py new file mode 100644 index 00000000..87f82ffa --- /dev/null +++ b/gbp/rpm/__init__.py @@ -0,0 +1,962 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2006,2007 Guido Guenther +# (C) 2012 Intel Corporation +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +"""provides some rpm source package related helpers""" + +import commands +import sys +import os +import re +import tempfile +import glob +import shutil as shutil +from optparse import OptionParser +from collections import defaultdict + +import gbp.command_wrappers as gbpc +from gbp.errors import GbpError +from gbp.git import GitRepositoryError +from gbp.patch_series import (PatchSeries, Patch) +import gbp.log +from gbp.pkg import (UpstreamSource, compressor_opts, parse_archive_filename) +from gbp.rpm.policy import RpmPkgPolicy +from gbp.rpm.linkedlist import LinkedList +from gbp.rpm.lib_rpm import librpm, get_librpm_log + + +class NoSpecError(Exception): + """Spec file parsing error""" + pass + +class MacroExpandError(Exception): + """Macro expansion in spec file failed""" + pass + + +class RpmUpstreamSource(UpstreamSource): + """Upstream source class for RPM packages""" + def __init__(self, name, unpacked=None, **kwargs): + super(RpmUpstreamSource, self).__init__(name, + unpacked, + RpmPkgPolicy, + **kwargs) + + +class SrcRpmFile(object): + """Keeps all needed data read from a source rpm""" + def __init__(self, srpmfile): + # Do not required signed packages to be able to import + ts_vsflags = (librpm.RPMVSF_NOMD5HEADER | librpm.RPMVSF_NORSAHEADER | + librpm.RPMVSF_NOSHA1HEADER | librpm.RPMVSF_NODSAHEADER | + librpm.RPMVSF_NOMD5 | librpm.RPMVSF_NORSA | + librpm.RPMVSF_NOSHA1 | librpm.RPMVSF_NODSA) + srpmfp = open(srpmfile) + self.rpmhdr = librpm.ts(vsflags=ts_vsflags).hdrFromFdno(srpmfp.fileno()) + srpmfp.close() + self.srpmfile = os.path.abspath(srpmfile) + + @property + def version(self): + """Get the (downstream) version of the RPM package""" + version = dict(upstreamversion = self.rpmhdr[librpm.RPMTAG_VERSION], + release = self.rpmhdr[librpm.RPMTAG_RELEASE]) + if self.rpmhdr[librpm.RPMTAG_EPOCH] is not None: + version['epoch'] = str(self.rpmhdr[librpm.RPMTAG_EPOCH]) + return version + + @property + def name(self): + """Get the name of the RPM package""" + return self.rpmhdr[librpm.RPMTAG_NAME] + + @property + def upstreamversion(self): + """Get the upstream version of the RPM package""" + return self.rpmhdr[librpm.RPMTAG_VERSION] + + @property + def packager(self): + """Get the packager of the RPM package""" + return self.rpmhdr[librpm.RPMTAG_PACKAGER] + + def unpack(self, dest_dir): + """ + Unpack the source rpm to tmpdir. + Leave the cleanup to the caller in case of an error. + """ + gbpc.RunAtCommand('rpm2cpio', + [self.srpmfile, '|', 'cpio', '-id'], + shell=True)(dir=dest_dir) + + +class SpecFile(object): + """Class for parsing/modifying spec files""" + tag_re = re.compile(r'^(?P[a-z]+)(?P[0-9]+)?\s*:\s*' + '(?P\S(.*\S)?)\s*$', flags=re.I) + directive_re = re.compile(r'^%(?P[a-z]+)(?P[0-9]+)?' + '(\s+(?P.*))?$', flags=re.I) + gbptag_re = re.compile(r'^\s*#\s*gbp-(?P[a-z-]+)' + '(\s*:\s*(?P\S.*))?$', flags=re.I) + # Here "sections" stand for all scripts, scriptlets and other directives, + # but not macros + section_identifiers = ('package', 'description', 'prep', 'build', 'install', + 'clean', 'check', 'pre', 'preun', 'post', 'postun', 'verifyscript', + 'files', 'changelog', 'triggerin', 'triggerpostin', 'triggerun', + 'triggerpostun') + + def __init__(self, filename=None, filedata=None): + + self._content = LinkedList() + + # Check args: only filename or filedata can be given, not both + if filename is None and filedata is None: + raise NoSpecError("No filename or raw data given for parsing!") + elif filename and filedata: + raise NoSpecError("Both filename and raw data given, don't know " + "which one to parse!") + elif filename: + # Load spec file into our special data structure + self.specfile = os.path.basename(filename) + self.specdir = os.path.dirname(os.path.abspath(filename)) + try: + with open(filename) as spec_file: + for line in spec_file.readlines(): + self._content.append(line) + except IOError as err: + raise NoSpecError("Unable to read spec file: %s" % err) + else: + self.specfile = None + self.specdir = None + for line in filedata.splitlines(): + self._content.append(line + '\n') + + # Use rpm-python to parse the spec file content + self._filtertags = ("excludearch", "excludeos", "exclusivearch", + "exclusiveos","buildarch") + self._listtags = self._filtertags + ('source', 'patch', + 'requires', 'conflicts', 'recommends', + 'suggests', 'supplements', 'enhances', + 'provides', 'obsoletes', 'buildrequires', + 'buildconflicts', 'buildrecommends', + 'buildsuggests', 'buildsupplements', + 'buildenhances', 'collections', + 'nosource', 'nopatch') + self._specinfo = self._parse_filtered_spec(self._filtertags) + + # Other initializations + source_header = self._specinfo.packages[0].header + self.name = source_header[librpm.RPMTAG_NAME] + self.upstreamversion = source_header[librpm.RPMTAG_VERSION] + self.release = source_header[librpm.RPMTAG_RELEASE] + # rpm-python returns epoch as 'long', convert that to string + self.epoch = str(source_header[librpm.RPMTAG_EPOCH]) \ + if source_header[librpm.RPMTAG_EPOCH] != None else None + self.packager = source_header[librpm.RPMTAG_PACKAGER] + self._tags = {} + self._special_directives = defaultdict(list) + self._gbp_tags = defaultdict(list) + + # Parse extra info from spec file + self._parse_content() + + # Find 'Packager' tag. Needed to circumvent a bug in python-rpm where + # spec.sourceHeader[librpm.RPMTAG_PACKAGER] is not reset when a new spec + # file is parsed + if 'packager' not in self._tags: + self.packager = None + + self.orig_src = self._guess_orig_file() + + def _parse_filtered_spec(self, skip_tags): + """Parse a filtered spec file in rpm-python""" + skip_tags = [tag.lower() for tag in skip_tags] + with tempfile.NamedTemporaryFile(prefix='gbp') as filtered: + filtered.writelines(str(line) for line in self._content + if str(line).split(":")[0].strip().lower() not in skip_tags) + filtered.flush() + try: + # Parse two times to circumvent a rpm-python problem where + # macros are not expanded if used before their definition + librpm.spec(filtered.name) + return librpm.spec(filtered.name) + except ValueError as err: + rpmlog = get_librpm_log() + gbp.log.debug("librpm log:\n %s" % + "\n ".join(rpmlog)) + raise GbpError("RPM error while parsing %s: %s (%s)" % + (self.specfile, err, rpmlog[-1])) + + @property + def version(self): + """Get the (downstream) version""" + version = dict(upstreamversion = self.upstreamversion, + release = self.release) + if self.epoch != None: + version['epoch'] = self.epoch + return version + + @property + def specpath(self): + """Get the dir/filename""" + return os.path.join(self.specdir, self.specfile) + + @property + def ignorepatches(self): + """Get numbers of ignored patches as a sorted list""" + if 'ignore-patches' in self._gbp_tags: + data = self._gbp_tags['ignore-patches'][-1]['args'].split() + return sorted([int(num) for num in data]) + return [] + + def _patches(self): + """Get all patch tags as a dict""" + if 'patch' not in self._tags: + return {} + return {patch['num']: patch for patch in self._tags['patch']['lines']} + + def _sources(self): + """Get all source tags as a dict""" + if 'source' not in self._tags: + return {} + return {src['num']: src for src in self._tags['source']['lines']} + + def sources(self): + """Get all source tags as a dict""" + return {src['num']: src['linevalue'] + for src in self._sources().values()} + + def _macro_replace(self, matchobj): + macro_dict = {'name': self.name, + 'version': self.upstreamversion, + 'release': self.release} + + if matchobj.group(2) in macro_dict: + return macro_dict[matchobj.group(2)] + raise MacroExpandError("Unknown macro '%s'" % matchobj.group(0)) + + def macro_expand(self, text): + """ + Expand the rpm macros (that gbp knows of) in the given text. + + @param text: text to check for macros + @type text: C{str} + @return: text with macros expanded + @rtype: C{str} + """ + # regexp to match '%{macro}' and '%macro' + macro_re = re.compile(r'%({)?(?P[a-z_][a-z0-9_]*)(?(1)})', flags=re.I) + return macro_re.sub(self._macro_replace, text) + + def write_spec_file(self): + """ + Write, possibly updated, spec to disk + """ + with open(os.path.join(self.specdir, self.specfile), 'w') as spec_file: + for line in self._content: + spec_file.write(str(line)) + + def _parse_tag(self, lineobj): + """Parse tag line""" + + line = str(lineobj) + + matchobj = self.tag_re.match(line) + if not matchobj: + return False + + tagname = matchobj.group('name').lower() + tagnum = int(matchobj.group('num')) if matchobj.group('num') else None + # 'Source:' tags + if tagname == 'source': + tagnum = 0 if tagnum is None else tagnum + # 'Patch:' tags + elif tagname == 'patch': + tagnum = -1 if tagnum is None else tagnum + + # Record all tag locations + try: + header = self._specinfo.packages[0].header + tagvalue = header[getattr(librpm, 'RPMTAG_%s' % tagname.upper())] + except AttributeError: + tagvalue = None + # We don't support "multivalue" tags like "Provides:" or "SourceX:" + # Rpm python doesn't support many of these, thus the explicit list + if type(tagvalue) is int or type(tagvalue) is long: + tagvalue = str(tagvalue) + elif type(tagvalue) is list or tagname in self._listtags: + tagvalue = None + elif not tagvalue: + # Rpm python doesn't give the following, for reason or another + if tagname not in ('buildroot', 'autoprov', 'autoreq', + 'autoreqprov') + self._filtertags: + gbp.log.warn("BUG: '%s:' tag not found by rpm" % tagname) + tagvalue = matchobj.group('value') + linerecord = {'line': lineobj, + 'num': tagnum, + 'linevalue': matchobj.group('value')} + if tagname in self._tags: + self._tags[tagname]['value'] = tagvalue + self._tags[tagname]['lines'].append(linerecord) + else: + self._tags[tagname] = {'value': tagvalue, 'lines': [linerecord]} + + return tagname + + @staticmethod + def _patch_macro_opts(args): + """Parse arguments of the '%patch' macro""" + + patchparser = OptionParser() + patchparser.add_option("-p", dest="strip") + patchparser.add_option("-s", dest="silence") + patchparser.add_option("-P", dest="patchnum") + patchparser.add_option("-b", dest="backup") + patchparser.add_option("-E", dest="removeempty") + arglist = args.split() + return patchparser.parse_args(arglist)[0] + + @staticmethod + def _setup_macro_opts(args): + """Parse arguments of the '%setup' macro""" + + setupparser = OptionParser() + setupparser.add_option("-n", dest="name") + setupparser.add_option("-c", dest="create_dir", action="store_true") + setupparser.add_option("-D", dest="no_delete_dir", action="store_true") + setupparser.add_option("-T", dest="no_unpack_default", + action="store_true") + setupparser.add_option("-b", dest="unpack_before") + setupparser.add_option("-a", dest="unpack_after") + setupparser.add_option("-q", dest="quiet", action="store_true") + arglist = args.split() + return setupparser.parse_args(arglist)[0] + + def _parse_directive(self, lineobj): + """Parse special directive/scriptlet/macro lines""" + + line = str(lineobj) + matchobj = self.directive_re.match(line) + if not matchobj: + return None + + directivename = matchobj.group('name') + # '%patch' macros + directiveid = None + if directivename == 'patch': + opts = self._patch_macro_opts(matchobj.group('args')) + if matchobj.group('num'): + directiveid = int(matchobj.group('num')) + elif opts.patchnum: + directiveid = int(opts.patchnum) + else: + directiveid = -1 + + # Record special directive/scriptlet/macro locations + if directivename in self.section_identifiers + ('setup', 'patch'): + linerecord = {'line': lineobj, + 'id': directiveid, + 'args': matchobj.group('args')} + self._special_directives[directivename].append(linerecord) + return directivename + + def _parse_gbp_tag(self, linenum, lineobj): + """Parse special git-buildpackage tags""" + + line = str(lineobj) + matchobj = self.gbptag_re.match(line) + if matchobj: + gbptagname = matchobj.group('name').lower() + if gbptagname not in ('ignore-patches', 'patch-macros'): + gbp.log.info("Found unrecognized Gbp tag on line %s: '%s'" % + (linenum, line)) + if matchobj.group('args'): + args = matchobj.group('args').strip() + else: + args = None + record = {'line': lineobj, 'args': args} + self._gbp_tags[gbptagname].append(record) + return gbptagname + + return None + + def _parse_content(self): + """ + Go through spec file content line-by-line and (re-)parse info from it + """ + in_preamble = True + for linenum, lineobj in enumerate(self._content): + matched = False + if in_preamble: + if self._parse_tag(lineobj): + continue + matched = self._parse_directive(lineobj) + if matched: + if matched in self.section_identifiers: + in_preamble = False + continue + self._parse_gbp_tag(linenum, lineobj) + + # Update sources info (basically possible macros expanded by rpm) + # And, double-check that we parsed spec content correctly + patches = self._patches() + sources = self._sources() + for name, num, typ in self._specinfo.sources: + # workaround rpm parsing bug + if typ == 1 or typ == 9: + if num in sources: + sources[num]['linevalue'] = name + else: + gbp.log.err("BUG: failed to parse all 'Source' tags!") + elif typ == 2 or typ == 10: + # Patch tag without any number defined is treated by RPM as + # having number (2^31-1), we use number -1 + if num >= pow(2,30): + num = -1 + if num in patches: + patches[num]['linevalue'] = name + else: + gbp.log.err("BUG: failed to parse all 'Patch' tags!") + + def _delete_tag(self, tag, num): + """Delete a tag""" + key = tag.lower() + tagname = '%s%s' % (tag, num) if num is not None else tag + if key not in self._tags: + gbp.log.warn("Trying to delete non-existent tag '%s:'" % tag) + return None + + sparedlines = [] + prev = None + for line in self._tags[key]['lines']: + if line['num'] == num: + gbp.log.debug("Removing '%s:' tag from spec" % tagname) + prev = self._content.delete(line['line']) + else: + sparedlines.append(line) + self._tags[key]['lines'] = sparedlines + if not self._tags[key]['lines']: + self._tags.pop(key) + return prev + + def _set_tag(self, tag, num, value, insertafter): + """Set a tag value""" + key = tag.lower() + tagname = '%s%s' % (tag, num) if num is not None else tag + value = value.strip() + if not value: + raise GbpError("Cannot set empty value to '%s:' tag" % tag) + + # Check type of tag, we don't support values for 'multivalue' tags + try: + header = self._specinfo.packages[0].header + tagvalue = header[getattr(librpm, 'RPMTAG_%s' % tagname.upper())] + except AttributeError: + tagvalue = None + tagvalue = None if type(tagvalue) is list else value + + # Try to guess the correct indentation from the previous or next tag + indent_re = re.compile(r'^([a-z]+([0-9]+)?\s*:\s*)', flags=re.I) + match = indent_re.match(str(insertafter)) + if not match: + match = indent_re.match(str(insertafter.next)) + indent = 12 if not match else len(match.group(1)) + text = '%-*s%s\n' % (indent, '%s:' % tagname, value) + if key in self._tags: + self._tags[key]['value'] = tagvalue + for line in reversed(self._tags[key]['lines']): + if line['num'] == num: + gbp.log.debug("Updating '%s:' tag in spec" % tagname) + line['line'].set_data(text) + line['linevalue'] = value + return line['line'] + + gbp.log.debug("Adding '%s:' tag after '%s...' line in spec" % + (tagname, str(insertafter)[0:20])) + line = self._content.insert_after(insertafter, text) + linerec = {'line': line, 'num': num, 'linevalue': value} + if key in self._tags: + self._tags[key]['lines'].append(linerec) + else: + self._tags[key] = {'value': tagvalue, 'lines': [linerec]} + return line + + def set_tag(self, tag, num, value, insertafter=None): + """Update a tag in spec file content""" + key = tag.lower() + tagname = '%s%s' % (tag, num) if num is not None else tag + if key in ('patch', 'vcs'): + if key in self._tags: + insertafter = key + elif not insertafter in self._tags: + insertafter = 'name' + after_line = self._tags[insertafter]['lines'][-1]['line'] + if value: + self._set_tag(tag, num, value, after_line) + elif key in self._tags: + self._delete_tag(tag, num) + else: + raise GbpError("Setting '%s:' tag not supported" % tagname) + + def _delete_special_macro(self, name, identifier): + """Delete a special macro line in spec file content""" + if name != 'patch': + raise GbpError("Deleting '%s:' macro not supported" % name) + + key = name.lower() + fullname = '%%%s%s' % (name, identifier) + sparedlines = [] + prev = None + for line in self._special_directives[key]: + if line['id'] == identifier: + gbp.log.debug("Removing '%s' macro from spec" % fullname) + prev = self._content.delete(line['line']) + else: + sparedlines.append(line) + self._special_directives[key] = sparedlines + if not prev: + gbp.log.warn("Tried to delete non-existent macro '%s'" % fullname) + return prev + + def _set_special_macro(self, name, identifier, args, insertafter): + """Update a special macro line in spec file content""" + key = name.lower() + fullname = '%%%s%s' % (name, identifier) + if key != 'patch': + raise GbpError("Setting '%s' macro not supported" % name) + + updated = 0 + text = "%%%s%d %s\n" % (name, identifier, args) + for line in self._special_directives[key]: + if line['id'] == identifier: + gbp.log.debug("Updating '%s' macro in spec" % fullname) + line['args'] = args + line['line'].set_data(text) + ret = line['line'] + updated += 1 + if not updated: + gbp.log.debug("Adding '%s' macro after '%s...' line in spec" % + (fullname, str(insertafter)[0:20])) + ret = self._content.insert_after(insertafter, text) + linerec = {'line': ret, 'id': identifier, 'args': args} + self._special_directives[key].append(linerec) + return ret + + def _set_section(self, name, text): + """Update/create a complete section in spec file.""" + if name not in self.section_identifiers: + raise GbpError("Not a valid section directive: '%s'" % name) + # Delete section, if it exists + if name in self._special_directives: + if len(self._special_directives[name]) > 1: + raise GbpError("Multiple %%%s sections found, don't know " + "which to update" % name) + line = self._special_directives[name][0]['line'] + gbp.log.debug("Removing content of %s section" % name) + while line.next: + match = self.directive_re.match(str(line.next)) + if match and match.group('name') in self.section_identifiers: + break + self._content.delete(line.next) + else: + gbp.log.debug("Adding %s section to the end of spec file" % name) + line = self._content.append('%%%s\n' % name) + linerec = {'line': line, 'id': None, 'args': None} + self._special_directives[name] = [linerec] + # Add new lines + gbp.log.debug("Updating content of %s section" % name) + for linetext in text.splitlines(): + line = self._content.insert_after(line, linetext + '\n') + + def set_changelog(self, text): + """Update or create the %changelog section""" + self._set_section('changelog', text) + + def get_changelog(self): + """Get the %changelog section""" + text = '' + if 'changelog' in self._special_directives: + line = self._special_directives['changelog'][0]['line'] + while line.next: + line = line.next + match = self.directive_re.match(str(line)) + if match and match.group('name') in self.section_identifiers: + break + text += str(line) + return text + + def update_patches(self, patches, commands): + """Update spec with new patch tags and patch macros""" + # Remove non-ignored patches + tag_prev = None + macro_prev = None + ignored = self.ignorepatches + # Remove 'Patch:̈́' tags + for tag in self._patches().values(): + if not tag['num'] in ignored: + tag_prev = self._delete_tag('patch', tag['num']) + # Remove a preceding comment if it seems to originate from GBP + if re.match("^\s*#.*patch.*auto-generated", + str(tag_prev), flags=re.I): + tag_prev = self._content.delete(tag_prev) + + # Remove '%patch:' macros + for macro in self._special_directives['patch']: + if not macro['id'] in ignored: + macro_prev = self._delete_special_macro('patch', macro['id']) + # Remove surrounding if-else + macro_next = macro_prev.next + if (str(macro_prev).startswith('%if') and + str(macro_next).startswith('%endif')): + self._content.delete(macro_next) + macro_prev = self._content.delete(macro_prev) + + # Remove a preceding comment line if it ends with '.patch' or + # '.diff' plus an optional compression suffix + if re.match("^\s*#.+(patch|diff)(\.(gz|bz2|xz|lzma))?\s*$", + str(macro_prev), flags=re.I): + macro_prev = self._content.delete(macro_prev) + + if len(patches) == 0: + return + + # Determine where to add Patch tag lines + if tag_prev: + gbp.log.debug("Adding 'Patch' tags in place of the removed tags") + tag_line = tag_prev + elif 'patch' in self._tags: + gbp.log.debug("Adding new 'Patch' tags after the last 'Patch' tag") + tag_line = self._tags['patch']['lines'][-1]['line'] + elif 'source' in self._tags: + gbp.log.debug("Didn't find any old 'Patch' tags, adding new " + "patches after the last 'Source' tag.") + tag_line = self._tags['source']['lines'][-1]['line'] + else: + gbp.log.debug("Didn't find any old 'Patch' or 'Source' tags, " + "adding new patches after the last 'Name' tag.") + tag_line = self._tags['name']['lines'][-1]['line'] + + # Determine where to add %patch macro lines + if 'patch-macros' in self._gbp_tags: + gbp.log.debug("Adding '%patch' macros after the start marker") + macro_line = self._gbp_tags['patch-macros'][-1]['line'] + elif macro_prev: + gbp.log.debug("Adding '%patch' macros in place of the removed " + "macros") + macro_line = macro_prev + elif self._special_directives['patch']: + gbp.log.debug("Adding new '%patch' macros after the last existing" + "'%patch' macro") + macro_line = self._special_directives['patch'][-1]['line'] + elif self._special_directives['setup']: + gbp.log.debug("Didn't find any old '%patch' macros, adding new " + "patches after the last '%setup' macro") + macro_line = self._special_directives['setup'][-1]['line'] + elif self._special_directives['prep']: + gbp.log.warn("Didn't find any old '%patch' or '%setup' macros, " + "adding new patches directly after '%prep' directive") + macro_line = self._special_directives['prep'][-1]['line'] + else: + raise GbpError("Couldn't determine where to add '%patch' macros") + + startnum = sorted(ignored)[-1] + 1 if ignored else 0 + gbp.log.debug("Starting autoupdate patch numbering from %s" % startnum) + # Add a comment indicating gbp generated patch tags + comment_text = "# Patches auto-generated by git-buildpackage:\n" + tag_line = self._content.insert_after(tag_line, comment_text) + for ind, patch in enumerate(patches): + cmds = commands[patch] if patch in commands else {} + patchnum = startnum + ind + tag_line = self._set_tag("Patch", patchnum, patch, tag_line) + # Add '%patch' macro and a preceding comment line + comment_text = "# %s\n" % patch + macro_line = self._content.insert_after(macro_line, comment_text) + macro_line = self._set_special_macro('patch', patchnum, '-p1', + macro_line) + for cmd, args in cmds.iteritems(): + if cmd in ('if', 'ifarch'): + self._content.insert_before(macro_line, '%%%s %s\n' % + (cmd, args)) + macro_line = self._content.insert_after(macro_line, + '%endif\n') + # We only support one command per patch, for now + break + + def patchseries(self, unapplied=False, ignored=False): + """Return non-ignored patches of the RPM as a gbp patchseries""" + series = PatchSeries() + if 'patch' in self._tags: + tags = self._patches() + applied = [] + for macro in self._special_directives['patch']: + if macro['id'] in tags: + applied.append((macro['id'], macro['args'])) + ignored = set() if ignored else set(self.ignorepatches) + + # Put all patches that are applied first in the series + for num, args in applied: + if num not in ignored: + opts = self._patch_macro_opts(args) + strip = int(opts.strip) if opts.strip else 0 + filename = os.path.basename(tags[num]['linevalue']) + series.append(Patch(os.path.join(self.specdir, filename), + strip=strip)) + # Finally, append all unapplied patches to the series, if requested + if unapplied: + applied_nums = set([num for num, _args in applied]) + unapplied = set(tags.keys()).difference(applied_nums) + for num in sorted(unapplied): + if num not in ignored: + filename = os.path.basename(tags[num]['linevalue']) + series.append(Patch(os.path.join(self.specdir, + filename), strip=0)) + return series + + def _guess_orig_prefix(self, orig): + """Guess prefix for the orig file""" + # Make initial guess about the prefix in the archive + filename = orig['filename'] + name, version = RpmPkgPolicy.guess_upstream_src_version(filename) + if name and version: + prefix = "%s-%s/" % (name, version) + else: + prefix = orig['filename_base'] + "/" + + # Refine our guess about the prefix + for macro in self._special_directives['setup']: + args = macro['args'] + opts = self._setup_macro_opts(args) + srcnum = None + if opts.no_unpack_default: + if opts.unpack_before: + srcnum = int(opts.unpack_before) + elif opts.unpack_after: + srcnum = int(opts.unpack_after) + else: + srcnum = 0 + if srcnum == orig['num']: + if opts.create_dir: + prefix = '' + elif opts.name: + try: + prefix = self.macro_expand(opts.name) + '/' + except MacroExpandError as err: + gbp.log.warn("Couldn't determine prefix from %%setup "\ + "macro (%s). Using filename base as a " \ + "fallback" % err) + prefix = orig['filename_base'] + '/' + else: + # RPM default + prefix = "%s-%s/" % (self.name, self.upstreamversion) + break + return prefix + + def _guess_orig_file(self): + """ + Try to guess the name of the primary upstream/source archive. + Returns a dict with all the relevant information. + """ + orig = None + sources = self.sources() + for num, filename in sorted(sources.iteritems()): + src = {'num': num, 'filename': os.path.basename(filename), + 'uri': filename} + src['filename_base'], src['archive_fmt'], src['compression'] = \ + parse_archive_filename(os.path.basename(filename)) + if (src['filename_base'].startswith(self.name) and + src['archive_fmt']): + # Take the first archive that starts with pkg name + orig = src + break + # otherwise we take the first archive + elif not orig and src['archive_fmt']: + orig = src + # else don't accept + if orig: + orig['prefix'] = self._guess_orig_prefix(orig) + + return orig + + +def parse_srpm(srpmfile): + """parse srpm by creating a SrcRpmFile object""" + try: + srcrpm = SrcRpmFile(srpmfile) + except IOError, err: + raise GbpError, "Error reading src.rpm file: %s" % err + except librpm.error, err: + raise GbpError, "RPM error while reading src.rpm: %s" % err + + return srcrpm + + +def guess_spec_fn(file_list, preferred_name=None): + """Guess spec file from a list of filenames""" + specs = [] + for filepath in file_list: + filename = os.path.basename(filepath) + # Stop at the first file matching the preferred name + if filename == preferred_name: + gbp.log.debug("Found a preferred spec file %s" % filepath) + specs = [filepath] + break + if filename.endswith(".spec"): + gbp.log.debug("Found spec file %s" % filepath) + specs.append(filepath) + if len(specs) == 0: + raise NoSpecError("No spec file found.") + elif len(specs) > 1: + raise NoSpecError("Multiple spec files found (%s), don't know which " + "to use." % ', '.join(specs)) + return specs[0] + + +def guess_spec(topdir, recursive=True, preferred_name=None): + """Guess a spec file""" + file_list = [] + if not topdir: + topdir = '.' + for root, dirs, files in os.walk(topdir): + file_list.extend([os.path.join(root, fname) for fname in files]) + if not recursive: + del dirs[:] + # Skip .git dir in any case + if '.git' in dirs: + dirs.remove('.git') + return SpecFile(os.path.abspath(guess_spec_fn(file_list, preferred_name))) + + +def guess_spec_repo(repo, treeish, topdir='', recursive=True, preferred_name=None): + """ + Try to find/parse the spec file from a given git treeish. + """ + topdir = topdir.rstrip('/') + ('/') if topdir else '' + try: + file_list = [nam for (mod, typ, sha, nam) in + repo.list_tree(treeish, recursive, topdir) if typ == 'blob'] + except GitRepositoryError as err: + raise NoSpecError("Cannot find spec file from treeish %s, Git error: %s" + % (treeish, err)) + spec_path = guess_spec_fn(file_list, preferred_name) + return spec_from_repo(repo, treeish, spec_path) + + +def spec_from_repo(repo, treeish, spec_path): + """Get and parse a spec file from a give Git treeish""" + try: + spec = SpecFile(filedata=repo.show('%s:%s' % (treeish, spec_path))) + spec.specdir = os.path.dirname(spec_path) + spec.specfile = os.path.basename(spec_path) + return spec + except GitRepositoryError as err: + raise NoSpecError("Git error: %s" % err) + + +def string_to_int(val_str): + """ + Convert string of possible unit identifier to int. + + @param val_str: value to be converted + @type val_str: C{str} + @return: value as integer + @rtype: C{int} + + >>> string_to_int("1234") + 1234 + >>> string_to_int("123k") + 125952 + >>> string_to_int("1234K") + 1263616 + >>> string_to_int("1M") + 1048576 + """ + units = {'k': 1024, + 'm': 1024**2, + 'g': 1024**3, + 't': 1024**4} + + if val_str[-1].lower() in units: + return int(val_str[:-1]) * units[val_str[-1].lower()] + else: + return int(val_str) + + +def split_version_str(version): + """ + Parse full version string and split it into individual "version + components", i.e. upstreamversion, epoch and release + + @param version: full version of a package + @type version: C{str} + @return: individual version components + @rtype: C{dict} + + >>> split_version_str("1") + {'release': None, 'epoch': None, 'upstreamversion': '1'} + >>> split_version_str("1.2.3-5.3") + {'release': '5.3', 'epoch': None, 'upstreamversion': '1.2.3'} + >>> split_version_str("3:1.2.3") + {'release': None, 'epoch': '3', 'upstreamversion': '1.2.3'} + >>> split_version_str("3:1-0") + {'release': '0', 'epoch': '3', 'upstreamversion': '1'} + """ + ret = {'epoch': None, 'upstreamversion': None, 'release': None} + + e_vr = version.split(":", 1) + if len(e_vr) == 1: + v_r = e_vr[0].split("-", 1) + else: + ret['epoch'] = e_vr[0] + v_r = e_vr[1].split("-", 1) + ret['upstreamversion'] = v_r[0] + if len(v_r) > 1: + ret['release'] = v_r[1] + + return ret + +def compose_version_str(evr): + """ + Compose a full version string from individual "version components", + i.e. epoch, version and release + + @param evr: dict of version components + @type evr: C{dict} of C{str} + @return: full version + @rtype: C{str} + + >>> compose_version_str({'epoch': '', 'upstreamversion': '1.0'}) + '1.0' + >>> compose_version_str({'epoch': '2', 'upstreamversion': '1.0', 'release': None}) + '2:1.0' + >>> compose_version_str({'epoch': None, 'upstreamversion': '1', 'release': '0'}) + '1-0' + >>> compose_version_str({'epoch': '2', 'upstreamversion': '1.0', 'release': '2.3'}) + '2:1.0-2.3' + >>> compose_version_str({'epoch': '2', 'upstreamversion': '', 'release': '2.3'}) + """ + if 'upstreamversion' in evr and evr['upstreamversion']: + version = "" + if 'epoch' in evr and evr['epoch']: + version += "%s:" % evr['epoch'] + version += evr['upstreamversion'] + if 'release' in evr and evr['release']: + version += "-%s" % evr['release'] + if version: + return version + return None + + +# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: diff --git a/gbp/rpm/git.py b/gbp/rpm/git.py new file mode 100644 index 00000000..c7cc023b --- /dev/null +++ b/gbp/rpm/git.py @@ -0,0 +1,105 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2011 Guido Günther +# (C) 2012 Intel Corporation +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +import re + +from gbp.git import GitRepository, GitRepositoryError +from gbp.pkg.pristinetar import PristineTar +from gbp.rpm import compose_version_str + +class RpmGitRepository(GitRepository): + """A git repository that holds the source of an RPM package""" + + def __init__(self, path): + super(RpmGitRepository, self).__init__(path) + self.pristine_tar = PristineTar(self) + + def find_version(self, format, str_fields): + """ + Check if a certain version is stored in this repo and return the SHA1 + of the related commit. That is, an annotated tag is dereferenced to the + commit object it points to. + + @param format: tag pattern + @type format: C{str} + @param str_fields: arguments for format string ('upstreamversion', 'release', 'vendor'...) + @type str_fields: C{dict} of C{str} + @return: sha1 of the commit the tag references to + """ + try: + tag = self.version_to_tag(format, str_fields) + except KeyError: + return None + if self.has_tag(tag): # new tags are injective + # dereference to a commit object + return self.rev_parse("%s^0" % tag) + return None + + @staticmethod + def version_to_tag(format, str_fields): + """ + Generate a tag from a given format and a version + + @param format: tag pattern + @type format: C{str} + @param str_fields: arguments for format string ('upstreamversion', 'release', 'vendor'...) + @type str_fields: C{dict} of C{str} + @return: version tag + + >>> RpmGitRepository.version_to_tag("packaging/%(version)s", dict(epoch='0', upstreamversion='0~0')) + 'packaging/0%0_0' + >>> RpmGitRepository.version_to_tag("%(vendor)s/v%(version)s", dict(upstreamversion='1.0', release='2', vendor="myvendor")) + 'myvendor/v1.0-2' + """ + version_tag = format % dict(str_fields, + version=compose_version_str(str_fields)) + return RpmGitRepository._sanitize_tag(version_tag) + + @staticmethod + def _sanitize_tag(tag): + """sanitize a version so git accepts it as a tag + + >>> RpmGitRepository._sanitize_tag("0.0.0") + '0.0.0' + >>> RpmGitRepository._sanitize_tag("0.0~0") + '0.0_0' + >>> RpmGitRepository._sanitize_tag("0:0.0") + '0%0.0' + >>> RpmGitRepository._sanitize_tag("0%0~0") + '0%0_0' + """ + return tag.replace('~', '_').replace(':', '%') + + @property + def pristine_tar_branch(self): + """ + The name of the pristine-tar branch, whether it already exists or + not. + """ + return PristineTar.branch + + def has_pristine_tar_branch(self): + """ + Wheter the repo has a I{pristine-tar} branch. + + @return: C{True} if the repo has pristine-tar commits already, C{False} + otherwise + @rtype: C{Bool} + """ + return True if self.has_branch(self.pristine_tar_branch) else False + +# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: diff --git a/gbp/rpm/lib_rpm.py b/gbp/rpm/lib_rpm.py new file mode 100644 index 00000000..4bad44e7 --- /dev/null +++ b/gbp/rpm/lib_rpm.py @@ -0,0 +1,47 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2012 Intel Corporation +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +"""Wrapper module for librpm""" + +import tempfile + +import gbp.log +from gbp.rpm.policy import RpmPkgPolicy + +try: + # Try to load special RPM lib to be used for GBP (only) + librpm = __import__(RpmPkgPolicy.python_rpmlib_module_name) +except ImportError: + gbp.log.warn("Failed to import '%s' as rpm python module, using host's " + "default rpm library instead" % + RpmPkgPolicy.python_rpmlib_module_name) + import rpm as librpm + +# Module initialization +_rpmlog = tempfile.NamedTemporaryFile(prefix='gbp_rpmlog') +_rpmlogfd = _rpmlog.file +librpm.setVerbosity(librpm.RPMLOG_INFO) +librpm.setLogFile(_rpmlogfd) + + +def get_librpm_log(truncate=True): + """Get rpmlib log output""" + _rpmlogfd.seek(0) + log = [line.strip() for line in _rpmlogfd.readlines()] + if truncate: + _rpmlogfd.truncate(0) + return log + diff --git a/gbp/rpm/linkedlist.py b/gbp/rpm/linkedlist.py new file mode 100644 index 00000000..ca000453 --- /dev/null +++ b/gbp/rpm/linkedlist.py @@ -0,0 +1,214 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2012 Intel Corporation +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +"""Simple implementation of a doubly linked list""" + +import collections + +import gbp.log + + +class LinkedListNode(object): + """Node of the linked list""" + + def __init__(self, data="", prev_node=None, next_node=None): + self.prev = prev_node + self.next = next_node + self._data = data + + def __str__(self): + return str(self.data) + + @property + def data(self): + """Get data stored into node""" + if self._data is None: + gbp.log.err("BUG: referencing a deleted node!") + return("") + return self._data + + def set_data(self, data): + """ + Set data stored into node + + >>> node = LinkedListNode('foo') + >>> node.data + 'foo' + >>> node.set_data('bar') + >>> node.data + 'bar' + >>> node.set_data(None) + >>> node.data + '' + """ + if data is None: + gbp.log.err("BUG: trying to store 'None', not allowed") + data = "" + self._data = data + + + def delete(self): + """Delete node""" + if self.prev: + self.prev.next = self.next + if self.next: + self.next.prev = self.prev + self._data = None + + +class LinkedListIterator(collections.Iterator): + """Iterator for the linked list""" + + def __init__(self, obj): + self._next = obj.first + + def next(self): + ret = self._next + if ret: + self._next = ret.next + else: + raise StopIteration + return ret + + +class LinkedList(collections.Iterable): + """Doubly linked list""" + + def __init__(self): + self._first = None + self._last = None + + def __iter__(self): + return LinkedListIterator(self) + + def __len__(self): + for num, data in enumerate(self): + pass + return num + 1 + + @property + def first(self): + """Get the first node of the list""" + return self._first + + def prepend(self, data): + """ + Insert to the beginning of list + + >>> list = LinkedList() + >>> [str(data) for data in list] + [] + >>> node = list.prepend("foo") + >>> len(list) + 1 + >>> node = list.prepend("bar") + >>> [str(data) for data in list] + ['bar', 'foo'] + """ + if self._first is None: + new = self._first = self._last = LinkedListNode(data) + else: + new = self.insert_before(self._first, data) + return new + + def append(self, data): + """ + Insert to the end of list + + >>> list = LinkedList() + >>> node = list.append('foo') + >>> len(list) + 1 + >>> node = list.append('bar') + >>> [str(data) for data in list] + ['foo', 'bar'] + """ + if self._last is None: + return self.prepend(data) + else: + return self.insert_after(self._last, data) + + def insert_before(self, node, data=""): + """ + Insert before a node + + >>> list = LinkedList() + >>> node1 = list.append('foo') + >>> node2 = list.insert_before(node1, 'bar') + >>> node3 = list.insert_before(node1, 'baz') + >>> [str(data) for data in list] + ['bar', 'baz', 'foo'] + """ + new = LinkedListNode(data, prev_node=node.prev, next_node=node) + if node.prev: + node.prev.next = new + else: + self._first = new + node.prev = new + return new + + def insert_after(self, node, data=""): + """ + Insert after a node + + >>> list = LinkedList() + >>> node1 = list.prepend('foo') + >>> node2 = list.insert_after(node1, 'bar') + >>> node3 = list.insert_after(node1, 'baz') + >>> [str(data) for data in list] + ['foo', 'baz', 'bar'] + """ + new = LinkedListNode(data, prev_node=node, next_node=node.next) + if node.next: + node.next.prev = new + else: + self._last = new + node.next = new + return new + + def delete(self, node): + """ + Delete node + + >>> list = LinkedList() + >>> node1 = list.prepend('foo') + >>> node2 = list.insert_after(node1, 'bar') + >>> node3 = list.insert_before(node2, 'baz') + >>> [str(data) for data in list] + ['foo', 'baz', 'bar'] + >>> str(list.delete(node3)) + 'foo' + >>> [str(data) for data in list] + ['foo', 'bar'] + >>> print "%s" % node3 + + >>> str(list.delete(node1)) + 'bar' + >>> [str(data) for data in list] + ['bar'] + >>> list.delete(node2) + >>> [str(data) for data in list] + [] + """ + ret = node.prev + if node is self._first: + ret = self._first = self._first.next + if node is self._last: + self._last = self._last.prev + node.delete() + return ret + +# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: diff --git a/gbp/rpm/policy.py b/gbp/rpm/policy.py new file mode 100644 index 00000000..f8cb8630 --- /dev/null +++ b/gbp/rpm/policy.py @@ -0,0 +1,72 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2012 Intel Corporation +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +"""Default packaging policy for RPM""" + +import re +from gbp.pkg import PkgPolicy, parse_archive_filename + +class RpmPkgPolicy(PkgPolicy): + """Packaging policy for RPM""" + + # Special rpmlib python module for GBP (only) + python_rpmlib_module_name = "rpm" + + alnum = 'a-zA-Z0-9' + # Valid characters for RPM pkg name + name_whitelist_chars = '._+%{}\-' + # Valid characters for RPM pkg version + version_whitelist_chars = '._+%{}~' + + # Regexp for checking the validity of package name + packagename_re = re.compile("^[%s][%s%s]+$" % + (alnum, alnum, name_whitelist_chars)) + packagename_msg = ("Package names must be at least two characters long, " + "start with an alphanumeric and can only contain " + "alphanumerics or characters in %s" % + list(name_whitelist_chars)) + + # Regexp for checking the validity of package (upstream) version + upstreamversion_re = re.compile("^[0-9][%s%s]*$" % + (alnum, version_whitelist_chars)) + upstreamversion_msg = ("Upstream version numbers must start with a digit " + "and can only containg alphanumerics or characters " + "in %s" % list(version_whitelist_chars)) + + @classmethod + def is_valid_orig_archive(cls, filename): + """ + Is this a valid orig source archive + + @param filename: upstream source archive filename + @type filename: C{str} + @return: true if valid upstream source archive filename + @rtype: C{bool} + + >>> RpmPkgPolicy.is_valid_orig_archive("foo/bar_baz.tar.gz") + True + >>> RpmPkgPolicy.is_valid_orig_archive("foo.bar.tar") + True + >>> RpmPkgPolicy.is_valid_orig_archive("foo.bar") + False + >>> RpmPkgPolicy.is_valid_orig_archive("foo.gz") + False + """ + _base, arch_fmt, _compression = parse_archive_filename(filename) + if arch_fmt: + return True + return False + diff --git a/tests/20_test_rpm.py b/tests/20_test_rpm.py new file mode 100644 index 00000000..227a1c68 --- /dev/null +++ b/tests/20_test_rpm.py @@ -0,0 +1,383 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2012 Intel Corporation +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +"""Test the classes under L{gbp.rpm}""" + +import filecmp +import os +import shutil +import tempfile +from nose.tools import assert_raises, eq_ # pylint: disable=E0611 + +from gbp.errors import GbpError +from gbp.rpm import (SrcRpmFile, SpecFile, parse_srpm, NoSpecError, guess_spec, + guess_spec_repo, spec_from_repo) +from gbp.git.repository import GitRepository + +# Disable "Method could be a function" +# pylint: disable=R0201 + + +DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', + 'rpm') +SRPM_DIR = os.path.join(DATA_DIR, 'srpms') +SPEC_DIR = os.path.join(DATA_DIR, 'specs') + + +class SpecFileTester(SpecFile): + """Helper class for testing""" + + def protected(self, name): + """Get a protected member""" + return super(SpecFileTester, self).__getattribute__(name) + + +class RpmTestBase(object): + """Test base class""" + def __init__(self): + self.tmpdir = None + + def setup(self): + """Test case setup""" + self.tmpdir = tempfile.mkdtemp(prefix='gbp_%s_' % __name__, dir='.') + + def teardown(self): + """Test case teardown""" + shutil.rmtree(self.tmpdir) + +class TestSpecFile(RpmTestBase): + """Test L{gbp.rpm.SpecFile}""" + + def test_spec(self): + """Test parsing of a valid spec file""" + spec_filepath = os.path.join(SPEC_DIR, 'gbp-test.spec') + spec = SpecFileTester(spec_filepath) + + # Test basic properties + assert spec.specfile == os.path.basename(spec_filepath) + assert spec.specdir == os.path.dirname(spec_filepath) + assert spec.specpath == spec_filepath + + assert spec.name == 'gbp-test' + assert spec.packager is None + + assert spec.upstreamversion == '1.0' + assert spec.release == '1' + assert spec.epoch is None + assert spec.version == {'release': '1', 'upstreamversion': '1.0'} + + orig = spec.orig_src + assert orig['filename'] == 'gbp-test-1.0.tar.bz2' + assert orig['uri'] == 'gbp-test-1.0.tar.bz2' + assert orig['filename_base'] == 'gbp-test-1.0' + assert orig['archive_fmt'] == 'tar' + assert orig['compression'] == 'bzip2' + assert orig['prefix'] == 'gbp-test/' + + def test_spec_2(self): + """Test parsing of another valid spec file""" + spec_filepath = os.path.join(SPEC_DIR, 'gbp-test2.spec') + spec = SpecFile(spec_filepath) + + # Test basic properties + assert spec.name == 'gbp-test2' + assert spec.packager == 'Markus Lehtonen ' \ + '' + + assert spec.epoch == '2' + assert spec.version == {'release': '0', 'upstreamversion': '3.0', + 'epoch': '2'} + + orig = spec.orig_src + assert orig['filename'] == 'gbp-test2-3.0.tar.gz' + assert orig['uri'] == 'ftp://ftp.host.com/gbp-test2-3.0.tar.gz' + assert orig['archive_fmt'] == 'tar' + assert orig['compression'] == 'gzip' + assert orig['prefix'] == '' + + def test_spec_3(self): + """Test parsing of yet another valid spec file""" + spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-native.spec') + spec = SpecFile(spec_filepath) + + # Test basic properties + assert spec.name == 'gbp-test-native' + orig = spec.orig_src + assert orig['filename'] == 'gbp-test-native-1.0.zip' + assert orig['archive_fmt'] == 'zip' + assert orig['compression'] == None + assert orig['prefix'] == 'gbp-test-native-1.0/' + + def test_spec_4(self): + """Test parsing of spec without orig tarball""" + spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-native2.spec') + spec = SpecFile(spec_filepath) + + # Test basic properties + assert spec.name == 'gbp-test-native2' + assert spec.orig_src is None + + def test_parse_raw(self): + """Test parsing of a valid spec file""" + with assert_raises(NoSpecError): + SpecFile(None, None) + with assert_raises(NoSpecError): + SpecFile('filename', 'filedata') + + spec_filepath = os.path.join(SPEC_DIR, 'gbp-test.spec') + with open(spec_filepath, 'r') as spec_fd: + spec_data = spec_fd.read() + spec = SpecFile(filedata=spec_data) + + # Test basic properties + assert spec.specfile == None + assert spec.specdir == None + assert spec.name == 'gbp-test' + + def test_update_spec(self): + """Test spec autoupdate functionality""" + # Create temporary spec file + tmp_spec = os.path.join(self.tmpdir, 'gbp-test.spec') + shutil.copy2(os.path.join(SPEC_DIR, 'gbp-test.spec'), tmp_spec) + + reference_spec = os.path.join(SPEC_DIR, 'gbp-test-reference.spec') + spec = SpecFile(tmp_spec) + spec.update_patches(['new.patch'], {}) + spec.write_spec_file() + assert filecmp.cmp(tmp_spec, reference_spec) is True + + # Test adding the VCS tag and adding changelog + reference_spec = os.path.join(SPEC_DIR, 'gbp-test-reference2.spec') + spec.set_tag('VCS', None, 'myvcstag') + spec.set_changelog("* Wed Feb 05 2014 Name 1\n- New entry\n") + spec.write_spec_file() + assert filecmp.cmp(tmp_spec, reference_spec) is True + + def test_update_spec2(self): + """Another test for spec autoupdate functionality""" + tmp_spec = os.path.join(self.tmpdir, 'gbp-test2.spec') + shutil.copy2(os.path.join(SPEC_DIR, 'gbp-test2.spec'), tmp_spec) + + reference_spec = os.path.join(SPEC_DIR, 'gbp-test2-reference2.spec') + spec = SpecFile(tmp_spec) + spec.update_patches(['1.patch', '2.patch'], + {'1.patch': {'if': 'true'}, + '2.patch': {'ifarch': '%ix86'}}) + spec.set_tag('VCS', None, 'myvcstag') + spec.write_spec_file() + assert filecmp.cmp(tmp_spec, reference_spec) is True + + # Test updating patches again, removing the VCS tag and re-writing + # changelog + reference_spec = os.path.join(SPEC_DIR, 'gbp-test2-reference.spec') + spec.update_patches(['new.patch'], {'new.patch': {'if': '1'}}) + spec.set_tag('VCS', None, '') + spec.set_changelog("* Wed Feb 05 2014 Name 2\n- New entry\n\n") + spec.write_spec_file() + assert filecmp.cmp(tmp_spec, reference_spec) is True + + def test_modifying(self): + """Test updating/deleting of tags and macros""" + tmp_spec = os.path.join(self.tmpdir, 'gbp-test.spec') + shutil.copy2(os.path.join(SPEC_DIR, 'gbp-test-updates.spec'), tmp_spec) + reference_spec = os.path.join(SPEC_DIR, + 'gbp-test-updates-reference.spec') + spec = SpecFileTester(tmp_spec) + + # Mangle tags + prev = spec.protected('_delete_tag')('Vendor', None) + spec.protected('_set_tag')('License', None, 'new license', prev) + spec.protected('_delete_tag')('source', 0) + assert spec.sources() == {} + spec.protected('_delete_tag')('patch', 0) + spec.protected('_delete_tag')('patch', -1) + assert spec.protected('_patches')() == {} + prev = spec.protected('_delete_tag')('invalidtag', None) + + with assert_raises(GbpError): + # Check that setting empty value fails + spec.protected('_set_tag')('Version', None, '', prev) + with assert_raises(GbpError): + # Check that setting invalid tag with public method fails + spec.set_tag('invalidtag', None, 'value') + + # Mangle macros + prev = spec.protected('_delete_special_macro')('patch', -1) + spec.protected('_delete_special_macro')('patch', 123) + spec.protected('_set_special_macro')('patch', 0, 'my new args', prev) + with assert_raises(GbpError): + spec.protected('_delete_special_macro')('invalidmacro', 0) + with assert_raises(GbpError): + spec.protected('_set_special_macro')('invalidmacro', 0, 'args', + prev) + + # Check resulting spec file + spec.write_spec_file() + assert filecmp.cmp(tmp_spec, reference_spec) is True + + def test_modifying_err(self): + """Test error conditions of modification methods""" + spec_filepath = os.path.join(SPEC_DIR, 'gbp-test2.spec') + spec = SpecFileTester(spec_filepath) + + # Unknown/invalid section name + with assert_raises(GbpError): + spec.protected('_set_section')('patch', 'new content\n') + + # Multiple sections with the same name + with assert_raises(GbpError): + spec.protected('_set_section')('files', '%{_sysconfdir}/foo\n') + + def test_changelog(self): + """Test changelog methods""" + spec_filepath = os.path.join(SPEC_DIR, 'gbp-test2.spec') + spec = SpecFile(spec_filepath) + + # Read changelog + eq_(spec.get_changelog(), + "* Tue Feb 04 2014 Name 1\n- My change\n\n\n") + + # Set changelog and check again + new_text = "* Wed Feb 05 2014 Name 2\n- New entry\n\n\n" + spec.set_changelog(new_text) + eq_(spec.get_changelog(), new_text) + + def test_quirks(self): + """Test spec that is broken/has anomalities""" + spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-quirks.spec') + spec = SpecFile(spec_filepath) + + # Check that we quess orig source and prefix correctly + assert spec.orig_src['prefix'] == 'foobar/' + + def test_tags(self): + """Test parsing of all the different tags of spec file""" + spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-tags.spec') + spec = SpecFileTester(spec_filepath) + + # Check all the tags + for name, val in spec.protected('_tags').iteritems(): + rval = None + if name in ('version', 'release', 'epoch'): + rval = '0' + elif name in ('autoreq', 'autoprov', 'autoreqprov'): + rval = 'No' + elif name not in spec.protected('_listtags'): + rval = 'my_%s' % name + if rval: + assert val['value'] == rval, ("'%s:' is '%s', expecting '%s'" % + (name, val['value'], rval)) + assert spec.ignorepatches == [] + # Check patch numbers and patch filenames + patches = {} + for patch in spec.protected('_tags')['patch']['lines']: + patches[patch['num']] = patch['linevalue'] + + assert patches == {0: 'my_patch0', -1: 'my_patch'} + + def test_patch_series(self): + """Test the getting the patches as a patchseries""" + spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-native.spec') + spec = SpecFileTester(spec_filepath) + + assert len(spec.patchseries()) == 0 + spec.update_patches(['1.patch', '2.patch', '3.patch'], {}) + assert len(spec.patchseries()) == 3 + spec.protected('_gbp_tags')['ignore-patches'].append({'args': "0"}) + spec.update_patches(['4.patch'], {}) + assert len(spec.patchseries()) == 1 + assert len(spec.patchseries(ignored=True)) == 2 + spec.protected('_delete_special_macro')('patch', 0) + assert len(spec.patchseries(ignored=True)) == 1 + series = spec.patchseries(unapplied=True, ignored=True) + assert len(series) == 2 + assert os.path.basename(series[-1].path) == '1.patch' + + def test_patch_series_quirks(self): + """Patches are applied in order different from the patch numbering""" + spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-quirks.spec') + spec = SpecFileTester(spec_filepath) + + # Check series is returned in the order the patches are applied + files = [os.path.basename(patch.path) for patch in spec.patchseries()] + assert files == ['05.patch', '01.patch'] + # Also ignored patches are returned in the correct order + files = [os.path.basename(patch.path) for patch in + spec.patchseries(ignored=True)] + assert files == ['05.patch', '02.patch', '01.patch'] + # Unapplied patches are added to the end of the series + files = [os.path.basename(patch.path) for patch in + spec.patchseries(unapplied=True)] + assert files == ['05.patch', '01.patch', '03.patch'] + # Return all patches (for which tag is found) + files = [os.path.basename(patch.path) for patch in + spec.patchseries(unapplied=True, ignored=True)] + assert files == ['05.patch', '02.patch', '01.patch', '03.patch', + '04.patch'] + + +class TestUtilityFunctions(RpmTestBase): + """Test utility functions of L{gbp.rpm}""" + + def test_guess_spec(self): + """Test guess_spec() function""" + # Spec not found + with assert_raises(NoSpecError): + guess_spec(DATA_DIR, recursive=False) + # Multiple spec files + with assert_raises(NoSpecError): + guess_spec(DATA_DIR, recursive=True) + with assert_raises(NoSpecError): + guess_spec(SPEC_DIR, recursive=False) + # Spec found + spec = guess_spec(SPEC_DIR, recursive=False, + preferred_name = 'gbp-test2.spec') + assert spec.specfile == 'gbp-test2.spec' + assert spec.specdir == SPEC_DIR + + def test_guess_spec_repo(self): + """Test guess_spec_repo() and spec_from_repo() functions""" + # Create dummy repository with some commits + repo = GitRepository.create(self.tmpdir) + with open(os.path.join(repo.path, 'foo.txt'), 'w') as fobj: + fobj.write('bar\n') + repo.add_files('foo.txt') + repo.commit_all('Add dummy file') + os.mkdir(os.path.join(repo.path, 'packaging')) + shutil.copy(os.path.join(SPEC_DIR, 'gbp-test.spec'), + os.path.join(repo.path, 'packaging')) + repo.add_files('packaging/gbp-test.spec') + repo.commit_all('Add spec file') + + # Spec not found + with assert_raises(NoSpecError): + guess_spec_repo(repo, 'HEAD~1', recursive=True) + with assert_raises(NoSpecError): + guess_spec_repo(repo, 'HEAD', recursive=False) + # Spec found + spec = guess_spec_repo(repo, 'HEAD', 'packaging', recursive=False) + spec = guess_spec_repo(repo, 'HEAD', recursive=True) + assert spec.specfile == 'gbp-test.spec' + assert spec.specdir == 'packaging' + assert spec.specpath == 'packaging/gbp-test.spec' + + # Test spec_from_repo() + with assert_raises(NoSpecError): + spec_from_repo(repo, 'HEAD~1', 'packaging/gbp-test.spec') + spec = spec_from_repo(repo, 'HEAD', 'packaging/gbp-test.spec') + assert spec.specfile == 'gbp-test.spec' + +# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: diff --git a/tests/data/rpm/rpmbuild/SOURCES/bar.tar.gz b/tests/data/rpm/rpmbuild/SOURCES/bar.tar.gz new file mode 100644 index 00000000..f5dae803 Binary files /dev/null and b/tests/data/rpm/rpmbuild/SOURCES/bar.tar.gz differ diff --git a/tests/data/rpm/rpmbuild/SOURCES/foo.txt b/tests/data/rpm/rpmbuild/SOURCES/foo.txt new file mode 100644 index 00000000..25ed442f --- /dev/null +++ b/tests/data/rpm/rpmbuild/SOURCES/foo.txt @@ -0,0 +1,3 @@ +FOO: + +file for testing rpm support of git-buildpackage. diff --git a/tests/data/rpm/rpmbuild/SOURCES/gbp-test-1.0.tar.bz2 b/tests/data/rpm/rpmbuild/SOURCES/gbp-test-1.0.tar.bz2 new file mode 100644 index 00000000..7d0759fe Binary files /dev/null and b/tests/data/rpm/rpmbuild/SOURCES/gbp-test-1.0.tar.bz2 differ diff --git a/tests/data/rpm/rpmbuild/SOURCES/gbp-test-native-1.0.zip b/tests/data/rpm/rpmbuild/SOURCES/gbp-test-native-1.0.zip new file mode 100644 index 00000000..22a273d1 Binary files /dev/null and b/tests/data/rpm/rpmbuild/SOURCES/gbp-test-native-1.0.zip differ diff --git a/tests/data/rpm/rpmbuild/SOURCES/gbp-test2-3.0.tar.gz b/tests/data/rpm/rpmbuild/SOURCES/gbp-test2-3.0.tar.gz new file mode 100644 index 00000000..7b3eaf3c Binary files /dev/null and b/tests/data/rpm/rpmbuild/SOURCES/gbp-test2-3.0.tar.gz differ diff --git a/tests/data/rpm/rpmbuild/SOURCES/my.patch b/tests/data/rpm/rpmbuild/SOURCES/my.patch new file mode 100644 index 00000000..50870df2 --- /dev/null +++ b/tests/data/rpm/rpmbuild/SOURCES/my.patch @@ -0,0 +1,9 @@ +diff --git a/dummy.sh b/dummy.sh +index 8c33db6..6f04268 100755 +--- dummy.sh ++++ dummy.sh +@@ -1,3 +1,3 @@ + #!/bin/sh + +-echo "Hello world" ++echo "Hello GBP" diff --git a/tests/data/rpm/rpmbuild/SOURCES/my2.patch b/tests/data/rpm/rpmbuild/SOURCES/my2.patch new file mode 100644 index 00000000..ad5ca2d2 --- /dev/null +++ b/tests/data/rpm/rpmbuild/SOURCES/my2.patch @@ -0,0 +1,7 @@ +diff --git a/mydir/myfile.txt b/mydir/myfile.txt +new file mode 100644 +index 0000000..2cdad29 +--- /dev/null ++++ b/mydir/myfile.txt +@@ -0,0 +1 @@ ++Dummy diff --git a/tests/data/rpm/rpmbuild/SOURCES/my3.patch b/tests/data/rpm/rpmbuild/SOURCES/my3.patch new file mode 100644 index 00000000..9fee859d --- /dev/null +++ b/tests/data/rpm/rpmbuild/SOURCES/my3.patch @@ -0,0 +1,7 @@ +diff --git a/README b/README +index a1311cb..a59f1b9 100644 +--- a/README ++++ b/README +@@ -1 +1 @@ +-Just for testing git-buildpackage. ++Just for testing GBP. diff --git a/tests/data/rpm/rpmbuild/SPECS/gbp-test-native.spec b/tests/data/rpm/rpmbuild/SPECS/gbp-test-native.spec new file mode 100644 index 00000000..38b07e48 --- /dev/null +++ b/tests/data/rpm/rpmbuild/SPECS/gbp-test-native.spec @@ -0,0 +1,34 @@ +Name: gbp-test-native +Summary: Test package for git-buildpackage +Version: 1.0 +Release: 1 +Group: Development/Libraries +License: GPLv2 +Source1: %{name}-%{version}.zip +BuildRequires: unzip + +%description +Package for testing the RPM functionality of git-buildpackage. +Mimics a "native" package + + +%prep +unzip %{SOURCE1} +%setup -T -D + + +%build +make + + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}/%{_datadir}/%{name} +cp -R * %{buildroot}/%{_datadir}/%{name} + + + +%files +%defattr(-,root,root,-) +%dir %{_datadir}/%{name} +%{_datadir}/%{name} diff --git a/tests/data/rpm/rpmbuild/SPECS/gbp-test-native2.spec b/tests/data/rpm/rpmbuild/SPECS/gbp-test-native2.spec new file mode 100644 index 00000000..34fd33dc --- /dev/null +++ b/tests/data/rpm/rpmbuild/SPECS/gbp-test-native2.spec @@ -0,0 +1,35 @@ +Name: gbp-test-native2 +Summary: Test package for git-buildpackage +Version: 2.0 +Release: 0 +Group: Development/Libraries +License: GPLv2 +Source: foo.txt +BuildRequires: unzip + +%description +Package for testing the RPM functionality of git-buildpackage. +Mimics a "native" package that doesn't have any source tarball. + + +%prep +# Just create build dir +%setup -T -c +cp %{SOURCE0} . + + +%build +# Nothing to do + + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}/%{_datadir}/%{name} +cp -R * %{buildroot}/%{_datadir}/%{name} + + + +%files +%defattr(-,root,root,-) +%dir %{_datadir}/%{name} +%{_datadir}/%{name} diff --git a/tests/data/rpm/rpmbuild/SPECS/gbp-test.spec b/tests/data/rpm/rpmbuild/SPECS/gbp-test.spec new file mode 100644 index 00000000..c46a734e --- /dev/null +++ b/tests/data/rpm/rpmbuild/SPECS/gbp-test.spec @@ -0,0 +1,42 @@ +Name: gbp-test +Summary: Test package for git-buildpackage +Version: 1.0 +Release: 1 +Group: Development/Libraries +License: GPLv2 +Source: %{name}-%{version}.tar.bz2 +Source1: foo.txt +Source20: bar.tar.gz +# Gbp-Ignore-Patches: 0 +Patch0: my.patch +Patch10: my2.patch +Patch20: my3.patch + + +%description +Package for testing the RPM functionality of git-buildpackage. + + +%prep +%setup -n %{name} -a 20 + +%patch0 +%patch10 -p1 + + +%build +make + + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}/%{_datadir}/%{name} +cp -R * %{buildroot}/%{_datadir}/%{name} +install %{SOURCE0} %{buildroot}/%{_datadir}/%{name} + + + +%files +%defattr(-,root,root,-) +%dir %{_datadir}/%{name} +%{_datadir}/%{name} diff --git a/tests/data/rpm/rpmbuild/SPECS/gbp-test2.spec b/tests/data/rpm/rpmbuild/SPECS/gbp-test2.spec new file mode 100644 index 00000000..8a92725d --- /dev/null +++ b/tests/data/rpm/rpmbuild/SPECS/gbp-test2.spec @@ -0,0 +1,60 @@ +Name: gbp-test2 +Summary: Test package 2 for git-buildpackage +Epoch: 2 +Version: 3.0 +Release: 0 +Group: Development/Libraries +License: GPLv2 +Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz +Source: foo.txt +Source20: bar.tar.gz +# Gbp-Ignore-Patches: -1 +Patch: my.patch +Patch10: my2.patch +Patch20: my3.patch +Packager: Markus Lehtonen +VCS: myoldvcstag + +%description +Package for testing the RPM functionality of git-buildpackage. + +%package empty +Summary: Empty subpackage + +%description empty +Empty subpackage for the %{name} test package. + + +%prep +%setup -T -n %{name}-%{version} -c -a 10 + +%patch +%patch -P 10 -p1 + +echo "Do things" + +# Gbp-Patch-Macros + +%build +make + + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}/%{_datadir}/%{name} +cp -R * %{buildroot}/%{_datadir}/%{name} +install %{SOURCE0} %{buildroot}/%{_datadir}/%{name} + + +%changelog +* Tue Feb 04 2014 Name 1 +- My change + + +%files +%defattr(-,root,root,-) +%dir %{_datadir}/%{name} +%{_datadir}/%{name} + +%files empty +%defattr(-,root,root,-) diff --git a/tests/data/rpm/specs/gbp-test-native.spec b/tests/data/rpm/specs/gbp-test-native.spec new file mode 120000 index 00000000..60de36f2 --- /dev/null +++ b/tests/data/rpm/specs/gbp-test-native.spec @@ -0,0 +1 @@ +../rpmbuild/SPECS/gbp-test-native.spec \ No newline at end of file diff --git a/tests/data/rpm/specs/gbp-test-native2.spec b/tests/data/rpm/specs/gbp-test-native2.spec new file mode 120000 index 00000000..ad13ad6a --- /dev/null +++ b/tests/data/rpm/specs/gbp-test-native2.spec @@ -0,0 +1 @@ +../rpmbuild/SPECS/gbp-test-native2.spec \ No newline at end of file diff --git a/tests/data/rpm/specs/gbp-test-quirks.spec b/tests/data/rpm/specs/gbp-test-quirks.spec new file mode 100644 index 00000000..bb56b008 --- /dev/null +++ b/tests/data/rpm/specs/gbp-test-quirks.spec @@ -0,0 +1,30 @@ +# +# Spec for testing some quirks of spec parsing +# + +Name: pkg_name +Summary: Spec for testing some quirks of spec parsing +Version: 0.1 +Release: 1.2 +License: GPLv2 +Source1: foobar.tar.gz +# Gbp-Ignore-Patches: 2 4 888 +Patch1: 01.patch +Patch2: 02.patch +Patch3: 03.patch +Patch4: 04.patch +Patch5: 05.patch + +%description +Spec for testing some quirks of spec parsing. No intended for building an RPM. + +%prep +# We don't have Source0 so rpmbuild would fail, but gbp shouldn't crash +%setup -q + +# Patches are applied out-of-order wrt. numbering +%patch5 +%patch2 +%patch1 +# Patch 999 does not exist, rpmbuild would fail but GBP should not +%patch999 diff --git a/tests/data/rpm/specs/gbp-test-reference.spec b/tests/data/rpm/specs/gbp-test-reference.spec new file mode 100644 index 00000000..050d1398 --- /dev/null +++ b/tests/data/rpm/specs/gbp-test-reference.spec @@ -0,0 +1,43 @@ +Name: gbp-test +Summary: Test package for git-buildpackage +Version: 1.0 +Release: 1 +Group: Development/Libraries +License: GPLv2 +Source: %{name}-%{version}.tar.bz2 +Source1: foo.txt +Source20: bar.tar.gz +# Gbp-Ignore-Patches: 0 +Patch0: my.patch +# Patches auto-generated by git-buildpackage: +Patch1: new.patch + + +%description +Package for testing the RPM functionality of git-buildpackage. + + +%prep +%setup -n %{name} -a 20 + +%patch0 +# new.patch +%patch1 -p1 + + +%build +make + + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}/%{_datadir}/%{name} +cp -R * %{buildroot}/%{_datadir}/%{name} +install %{SOURCE0} %{buildroot}/%{_datadir}/%{name} + + + +%files +%defattr(-,root,root,-) +%dir %{_datadir}/%{name} +%{_datadir}/%{name} diff --git a/tests/data/rpm/specs/gbp-test-reference2.spec b/tests/data/rpm/specs/gbp-test-reference2.spec new file mode 100644 index 00000000..0fbe0260 --- /dev/null +++ b/tests/data/rpm/specs/gbp-test-reference2.spec @@ -0,0 +1,47 @@ +Name: gbp-test +VCS: myvcstag +Summary: Test package for git-buildpackage +Version: 1.0 +Release: 1 +Group: Development/Libraries +License: GPLv2 +Source: %{name}-%{version}.tar.bz2 +Source1: foo.txt +Source20: bar.tar.gz +# Gbp-Ignore-Patches: 0 +Patch0: my.patch +# Patches auto-generated by git-buildpackage: +Patch1: new.patch + + +%description +Package for testing the RPM functionality of git-buildpackage. + + +%prep +%setup -n %{name} -a 20 + +%patch0 +# new.patch +%patch1 -p1 + + +%build +make + + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}/%{_datadir}/%{name} +cp -R * %{buildroot}/%{_datadir}/%{name} +install %{SOURCE0} %{buildroot}/%{_datadir}/%{name} + + + +%files +%defattr(-,root,root,-) +%dir %{_datadir}/%{name} +%{_datadir}/%{name} +%changelog +* Wed Feb 05 2014 Name 1 +- New entry diff --git a/tests/data/rpm/specs/gbp-test-tags.spec b/tests/data/rpm/specs/gbp-test-tags.spec new file mode 100644 index 00000000..ee4c2b94 --- /dev/null +++ b/tests/data/rpm/specs/gbp-test-tags.spec @@ -0,0 +1,74 @@ +# +# Spec file for testing all RPM tags (that we know of +# + +%define suse_release %(test -e /etc/SuSE-release && head -n1 /etc/SuSE-release | cut -d ' ' -f2 | cut --output-delimiter=0 -d. -f1,2 || echo 0) +%if "%{suse_release}" >= "1201" +%define test_weak_dep_tags 1 +%endif + +%define test_arch_os_tags %(test -n "$GBP_SKIP_ARCH_OS_TAGS" && echo 0 || echo 1) + +%define source_fn_base source +%define patch_fn_base patch + +# Gbp-Undefined-Tag: foobar + +# Test that we accept different cases +NAME: my_name +version: 0 +ReLeasE: 0 + +# Rest of the tags +Epoch: 0 +Summary: my_summary +License: my_license +Distribution: my_distribution +Vendor: my_vendor +Group: my_group +Packager: my_packager +Url: my_url +Vcs: my_vcs +Source: my_source +Patch: my_%patch_fn_base +Patch0: my_%{patch_fn_base}0 +Nosource: 0 +Nopatch: 0 +#Icon: my_icon +BuildRoot: my_buildroot +Provides: my_provides +Requires: my_requires +Conflicts: my_conflicts +Obsoletes: my_obsoletes +BuildConflicts: my_buildconflicts +BuildRequires: my_buildrequires +AutoReqProv: No +AutoReq: No +AutoProv: No +DistTag: my_disttag +BugUrl: my_bugurl +Collections: my_collections + +%if 0%{?test_weak_dep_tags} +Recommends: my_recommends +Suggests: my_suggests +Supplements: my_supplements +Enhances: my_enhances +BuildRecommends:my_buildrecommends +BuildSuggests: my_buildsuggests +BuildSupplements:my_buildsupplements +BuildEnhances: my_buildenhances +%endif + +# These should be filtered out by GBP +%if "%{test_arch_os_tags}" != "0" +BuildArch: my_buildarch +ExcludeArch: my_excludearch +ExclusiveArch: my_exclusivearch +ExcludeOs: my_excludeos +ExclusiveOs: my_exclusiveos +%endif + +%description +Package for testing GBP. + diff --git a/tests/data/rpm/specs/gbp-test-updates-reference.spec b/tests/data/rpm/specs/gbp-test-updates-reference.spec new file mode 100644 index 00000000..ff56f589 --- /dev/null +++ b/tests/data/rpm/specs/gbp-test-updates-reference.spec @@ -0,0 +1,44 @@ +# +# Spec file for testing deleting/adding/updating tags and macros +# + +# Gbp-Undefined-Tag: foobar + +# Test that we accept different cases +Name: my_name +Version: 0 +Release: 1 +Summary: my_summary +License: new license +Distribution: my_distribution +Group: my_group +Packager: my_packager +Url: my_url +Vcs: my_vcs +Nosource: 0 +Nopatch: 0 +BuildRoot: my_buildroot +Provides: my_provides +Requires: my_requires +Conflicts: my_conflicts +Obsoletes: my_obsoletes +BuildConflicts: my_buildconflicts +BuildRequires: my_buildrequires +AutoReqProv: No +AutoReq: No +AutoProv: No +DistTag: my_disttag +BugUrl: my_bugurl +Collections: my_collections + +%description +Package for testing GBP. + +%prep +%setup -n my_prefix + +%patch0 my new args + +%build + +%install diff --git a/tests/data/rpm/specs/gbp-test-updates.spec b/tests/data/rpm/specs/gbp-test-updates.spec new file mode 100644 index 00000000..dc8ffbf9 --- /dev/null +++ b/tests/data/rpm/specs/gbp-test-updates.spec @@ -0,0 +1,49 @@ +# +# Spec file for testing deleting/adding/updating tags and macros +# + +# Gbp-Undefined-Tag: foobar + +# Test that we accept different cases +Name: my_name +Version: 0 +Release: 1 +Summary: my_summary +License: my_license +Distribution: my_distribution +Vendor: my_vendor +Group: my_group +Packager: my_packager +Url: my_url +Vcs: my_vcs +Source: my_source +Patch: my_%patch_fn_base +Patch0: my_%{patch_fn_base}0 +Nosource: 0 +Nopatch: 0 +BuildRoot: my_buildroot +Provides: my_provides +Requires: my_requires +Conflicts: my_conflicts +Obsoletes: my_obsoletes +BuildConflicts: my_buildconflicts +BuildRequires: my_buildrequires +AutoReqProv: No +AutoReq: No +AutoProv: No +DistTag: my_disttag +BugUrl: my_bugurl +Collections: my_collections + +%description +Package for testing GBP. + +%prep +%setup -n my_prefix + +%patch -b my_patch +%patch -P0 -b my_patch0 + +%build + +%install diff --git a/tests/data/rpm/specs/gbp-test.spec b/tests/data/rpm/specs/gbp-test.spec new file mode 120000 index 00000000..30ae2845 --- /dev/null +++ b/tests/data/rpm/specs/gbp-test.spec @@ -0,0 +1 @@ +../rpmbuild/SPECS/gbp-test.spec \ No newline at end of file diff --git a/tests/data/rpm/specs/gbp-test2-reference.spec b/tests/data/rpm/specs/gbp-test2-reference.spec new file mode 100644 index 00000000..1882131f --- /dev/null +++ b/tests/data/rpm/specs/gbp-test2-reference.spec @@ -0,0 +1,61 @@ +Name: gbp-test2 +Summary: Test package 2 for git-buildpackage +Epoch: 2 +Version: 3.0 +Release: 0 +Group: Development/Libraries +License: GPLv2 +Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz +Source: foo.txt +Source20: bar.tar.gz +# Gbp-Ignore-Patches: -1 +Patch: my.patch +# Patches auto-generated by git-buildpackage: +Patch0: new.patch +Packager: Markus Lehtonen + +%description +Package for testing the RPM functionality of git-buildpackage. + +%package empty +Summary: Empty subpackage + +%description empty +Empty subpackage for the %{name} test package. + + +%prep +%setup -T -n %{name}-%{version} -c -a 10 + +%patch + +echo "Do things" + +# Gbp-Patch-Macros +# new.patch +%if 1 +%patch0 -p1 +%endif + +%build +make + + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}/%{_datadir}/%{name} +cp -R * %{buildroot}/%{_datadir}/%{name} +install %{SOURCE0} %{buildroot}/%{_datadir}/%{name} + + +%changelog +* Wed Feb 05 2014 Name 2 +- New entry + +%files +%defattr(-,root,root,-) +%dir %{_datadir}/%{name} +%{_datadir}/%{name} + +%files empty +%defattr(-,root,root,-) diff --git a/tests/data/rpm/specs/gbp-test2-reference2.spec b/tests/data/rpm/specs/gbp-test2-reference2.spec new file mode 100644 index 00000000..d41f4503 --- /dev/null +++ b/tests/data/rpm/specs/gbp-test2-reference2.spec @@ -0,0 +1,68 @@ +Name: gbp-test2 +Summary: Test package 2 for git-buildpackage +Epoch: 2 +Version: 3.0 +Release: 0 +Group: Development/Libraries +License: GPLv2 +Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz +Source: foo.txt +Source20: bar.tar.gz +# Gbp-Ignore-Patches: -1 +Patch: my.patch +# Patches auto-generated by git-buildpackage: +Patch0: 1.patch +Patch1: 2.patch +Packager: Markus Lehtonen +VCS: myvcstag + +%description +Package for testing the RPM functionality of git-buildpackage. + +%package empty +Summary: Empty subpackage + +%description empty +Empty subpackage for the %{name} test package. + + +%prep +%setup -T -n %{name}-%{version} -c -a 10 + +%patch + +echo "Do things" + +# Gbp-Patch-Macros +# 1.patch +%if true +%patch0 -p1 +%endif +# 2.patch +%ifarch %ix86 +%patch1 -p1 +%endif + +%build +make + + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}/%{_datadir}/%{name} +cp -R * %{buildroot}/%{_datadir}/%{name} +install %{SOURCE0} %{buildroot}/%{_datadir}/%{name} + + +%changelog +* Tue Feb 04 2014 Name 1 +- My change + + +%files +%defattr(-,root,root,-) +%dir %{_datadir}/%{name} +%{_datadir}/%{name} + +%files empty +%defattr(-,root,root,-) diff --git a/tests/data/rpm/specs/gbp-test2.spec b/tests/data/rpm/specs/gbp-test2.spec new file mode 120000 index 00000000..af4080cb --- /dev/null +++ b/tests/data/rpm/specs/gbp-test2.spec @@ -0,0 +1 @@ +../rpmbuild/SPECS/gbp-test2.spec \ No newline at end of file -- cgit v1.2.3