#!/usr/bin/env python # # autospec.py - figure out the BuildRequires from a prepped source tree # # Copyright (c) David Malcolm # # 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. import re from os.path import join, exists import rpm import os import sys class Specdata: """ This class accumulates results from sniffing BuildRequires """ def __init__(self): self.brDict = {} self.missingHeaders = {} self.showReasons = False def __str__(self): result = "" for br in sorted(self.brDict): result += "BuildRequires: %s\n" % br if self.showReasons: reasons = self.brDict[br] for reason in reasons: result += "# (for %s)\n" % reason for h in self.missingHeaders: print "# unknown header: #include <%s>" % h return result def add_build_requires(self, package, reason): if package in self.brDict: if reason not in self.brDict[package]: self.brDict[package].append(reason) else: self.brDict[package] = [reason] def add_missing_header(self, header): self.missingHeaders[header] = True def get_package_for_file(absPath): ts = rpm.TransactionSet() mi = ts.dbMatch('basenames', absPath) for h in mi: return h['name'] def get_yum_whatprovides_file(absPath): # FIXME: use yum API to amortize startup expense of all these yum queries print '# yum whatprovides %s' % absPath for line in os.popen('yum whatprovides %s' % absPath): m = re.match('(.*)\.(.*) : (.*)', line) if m: return m.group(1) # Don't bother checking for headers; these are for Windows support etc headerBlacklist = """ windows.h ddraw.h dsound.h dinput.h winsock.h """.strip().split('\n') #print headerBlacklist class BRSniffer: """ Given a prepped source tree, try to guess appropriate BuildRequires lines for the specfile. A function really, but it's useful as a class for chopping up into subroutines """ def __init__(self): self.spec = Specdata() self.relHeaders = {} # header files processed so far def scan_any_configure_files(self, treePath): # Scan for configure.ac/configure.in first, then the full configure script (to try to catch everything) for acFilename in ['configure.ac', 'configure.in', 'configure']: path = join(treePath, acFilename) if exists(path): print "# scanning %s" % acFilename for line in open(path): # print line, m = re.match(r'.*#include \<(.*)\>.*', line) if m: relHeader = m.group(1) # e.g. "X11/Xaw/Form.h" self.process_rel_header(relHeader) def process_rel_header(self, relHeader): """ Handle a #include line, trying to add whatever provides foo.h """ inclusion = '#include <%s>' % relHeader if relHeader in self.relHeaders: # we've already seen this header: return self.relHeaders[relHeader]=True # in case we see it again # Ignore certain headers that look like MS Windows support etc if relHeader in headerBlacklist: return # Perl and Python headers live in other paths that won't get found # without specialcasing (or actually parsing the m4): if relHeader == 'perl.h': self.spec.add_build_requires('perl-devel', reason = inclusion) return if relHeader == 'Python.h': self.spec.add_build_requires('python-devel', reason = inclusion) return if not re.match('.*\.h', relHeader): # C++-style inclusion e.g. '#include ': # for now, assume this: self.spec.add_build_requires('libstdc++-devel', reason = inclusion) return # FIXME: should search in various paths? absHeader = join('/usr/include', relHeader) if exists(absHeader): # which installed pacakge provides this? # print "Uses: %s " % absHeader package = get_package_for_file(absHeader) if package: self.spec.add_build_requires(package, reason = inclusion) else: # not found: use yum; which package provides this? # print "Not found: %s " % absHeader package = get_yum_whatprovides_file(absHeader) if package: self.spec.add_build_requires(package, reason = inclusion) else: self.spec.add_missing_header(relHeader) def guess_buildrequires(treepath): sniffer = BRSniffer() sniffer.scan_any_configure_files(treepath) return sniffer.spec def usage(): print "%s PATH" % sys.argv[0] print "given the path to a prepped source tree, try to guess a suitable" print "set of BuildRequires: lines for the specfile" # FIXME: would it be better to simply work with a specfile, and figure things out based on buildroot etc? if __name__ == '__main__': try: treepath = sys.argv[1] except: usage() sys.exit(1) s = guess_buildrequires(treepath) print s