123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- #!/usr/bin/python -tt
- # vim: ai ts=4 sts=4 et sw=4
- # Copyright (c) 2009 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; version 2 of the License
- #
- # 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.
- """
- Overview of deb2spectacle:
- Tool to convert debian file to spectacle compatibile YAML/spec
- The input files can be:
- """
- INPUT_SRC="""
- 1. "control" file for only package metainfo
- 2. "debian/" directory that contains all the deb files
- 3. tarball "debian.tar.gz" which contain all the deb files
- """
- #TODO: 4. gzipped diff file with names *.diff.gz which include all the content of deb files
- #TODO: 5. binary .deb support using pure python parser
- import os, sys
- import re
- import string
- # spectacle modules
- from spectacle.convertor import *
- from spectacle.dumper import *
- from spectacle import logger
- from spectacle import deb822
- from spectacle.__version__ import VERSION
- # global constants
- DEBTARBALL = 'debian.tar.gz'
- class DebConvertor(Convertor):
- """ Convertor for deb keys to yaml ones """
- def __init__(self):
- deb_cv_table = {
- 'Section': 'Group',
- 'Build-Depends': 'PkgBR',
- 'Build-Depends-Indep': 'PkgBR',
- 'Build-Conflicts': 'BuildConflicts',
- 'Build-Conflicts-Indep': 'BuildConflicts',
- 'Depends': 'Requires',
- 'Pre-Depends': 'RequiresPre',
- 'Pre-Depends': 'RequiresPre',
- 'Suggests': 'Requires', # FIXME
- 'Recommends': 'Requires', # FIXME
- 'Replaces': 'Obsoletes',
- 'Breaks': 'Conflicts',
- 'Homepage': 'URL',
- 'Architecture': 'BuildArch',
- 'Vcs-Git': 'SCM',
- 'Vcs-Svn': 'SCM',
- 'Vcs-Cvs': 'SCM',
- 'Vcs-Hg': 'SCM',
- }
- self.re_debvar = re.compile('\$\{([^}]+)\}', re.M)
- self.debvars = {
- 'shlibs:Depends': '', # spec autodep/autopro can handle it
- 'shlibs:Pre-Depends': '',
- 'misc:Depends': '',
- 'python:Depends': 'python',
- 'python:Provides': '',
- 'perl:Depends': 'perl',
- 'binary:Version': '%{version}-%{release}',
- 'source:Version': '%{version}-%{release}',
- 'Source-Version': '%{version}-%{release}', #legacy one
- # TODO more
- }
- self.depkeys = (
- 'PkgBR',
- 'Requires',
- 'RequiresPre',
- 'Provides',
- 'Obsoletes',
- 'Conflicts',
- 'BuildConflicts',
- )
- self.re_userfield = re.compile('^X[BCS]+-.*')
- self.dropkeys = (
- 'Maintainer',
- 'Uploaders',
- 'Changed-By',
- 'Priority',
- 'Essential',
- 'Standards-Version',
- 'Vcs-Browser',
- 'Vcs-Bzr'
- )
- # regex of depends compare
- self.re_depcomp = re.compile('\(([<>=]+)\s*([^)]*)\)')
- # regex of depends about os and arch
- self.re_deposarch = re.compile('(.*)\[[^]]*\](.*)$')
- Convertor.__init__(self, deb_cv_table)
- def _replace_var(self, val):
- if isinstance(val, list):
- return val
- while self.re_debvar.search(val):
- nval = val
- for m in self.re_debvar.finditer(val):
- var = m.group(1)
- if var in self.debvars:
- rep = self.debvars[var]
- nval = nval.replace('${%s}' % var, rep)
- if val == nval:
- break
- val = nval
- return val
- def _conv_dep(self, single):
- def comp_replace(dep):
- if not dep:
- return None
- dep = dep.strip()
- m = self.re_deposarch.search(dep)
- if m:
- (old, name, rest) = m.group(0, 1, 2)
- dep = dep.replace(old,'%s %s' %(name, rest))
- m = self.re_depcomp.search(dep)
- if m:
- (old, op, ver) = m.group(0, 1, 2)
- if op == '>>':
- op = '>'
- elif op == '<<':
- op = '<'
- dep = dep.replace(old, '%s %s' %(op, ver))
- return dep
- return filter(lambda x:x, map(comp_replace, str(single).split(',')))
- def _translate_keys(self, items):
- """ override parent class method for pre-processing
- """
- #! at this point, all keys name have been translated
- # drop debian only keys
- for key in items.keys():
- if key in self.dropkeys or self.re_userfield.match(key):
- del items[key]
- # split 'Description' to 'Summary' + 'Description'
- lines = items['Description'].splitlines()
- items['Summary'] = lines[0]
- items['Description'] = '\n'.join(lines[1:])
- # expand all debian variables
- for key, val in items.iteritems():
- items[key] = self._replace_var(val)
- # depends keys handling
- for key, val in items.iteritems():
- if key in self.depkeys:
- items[key] = self._conv_dep(val)
- # architecture translate
- if 'BuildArch' in items:
- if items['BuildArch'] == 'any':
- items['BuildArch'] = 'noarch'
- # otherwise, no BuildArch needed
- del items['BuildArch']
- Convertor._translate_keys(self, items)
- class DebInfo:
- """ Container of package information from debian files """
- def __init__(self, binver, license):
- self.fields = {'Version': binver,
- 'License': license,
- 'Release': '1',
- }
- self.pkgname = None
- self.sec_section = None
- def _get_subpkgname(self, raw):
- if raw.startswith(self.pkgname):
- left = raw.lstrip(self.pkgname)
- try:
- int(left)
- # should be main pkg
- return self.pkgname
- except ValueError:
- # not pure number string
- if left.startswith('-'):
- # sub packages with main-name prefix
- subname = left.lstrip('-')
- if subname == 'dev':
- # the rpm tradition
- return 'devel'
- else:
- return subname
- # return orignal value without special recog
- return raw
- def format_filelist(self, filelist):
- """ format the debian filelist format
- """
- ret = []
- for file in filelist:
- file = string.replace (file, 'debian/tmp', '')
- file = file.strip()
- if file.find (' ') != -1:
- base = file.split (' ')[0].split ('/')[-1]
- dir = file.split (' ')[1]
- file = '%s/%s' % (dir, base)
- ret.append (file)
- return ret
- def feed(self, ctl, rules=None):
- """ feed in the file content of:
- ctl: "control"
- rules: "rules", optional
- """
- for para in deb822.Deb822.iter_paragraphs(ctl.splitlines()):
- items = dict(para.items())
- if 'Source' in items:
- # Source paragraph, and must be the 1st para
- items['Name'] = items['Source']
- del items['Source']
- items['Sources'] = '%s-%s.tar.gz' % (items['Name'], self.fields['Version'])
- self.fields.update(items)
- self.pkgname = items['Name']
- self.src_section = items['Section']
- else:
- # confirm the format of input
- if 'Package' not in items or not self.pkgname:
- logger.error('input debian control is in wrong format')
- bpkg = items['Package']
- del items['Package']
- subpkg = self._get_subpkgname(bpkg)
- if subpkg == self.pkgname:
- # main package
- if os.path.isdir('debian') and os.path.isfile('debian/%s.install' % (subpkg)):
- items['Files'] = self.format_filelist (open('debian/%s.install' % (subpkg)).readlines())
- self.fields.update(items)
- else:
- # sub-packages
- if 'SubPackages' not in self.fields:
- self.fields['SubPackages'] = []
- if os.path.isdir('debian') and os.path.isfile('debian/%s.install' % (subpkg)):
- items['Files'] = self.format_filelist (open('debian/%s.install' % (subpkg)).readlines())
- items['Name'] = subpkg
- if subpkg == bpkg:
- items['AsWholeName'] = 'True'
- if 'Section' not in items:
- items['Section'] = self.src_section
- self.fields['SubPackages'].append(items)
- def all_info(self):
- return self.fields
- def convert_deb(ctl, binver, license, outdir):
- """ Generate spectacle files based on content of debain/control
- argument :ctl contain the content of debian/control
- """
- convertor = DebConvertor()
- # feed in the debian/control file to parser
- debi = DebInfo(binver, license)
- debi.feed(ctl)
- # Dump data to yaml
- dumper = SpectacleDumper(opath = '%s/%s.yaml' % (outdir, debi.pkgname))
- spec_fpath = dumper.dump(convertor.convert(debi.all_info()))
- logger.info('YAML and SPEC file are created with pkg name %s in dir: %s' % (debi.pkgname, outdir))
- def get_binver_from_dsc(dsc_fp):
- re_ver = re.compile('^Version:\s*(.*)')
- try:
- with open(dsc_fp) as f:
- for line in f:
- line = line.strip()
- if not line:
- continue
- m = re_ver.match(line)
- if m:
- return m.group(1)
- logger.error('cannot found version information in dsc file %s, please specify version using -V|--version' % dsc_fp)
- except OSError:
- logger.error('cannot read dsc file %s' % dsc_fp)
- def parse_options(args):
- import optparse
- usage = """Usage: %prog [options] [debian-path]
- The "debian-path" can be:""" + INPUT_SRC
- parser = optparse.OptionParser(usage, version=VERSION)
- parser.add_option("-o", "--outdir", type="string",
- dest="outdir", default=None,
- help="Path of output yaml/spec files")
- parser.add_option("-V", "--binver", type="string",
- help="The version string of binary packages")
- parser.add_option("", "--dsc", type="string",
- help='The path of "dsc" file for version information')
- parser.add_option("-L", "--license", type="string",
- help="The License string of this package")
- return parser.parse_args()
- if __name__ == '__main__':
- """ Main Function """
- (opts, args) = parse_options(sys.argv[1:])
- if not opts.binver and not opts.dsc:
- logger.error('Must provide one of the two options "-V|--binver" and "--dsc"')
- if not opts.license:
- logger.error('Must provide the License string using "-L|--license" options')
- ctl_path = None
- if not args:
- if os.path.isfile('control'):
- ctl_path = os.path.abspath('./control')
- logger.info('using "control" under current directory')
- elif os.path.isdir('debian') and os.path.isfile('debian/control'):
- ctl_path = os.path.abspath('./debian/control')
- logger.info('using "control" under "debian/" directory')
- elif os.path.isfile(DEBTARBALL):
- logger.info('using "%s" under current directory' % DEBTARBALL)
- else:
- # no debian packaging file found in cwd
- logger.error('found no debian files in current dir, please specify one')
- else:
- if not args[0].endswith(DEBTARBALL):
- ctl_path = os.path.abspath(os.path.expanduser(args[0]))
- if ctl_path:
- if not os.path.exists(ctl_path):
- # input file does not exist
- logger.error("%s: File does not exist" % ctl_path)
- ctl_cont = open(ctl_path).read()
- else: # means the input is debian.tar.gz
- # extract "control" from it
- import tarfile
- if tarfile.is_tarfile(DEBTARBALL):
- tar = tarfile.open(DEBTARBALL)
- found = False
- for member in tar.getmembers():
- if member.name == 'debian/control' and \
- member.isfile():
- found = True
- break
- if found:
- ctl_cont = tar.extractfile('debian/control').read()
- tar.close()
- logger.info('using "debian/control" in debian.tar.gz archive')
- else:
- logger.error('debian.tar.gz contains no "debian/control" file')
- else:
- logger.error('debian.tar.gz is not a valid tarball')
-
- # prepare the output folder
- if opts.outdir:
- outdir = os.path.abspath(os.path.expanduser(opts.outdir))
- else:
- outdir = os.path.abspath('./spectacle')
- if opts.binver:
- binver = opts.binver
- else:
- # opts.dsc must be valid
- binver = get_binver_from_dsc(opts.dsc) # TODO
- binver = binver.split('-',)[0]
- if not os.path.exists(outdir):
- os.makedirs(outdir)
- convert_deb(ctl_cont, binver, opts.license, outdir)
|