[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[SCRIPT] Autogenerating BuildRequires for specfiles



Whilst attempting to package crystalspace I got sick of scanning the
configure.ac by hand, and wrote a script to try to automate this.

Attached is the result; you give it the path to an unpacked/prepped
source tree, and it looks inside at the
configure.ac/configure.in/configure scripts (if any), scouring them for
header file references, then using rpm and yum to figure out what
provides them, and finally spits out a sorted list of BuildRequires:
lines.

It uses some special-casing/heuristics/hacks to try to generate
something sane.

This was useful to me, and I suspect to other, so perhaps (suitably
renamed) it could live in the rpmdevtools package?

If anyone's familiar with the yum api, there's some major optimization
that could be done (I call out to yum each time, rather than setting it
up in code and amortizing the startup costs)

Hope this helps
Dave
#!/usr/bin/env python
#
# autospec.py - figure out the BuildRequires from a prepped source tree
#
# Copyright (c) David Malcolm <dmalcolm redhat com>
#
# 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 <foo.h> 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 <string>':
            # 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

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]