1 # vim: set fileencoding=utf-8 :
3 # (C) 2006,2007 Guido Günther <agx@sigxcpu.org>
4 # (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, please see
17 # <http://www.gnu.org/licenses/>
18 """provides some rpm source package related helpers"""
23 from optparse import OptionParser
24 from collections import defaultdict
26 import gbp.command_wrappers as gbpc
27 from gbp.errors import GbpError
28 from gbp.git import GitRepositoryError
29 from gbp.patch_series import (PatchSeries, Patch)
31 from gbp.pkg import (UpstreamSource, Archive)
32 from gbp.rpm.policy import RpmPkgPolicy
33 from gbp.rpm.linkedlist import LinkedList
34 from gbp.rpm.lib_rpm import librpm, get_librpm_log
42 class NoSpecError(Exception):
43 """Spec file parsing error"""
47 class MacroExpandError(Exception):
48 """Macro expansion in spec file failed"""
52 class RpmUpstreamSource(UpstreamSource):
53 """Upstream source class for RPM packages"""
54 def __init__(self, name, unpacked=None, **kwargs):
55 super(RpmUpstreamSource, self).__init__(name,
61 class SrcRpmFile(object):
62 """Keeps all needed data read from a source rpm"""
63 def __init__(self, srpmfile):
64 # Do not required signed packages to be able to import
66 for flag in ['RPMVSF_NOMD5HEADER', 'RPMVSF_NORSAHEADER',
67 'RPMVSF_NOSHA1HEADER', 'RPMVSF_NODSAHEADER',
68 'RPMVSF_NOMD5', 'RPMVSF_NORSA', 'RPMVSF_NOSHA1',
71 # Ignore flags not present in different librpm versions
72 ts_vsflags |= getattr(librpm, flag)
73 except AttributeError:
75 with open(srpmfile) as srpmfp:
76 self.rpmhdr = librpm.ts(vsflags=ts_vsflags).hdrFromFdno(srpmfp.fileno())
77 self.srpmfile = os.path.abspath(srpmfile)
81 """Get the (downstream) version of the RPM package"""
82 version = dict(upstreamversion=self.rpmhdr[librpm.RPMTAG_VERSION].decode(),
83 release=self.rpmhdr[librpm.RPMTAG_RELEASE].decode())
84 if self.rpmhdr[librpm.RPMTAG_EPOCH] is not None:
85 version['epoch'] = str(self.rpmhdr[librpm.RPMTAG_EPOCH])
90 """Get the name of the RPM package"""
91 return self.rpmhdr[librpm.RPMTAG_NAME].decode()
94 def upstreamversion(self):
95 """Get the upstream version of the RPM package"""
96 return self.rpmhdr[librpm.RPMTAG_VERSION].decode()
100 """Get the packager of the RPM package"""
101 return _decode(self.rpmhdr[librpm.RPMTAG_PACKAGER])
103 def unpack(self, dest_dir):
105 Unpack the source rpm to tmpdir.
106 Leave the cleanup to the caller in case of an error.
108 c = gbpc.RunAtCommand('rpm2cpio',
109 [self.srpmfile, '|', 'cpio', '-id'],
110 shell=True, capture_stderr=True)
111 c.run_error = "'%s' failed: {stderr_or_reason}" % (" ".join([c.cmd] + c.args))
115 class SpecFile(object):
116 """Class for parsing/modifying spec files"""
117 tag_re = re.compile(r'^(?P<name>[a-z]+)(?P<num>[0-9]+)?\s*:\s*'
118 r'(?P<value>\S(.*\S)?)\s*$', flags=re.I)
119 directive_re = re.compile(r'^%(?P<name>[a-z]+)(?P<num>[0-9]+)?'
120 r'(\s+(?P<args>.*))?$', flags=re.I)
121 gbptag_re = re.compile(r'^\s*#\s*gbp-(?P<name>[a-z-]+)'
122 r'(\s*:\s*(?P<args>\S.*))?$', flags=re.I)
123 # Here "sections" stand for all scripts, scriptlets and other directives,
125 section_identifiers = ('package', 'description', 'prep', 'build', 'install',
126 'clean', 'check', 'pre', 'preun', 'post', 'postun', 'verifyscript',
127 'files', 'changelog', 'triggerin', 'triggerpostin', 'triggerun',
130 def __init__(self, filename=None, filedata=None):
132 self._content = LinkedList()
134 # Check args: only filename or filedata can be given, not both
135 if filename is None and filedata is None:
136 raise NoSpecError("No filename or raw data given for parsing!")
137 elif filename and filedata:
138 raise NoSpecError("Both filename and raw data given, don't know "
139 "which one to parse!")
141 # Load spec file into our special data structure
142 self.specfile = os.path.basename(filename)
143 self.specdir = os.path.dirname(os.path.abspath(filename))
145 with open(filename) as spec_file:
146 for line in spec_file.readlines():
147 self._content.append(line)
148 except IOError as err:
149 raise NoSpecError("Unable to read spec file: %s" % err)
153 for line in filedata.splitlines():
154 self._content.append(line + '\n')
156 # Use rpm-python to parse the spec file content
157 self._filtertags = ("excludearch", "excludeos", "exclusivearch",
158 "exclusiveos", "buildarch")
159 self._listtags = self._filtertags + ('source', 'patch',
160 'requires', 'conflicts', 'recommends',
161 'suggests', 'supplements', 'enhances',
162 'provides', 'obsoletes', 'buildrequires',
163 'buildconflicts', 'buildrecommends',
164 'buildsuggests', 'buildsupplements',
165 'buildenhances', 'collections',
166 'nosource', 'nopatch')
167 self._specinfo = self._parse_filtered_spec(self._filtertags)
169 # Other initializations
170 source_header = self._specinfo.packages[0].header
171 self.name = source_header[librpm.RPMTAG_NAME].decode()
172 self.upstreamversion = source_header[librpm.RPMTAG_VERSION].decode()
173 self.release = source_header[librpm.RPMTAG_RELEASE].decode()
174 # rpm-python returns epoch as 'long', convert that to string
175 self.epoch = str(source_header[librpm.RPMTAG_EPOCH]) \
176 if source_header[librpm.RPMTAG_EPOCH] is not None else None
177 self.packager = _decode(source_header[librpm.RPMTAG_PACKAGER])
179 self._special_directives = defaultdict(list)
180 self._gbp_tags = defaultdict(list)
182 # Parse extra info from spec file
183 self._parse_content()
185 # Find 'Packager' tag. Needed to circumvent a bug in python-rpm where
186 # spec.sourceHeader[librpm.RPMTAG_PACKAGER] is not reset when a new spec
188 if 'packager' not in self._tags:
191 self.orig_src = self._guess_orig_file()
193 def _parse_filtered_spec(self, skip_tags):
194 """Parse a filtered spec file in rpm-python"""
195 skip_tags = [tag.lower() for tag in skip_tags]
196 with tempfile.NamedTemporaryFile(prefix='gbp', mode='w+') as filtered:
197 filtered.writelines(str(line) for line in self._content
198 if str(line).split(":")[0].strip().lower() not in skip_tags)
201 # Parse two times to circumvent a rpm-python problem where
202 # macros are not expanded if used before their definition
203 librpm.spec(filtered.name)
204 return librpm.spec(filtered.name)
205 except ValueError as err:
206 rpmlog = get_librpm_log()
207 gbp.log.debug("librpm log:\n %s" %
209 raise GbpError("RPM error while parsing %s: %s (%s)" %
210 (self.specfile, err, rpmlog[-1]))
214 """Get the (downstream) version"""
215 version = dict(upstreamversion=self.upstreamversion,
216 release=self.release)
217 if self.epoch is not None:
218 version['epoch'] = self.epoch
223 """Get the dir/filename"""
224 return os.path.join(self.specdir, self.specfile)
227 def ignorepatches(self):
228 """Get numbers of ignored patches as a sorted list"""
229 if 'ignore-patches' in self._gbp_tags:
230 data = self._gbp_tags['ignore-patches'][-1]['args'].split()
231 return sorted([int(num) for num in data])
235 """Get all patch tags as a dict"""
236 if 'patch' not in self._tags:
238 return {patch['num']: patch for patch in self._tags['patch']['lines']}
241 """Get all source tags as a dict"""
242 if 'source' not in self._tags:
244 return {src['num']: src for src in self._tags['source']['lines']}
247 """Get all source tags as a dict"""
248 return {src['num']: src['linevalue']
249 for src in self._sources().values()}
251 def _macro_replace(self, matchobj):
252 macro_dict = {'name': self.name,
253 'version': self.upstreamversion,
254 'release': self.release}
256 if matchobj.group(2) in macro_dict:
257 return macro_dict[matchobj.group(2)]
258 raise MacroExpandError("Unknown macro '%s'" % matchobj.group(0))
260 def macro_expand(self, text):
262 Expand the rpm macros (that gbp knows of) in the given text.
264 @param text: text to check for macros
266 @return: text with macros expanded
269 # regexp to match '%{macro}' and '%macro'
270 macro_re = re.compile(r'%({)?(?P<macro_name>[a-z_][a-z0-9_]*)(?(1)})', flags=re.I)
271 return macro_re.sub(self._macro_replace, text)
273 def write_spec_file(self):
275 Write, possibly updated, spec to disk
277 with open(os.path.join(self.specdir, self.specfile), 'w') as spec_file:
278 for line in self._content:
279 spec_file.write(str(line))
281 def _parse_tag(self, lineobj):
286 matchobj = self.tag_re.match(line)
290 tagname = matchobj.group('name').lower()
291 tagnum = int(matchobj.group('num')) if matchobj.group('num') else None
293 if tagname == 'source':
294 tagnum = 0 if tagnum is None else tagnum
296 elif tagname == 'patch':
297 tagnum = -1 if tagnum is None else tagnum
299 # Record all tag locations
301 header = self._specinfo.packages[0].header
302 tagvalue = header[getattr(librpm, 'RPMTAG_%s' % tagname.upper())]
303 except AttributeError:
305 # We don't support "multivalue" tags like "Provides:" or "SourceX:"
306 # Rpm python doesn't support many of these, thus the explicit list
307 if isinstance(tagvalue, int):
308 tagvalue = str(tagvalue)
309 elif type(tagvalue) is list or tagname in self._listtags:
312 # Rpm python doesn't give the following, for reason or another
313 if tagname not in ('buildroot', 'autoprov', 'autoreq',
314 'autoreqprov') + self._filtertags:
315 gbp.log.warn("BUG: '%s:' tag not found by rpm" % tagname)
316 tagvalue = matchobj.group('value')
317 linerecord = {'line': lineobj,
319 'linevalue': matchobj.group('value')}
320 if tagname in self._tags:
321 self._tags[tagname]['value'] = tagvalue
322 self._tags[tagname]['lines'].append(linerecord)
324 if tagvalue and not isinstance(tagvalue, str):
325 tagvalue = tagvalue.decode()
326 self._tags[tagname] = {'value': tagvalue, 'lines': [linerecord]}
331 def _patch_macro_opts(args):
332 """Parse arguments of the '%patch' macro"""
334 patchparser = OptionParser(
335 prog="%s internal patch macro opts parser" % __name__,
336 usage="%prog for " + args)
337 patchparser.add_option("-p", dest="strip")
338 patchparser.add_option("-s", dest="silence")
339 patchparser.add_option("-P", dest="patchnum")
340 patchparser.add_option("-b", dest="backup")
341 patchparser.add_option("-E", dest="removeempty")
342 patchparser.add_option("-F", dest="fuzz")
343 arglist = args.split()
344 return patchparser.parse_args(arglist)[0]
347 def _setup_macro_opts(args):
348 """Parse arguments of the '%setup' macro"""
350 setupparser = OptionParser(
351 prog="%s internal setup macro opts parser" % __name__,
352 usage="%prog for " + args)
353 setupparser.add_option("-n", dest="name")
354 setupparser.add_option("-c", dest="create_dir", action="store_true")
355 setupparser.add_option("-D", dest="no_delete_dir", action="store_true")
356 setupparser.add_option("-T", dest="no_unpack_default",
358 setupparser.add_option("-b", dest="unpack_before")
359 setupparser.add_option("-a", dest="unpack_after")
360 setupparser.add_option("-q", dest="quiet", action="store_true")
361 arglist = args.split()
362 return setupparser.parse_args(arglist)[0]
364 def _parse_directive(self, lineobj):
365 """Parse special directive/scriptlet/macro lines"""
368 matchobj = self.directive_re.match(line)
372 directivename = matchobj.group('name')
375 if directivename == 'patch':
376 opts = self._patch_macro_opts(matchobj.group('args'))
377 if matchobj.group('num'):
378 directiveid = int(matchobj.group('num'))
380 directiveid = int(opts.patchnum)
384 # Record special directive/scriptlet/macro locations
385 if directivename in self.section_identifiers + ('setup', 'patch',
387 linerecord = {'line': lineobj,
389 'args': matchobj.group('args')}
390 self._special_directives[directivename].append(linerecord)
393 def _parse_gbp_tag(self, linenum, lineobj):
394 """Parse special git-buildpackage tags"""
397 matchobj = self.gbptag_re.match(line)
399 gbptagname = matchobj.group('name').lower()
400 if gbptagname not in ('ignore-patches', 'patch-macros'):
401 gbp.log.info("Found unrecognized Gbp tag on line %s: '%s'" %
403 if matchobj.group('args'):
404 args = matchobj.group('args').strip()
407 record = {'line': lineobj, 'args': args}
408 self._gbp_tags[gbptagname].append(record)
413 def _parse_content(self):
415 Go through spec file content line-by-line and (re-)parse info from it
418 for linenum, lineobj in enumerate(self._content):
421 if self._parse_tag(lineobj):
423 matched = self._parse_directive(lineobj)
425 if matched in self.section_identifiers:
428 self._parse_gbp_tag(linenum, lineobj)
430 # Update sources info (basically possible macros expanded by rpm)
431 # And, double-check that we parsed spec content correctly
432 patches = self._patches()
433 sources = self._sources()
434 for name, num, typ in self._specinfo.sources:
435 # workaround rpm parsing bug
436 if typ == 1 or typ == 9:
438 sources[num]['linevalue'] = name
440 gbp.log.err("BUG: failed to parse all 'Source' tags!")
441 elif typ == 2 or typ == 10:
442 # Patch tag without any number defined is treated by RPM as
443 # having number (2^31-1), we use number -1
444 if num >= pow(2, 30):
447 patches[num]['linevalue'] = name
449 gbp.log.err("BUG: failed to parse all 'Patch' tags!")
451 def _delete_tag(self, tag, num):
454 tagname = '%s%s' % (tag, num) if num is not None else tag
455 if key not in self._tags:
456 gbp.log.warn("Trying to delete non-existent tag '%s:'" % tag)
461 for line in self._tags[key]['lines']:
462 if line['num'] == num:
463 gbp.log.debug("Removing '%s:' tag from spec" % tagname)
464 prev = self._content.delete(line['line'])
466 sparedlines.append(line)
467 self._tags[key]['lines'] = sparedlines
468 if not self._tags[key]['lines']:
472 def _set_tag(self, tag, num, value, insertafter):
473 """Set a tag value"""
475 tagname = '%s%s' % (tag, num) if num is not None else tag
476 value = value.strip()
478 raise GbpError("Cannot set empty value to '%s:' tag" % tag)
480 # Check type of tag, we don't support values for 'multivalue' tags
482 header = self._specinfo.packages[0].header
483 tagvalue = header[getattr(librpm, 'RPMTAG_%s' % tagname.upper())]
484 except AttributeError:
486 tagvalue = None if type(tagvalue) is list else value
488 # Try to guess the correct indentation from the previous or next tag
489 indent_re = re.compile(r'^([a-z]+([0-9]+)?\s*:\s*)', flags=re.I)
490 match = indent_re.match(str(insertafter))
492 match = indent_re.match(str(insertafter.next))
493 indent = 12 if not match else len(match.group(1))
494 text = '%-*s%s\n' % (indent, '%s:' % tagname, value)
495 if key in self._tags:
496 self._tags[key]['value'] = tagvalue
497 for line in reversed(self._tags[key]['lines']):
498 if line['num'] == num:
499 gbp.log.debug("Updating '%s:' tag in spec" % tagname)
500 line['line'].set_data(text)
501 line['linevalue'] = value
504 gbp.log.debug("Adding '%s:' tag after '%s...' line in spec" %
505 (tagname, str(insertafter)[0:20]))
506 line = self._content.insert_after(insertafter, text)
507 linerec = {'line': line, 'num': num, 'linevalue': value}
508 if key in self._tags:
509 self._tags[key]['lines'].append(linerec)
511 self._tags[key] = {'value': tagvalue, 'lines': [linerec]}
514 def set_tag(self, tag, num, value, insertafter=None):
515 """Update a tag in spec file content"""
517 tagname = '%s%s' % (tag, num) if num is not None else tag
518 if key in ('patch', 'vcs'):
519 if key in self._tags:
521 elif insertafter not in self._tags:
523 after_line = self._tags[insertafter]['lines'][-1]['line']
525 self._set_tag(tag, num, value, after_line)
526 elif key in self._tags:
527 self._delete_tag(tag, num)
529 raise GbpError("Setting '%s:' tag not supported" % tagname)
531 def _delete_special_macro(self, name, identifier):
532 """Delete a special macro line in spec file content"""
534 raise GbpError("Deleting '%s:' macro not supported" % name)
537 fullname = '%%%s%s' % (name, identifier)
540 for line in self._special_directives[key]:
541 if line['id'] == identifier:
542 gbp.log.debug("Removing '%s' macro from spec" % fullname)
543 prev = self._content.delete(line['line'])
545 sparedlines.append(line)
546 self._special_directives[key] = sparedlines
548 gbp.log.warn("Tried to delete non-existent macro '%s'" % fullname)
551 def _set_special_macro(self, name, identifier, args, insertafter):
552 """Update a special macro line in spec file content"""
554 fullname = '%%%s%s' % (name, identifier)
556 raise GbpError("Setting '%s' macro not supported" % name)
559 text = "%%%s%d %s\n" % (name, identifier, args)
560 for line in self._special_directives[key]:
561 if line['id'] == identifier:
562 gbp.log.debug("Updating '%s' macro in spec" % fullname)
564 line['line'].set_data(text)
568 gbp.log.debug("Adding '%s' macro after '%s...' line in spec" %
569 (fullname, str(insertafter)[0:20]))
570 ret = self._content.insert_after(insertafter, text)
571 linerec = {'line': ret, 'id': identifier, 'args': args}
572 self._special_directives[key].append(linerec)
575 def _set_section(self, name, text):
576 """Update/create a complete section in spec file."""
577 if name not in self.section_identifiers:
578 raise GbpError("Not a valid section directive: '%s'" % name)
579 # Delete section, if it exists
580 if name in self._special_directives:
581 if len(self._special_directives[name]) > 1:
582 raise GbpError("Multiple %%%s sections found, don't know "
583 "which to update" % name)
584 line = self._special_directives[name][0]['line']
585 gbp.log.debug("Removing content of %s section" % name)
587 match = self.directive_re.match(str(line.next))
588 if match and match.group('name') in self.section_identifiers:
590 self._content.delete(line.next)
592 gbp.log.debug("Adding %s section to the end of spec file" % name)
593 line = self._content.append('%%%s\n' % name)
594 linerec = {'line': line, 'id': None, 'args': None}
595 self._special_directives[name] = [linerec]
597 gbp.log.debug("Updating content of %s section" % name)
598 for linetext in text.splitlines():
599 line = self._content.insert_after(line, linetext + '\n')
601 def set_changelog(self, text):
602 """Update or create the %changelog section"""
603 self._set_section('changelog', text)
605 def get_changelog(self):
606 """Get the %changelog section"""
608 if 'changelog' in self._special_directives:
609 line = self._special_directives['changelog'][0]['line']
612 match = self.directive_re.match(str(line))
613 if match and match.group('name') in self.section_identifiers:
618 def update_patches(self, patches, commands):
619 """Update spec with new patch tags and patch macros"""
620 # Remove non-ignored patches
623 ignored = self.ignorepatches
624 # Remove 'Patch:̈́' tags
625 for tag in self._patches().values():
626 if not tag['num'] in ignored:
627 tag_prev = self._delete_tag('patch', tag['num'])
628 # Remove a preceding comment if it seems to originate from GBP
629 if re.match("^\s*#.*patch.*auto-generated",
630 str(tag_prev), flags=re.I):
631 tag_prev = self._content.delete(tag_prev)
633 # Remove '%patch:' macros
634 for macro in self._special_directives['patch']:
635 if not macro['id'] in ignored:
636 macro_prev = self._delete_special_macro('patch', macro['id'])
637 # Remove surrounding if-else
638 macro_next = macro_prev.next
639 if (str(macro_prev).startswith('%if') and
640 str(macro_next).startswith('%endif')):
641 self._content.delete(macro_next)
642 macro_prev = self._content.delete(macro_prev)
644 # Remove a preceding comment line if it ends with '.patch' or
645 # '.diff' plus an optional compression suffix
646 if re.match("^\s*#.+(patch|diff)(\.(gz|bz2|xz|lzma))?\s*$",
647 str(macro_prev), flags=re.I):
648 macro_prev = self._content.delete(macro_prev)
650 if len(patches) == 0:
653 # Determine where to add Patch tag lines
655 gbp.log.debug("Adding 'Patch' tags in place of the removed tags")
657 elif 'patch' in self._tags:
658 gbp.log.debug("Adding new 'Patch' tags after the last 'Patch' tag")
659 tag_line = self._tags['patch']['lines'][-1]['line']
660 elif 'source' in self._tags:
661 gbp.log.debug("Didn't find any old 'Patch' tags, adding new "
662 "patches after the last 'Source' tag.")
663 tag_line = self._tags['source']['lines'][-1]['line']
665 gbp.log.debug("Didn't find any old 'Patch' or 'Source' tags, "
666 "adding new patches after the last 'Name' tag.")
667 tag_line = self._tags['name']['lines'][-1]['line']
669 # Determine where to add %patch macro lines
670 if self._special_directives['autosetup']:
671 gbp.log.debug("Found '%autosetup, skip adding %patch macros")
673 elif 'patch-macros' in self._gbp_tags:
674 gbp.log.debug("Adding '%patch' macros after the start marker")
675 macro_line = self._gbp_tags['patch-macros'][-1]['line']
677 gbp.log.debug("Adding '%patch' macros in place of the removed "
679 macro_line = macro_prev
680 elif self._special_directives['patch']:
681 gbp.log.debug("Adding new '%patch' macros after the last existing"
683 macro_line = self._special_directives['patch'][-1]['line']
684 elif self._special_directives['setup']:
685 gbp.log.debug("Didn't find any old '%patch' macros, adding new "
686 "patches after the last '%setup' macro")
687 macro_line = self._special_directives['setup'][-1]['line']
688 elif self._special_directives['prep']:
689 gbp.log.warn("Didn't find any old '%patch' or '%setup' macros, "
690 "adding new patches directly after '%prep' directive")
691 macro_line = self._special_directives['prep'][-1]['line']
693 raise GbpError("Couldn't determine where to add '%patch' macros")
695 startnum = sorted(ignored)[-1] + 1 if ignored else 0
696 gbp.log.debug("Starting autoupdate patch numbering from %s" % startnum)
697 # Add a comment indicating gbp generated patch tags
698 comment_text = "# Patches auto-generated by git-buildpackage:\n"
699 tag_line = self._content.insert_after(tag_line, comment_text)
700 for ind, patch in enumerate(patches):
701 cmds = commands[patch] if patch in commands else {}
702 patchnum = startnum + ind
703 tag_line = self._set_tag("Patch", patchnum, patch, tag_line)
705 # Add '%patch' macro and a preceding comment line
706 if macro_line is not None:
707 comment_text = "# %s\n" % patch
708 macro_line = self._content.insert_after(macro_line, comment_text)
709 macro_line = self._set_special_macro('patch', patchnum, '-p1',
711 for cmd, args in cmds.items():
712 if cmd in ('if', 'ifarch'):
713 self._content.insert_before(macro_line, '%%%s %s\n' %
715 macro_line = self._content.insert_after(macro_line,
717 # We only support one command per patch, for now
720 def patchseries(self, unapplied=False, ignored=False):
721 """Return non-ignored patches of the RPM as a gbp patchseries"""
722 series = PatchSeries()
724 ignored = set() if ignored else set(self.ignorepatches)
725 tags = dict([(k, v) for k, v in self._patches().items() if k not in ignored])
727 if self._special_directives['autosetup']:
728 # Return all patchses if %autosetup is used
729 for num in sorted(tags):
730 filename = os.path.basename(tags[num]['linevalue'])
731 series.append(Patch(os.path.join(self.specdir, filename)))
734 for macro in self._special_directives['patch']:
735 if macro['id'] in tags:
736 applied.append((macro['id'], macro['args']))
738 # Put all patches that are applied first in the series
739 for num, args in applied:
740 opts = self._patch_macro_opts(args)
741 strip = int(opts.strip) if opts.strip else 0
742 filename = os.path.basename(tags[num]['linevalue'])
743 series.append(Patch(os.path.join(self.specdir, filename),
745 # Finally, append all unapplied patches to the series, if requested
747 applied_nums = set([num for num, _args in applied])
748 unapplied = set(tags.keys()).difference(applied_nums)
749 for num in sorted(unapplied):
750 filename = os.path.basename(tags[num]['linevalue'])
751 series.append(Patch(os.path.join(self.specdir, filename),
755 def _guess_orig_prefix(self, orig):
756 """Guess prefix for the orig file"""
757 # Make initial guess about the prefix in the archive
758 filename = orig['filename']
759 name, version = RpmPkgPolicy.guess_upstream_src_version(filename)
761 prefix = "%s-%s/" % (name, version)
763 prefix = orig['filename_base'] + "/"
765 # Refine our guess about the prefix
766 for macro in self._special_directives['setup']:
768 opts = self._setup_macro_opts(args)
770 if opts.no_unpack_default:
771 if opts.unpack_before:
772 srcnum = int(opts.unpack_before)
773 elif opts.unpack_after:
774 srcnum = int(opts.unpack_after)
777 if srcnum == orig['num']:
782 prefix = self.macro_expand(opts.name) + '/'
783 except MacroExpandError as err:
784 gbp.log.warn("Couldn't determine prefix from %%setup "
785 "macro (%s). Using filename base as a "
787 prefix = orig['filename_base'] + '/'
790 prefix = "%s-%s/" % (self.name, self.upstreamversion)
794 def _guess_orig_file(self):
796 Try to guess the name of the primary upstream/source archive.
797 Returns a dict with all the relevant information.
800 sources = self.sources()
801 for num, filename in sorted(sources.items()):
802 src = {'num': num, 'filename': os.path.basename(filename),
804 src['filename_base'], src['archive_fmt'], src['compression'] = \
805 Archive.parse_filename(os.path.basename(filename))
806 if (src['filename_base'].startswith(self.name) and
808 # Take the first archive that starts with pkg name
811 # otherwise we take the first archive
812 elif not orig and src['archive_fmt']:
816 orig['prefix'] = self._guess_orig_prefix(orig)
821 def parse_srpm(srpmfile):
822 """parse srpm by creating a SrcRpmFile object"""
824 srcrpm = SrcRpmFile(srpmfile)
825 except IOError as err:
826 raise GbpError("Error reading src.rpm file: %s" % err)
827 except librpm.error as err:
828 raise GbpError("RPM error while reading src.rpm: %s" % err)
833 def guess_spec_fn(file_list, preferred_name=None):
834 """Guess spec file from a list of filenames"""
836 for filepath in file_list:
837 filename = os.path.basename(filepath)
838 # Stop at the first file matching the preferred name
839 if filename == preferred_name:
840 gbp.log.debug("Found a preferred spec file %s" % filepath)
843 if filename.endswith(".spec"):
844 gbp.log.debug("Found spec file %s" % filepath)
845 specs.append(filepath)
847 raise NoSpecError("No spec file found.")
849 raise NoSpecError("Multiple spec files found (%s), don't know which "
850 "to use." % ', '.join(specs))
854 def guess_spec(topdir, recursive=True, preferred_name=None):
855 """Guess a spec file"""
859 for root, dirs, files in os.walk(topdir):
860 file_list.extend([os.path.join(root, fname) for fname in files])
863 # Skip .git dir in any case
866 return SpecFile(os.path.abspath(guess_spec_fn(file_list, preferred_name)))
869 def guess_spec_repo(repo, treeish, topdir='', recursive=True, preferred_name=None):
871 Try to find/parse the spec file from a given git treeish.
873 topdir = topdir.rstrip('/') + ('/') if topdir else ''
875 file_list = [nam.decode() for (mod, typ, sha, nam) in
876 repo.list_tree(treeish, recursive, topdir) if typ == 'blob']
877 except GitRepositoryError as err:
878 raise NoSpecError("Cannot find spec file from treeish %s, Git error: %s"
880 spec_path = guess_spec_fn(file_list, preferred_name)
881 return spec_from_repo(repo, treeish, spec_path)
884 def spec_from_repo(repo, treeish, spec_path):
885 """Get and parse a spec file from a give Git treeish"""
887 spec = SpecFile(filedata=repo.show('%s:%s' % (treeish, spec_path)).decode())
888 spec.specdir = os.path.dirname(spec_path)
889 spec.specfile = os.path.basename(spec_path)
891 except GitRepositoryError as err:
892 raise NoSpecError("Git error: %s" % err)
895 def string_to_int(val_str):
897 Convert string of possible unit identifier to int.
899 @param val_str: value to be converted
900 @type val_str: C{str}
901 @return: value as integer
904 >>> string_to_int("1234")
906 >>> string_to_int("123k")
908 >>> string_to_int("1234K")
910 >>> string_to_int("1M")
918 if val_str[-1].lower() in units:
919 return int(val_str[:-1]) * units[val_str[-1].lower()]
924 def split_version_str(version):
926 Parse full version string and split it into individual "version
927 components", i.e. upstreamversion, epoch and release
929 @param version: full version of a package
930 @type version: C{str}
931 @return: individual version components
934 >>> sorted(split_version_str("1").items())
935 [('epoch', None), ('release', None), ('upstreamversion', '1')]
936 >>> sorted(split_version_str("1.2.3-5.3").items())
937 [('epoch', None), ('release', '5.3'), ('upstreamversion', '1.2.3')]
938 >>> sorted(split_version_str("3:1.2.3").items())
939 [('epoch', '3'), ('release', None), ('upstreamversion', '1.2.3')]
940 >>> sorted(split_version_str("3:1-0").items())
941 [('epoch', '3'), ('release', '0'), ('upstreamversion', '1')]
943 ret = {'epoch': None, 'upstreamversion': None, 'release': None}
945 e_vr = version.split(":", 1)
947 v_r = e_vr[0].split("-", 1)
949 ret['epoch'] = e_vr[0]
950 v_r = e_vr[1].split("-", 1)
951 ret['upstreamversion'] = v_r[0]
953 ret['release'] = v_r[1]
958 def compose_version_str(evr):
960 Compose a full version string from individual "version components",
961 i.e. epoch, version and release
963 @param evr: dict of version components
964 @type evr: C{dict} of C{str}
965 @return: full version
968 >>> compose_version_str({'epoch': '', 'upstreamversion': '1.0'})
970 >>> compose_version_str({'epoch': '2', 'upstreamversion': '1.0', 'release': None})
972 >>> compose_version_str({'epoch': None, 'upstreamversion': '1', 'release': '0'})
974 >>> compose_version_str({'epoch': '2', 'upstreamversion': '1.0', 'release': '2.3'})
976 >>> compose_version_str({'epoch': '2', 'upstreamversion': '', 'release': '2.3'})
978 if 'upstreamversion' in evr and evr['upstreamversion']:
980 if 'epoch' in evr and evr['epoch']:
981 version += "%s:" % evr['epoch']
982 version += evr['upstreamversion']
983 if 'release' in evr and evr['release']:
984 version += "-%s" % evr['release']
990 def filter_version(evr, *keys):
992 Remove entry from the version dict
994 @param evr: dict of version components
995 @type evr: C{dict} of C{str}
996 @param keys: keys to remove
998 @return: new version dict
999 @rtype: C{dict} of C{str}
1001 >>> sorted(list(filter_version({'epoch': 'foo', 'upstreamversion': 'bar', 'vendor': 'baz'}, 'vendor').keys()))
1002 ['epoch', 'upstreamversion']
1003 >>> list(filter_version({'epoch': 'foo', 'upstreamversion': 'bar', 'revision': 'baz'}, 'epoch', 'revision').keys())
1006 return {k: evr[k] for k in evr if k not in keys}
1009 # vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: