Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 695 lines (593 sloc) 23 KB
#!/usr/bin/python
### This python scripts automatically generates an RPMforge SPEC file
### for Perl modules based on CPAN information.
### Example modules:
### perl-Tree-Simple tests META.yml
### perl-Tree-Simple-Visitor tests sub-modules
### perl-Kwiki tests perl Buildrequires epoch
### perl-ExtUtils-FakeConfig tests zip files (unsupported yet)
### More documentation about:
### META.yml http://module-build.sourceforge.net/META-spec-current.html
import sys, os, time, getopt, urllib2, gzip, re, yaml, tarfile, rpm, types
import cElementTree as ElementTree
args = sys.argv[1:]
try:
logname = os.getlogin()
except:
logname = 'dag'
### Chown files if possible
try:
os.umask(0022)
import pwd
pw = pwd.getpwnam(logname)
os.setuid(pw.pw_uid)
os.seteuid(pw.pw_uid)
except:
pass
create = False
debug = False
noarch = True
package_make = False
package_build = False
output = False
realversion = None
authors = []
email = ''
tmppath = '/var/tmp'
license = ''
package_version = None
### Files considered a document:
### Announce ANNOUNCE Artistic ARTISTIC Artistic.txt AUTHORS Bugs BUGS
### Changelog ChangeLog CHANGELOG Changes CHANGES Changes.pod CHANGES.TXT
### Copying COPYING COPYRIGHT Credits CREDITS CREDITS.txt FAQ GNU_GPL.txt
### GNU_LGPL.txt GNU_LICENSE HACKING HISTORY INFO INSTALL INSTALLING
### INSTALL.txt LICENCE LICENSE MANIFEST META.yml NEWS NOTES NOTICE
### PORTING readme README readme.txt README.txt README.TXT RELEASE_NOTES
### SIGNATURE THANKS TODO UPGRADE VERSION *.txt
docfiles = ('^ANNOUNCE', '^Artistic', '^AUTHORS', '^BUGS', '^ChangeLog',
'^Changes', '^Changes.pod', '^COPYING', '^COPYRIGHT', '^CREDITS',
'^FAQ', '^GNU_LICENSE', '^HACKING', '^HISTORY', '^INFO', '^INSTALL',
'^INSTALLING', '^LICENCE', '^LICENSE', '^MANIFEST', '^META.yml',
'^NEWS', '^NOTES', '^NOTICE', '^PORTING', '^README', '^RELEASE_NOTES',
'^ROADMAP', '^SIGNATURE', '^THANKS', '^TODO', '^UPGRADE', '^VERSION',
'^[^/]+.txt$', '^[^/]+.html$')
docdirs = ('contrib/', 'doc/', 'docs/', 'eg/', 'example/', 'examples/',
'htdocs/', 'notes/', 'samples/', 'tutorial/')
authorities = {
'dag': 'Dag Wieers <dag@wieers.com>',
'dries': 'Dries Verachtert <dries@ulyssis.org>',
logname: 'Unknown',
}
licenses = {
'perl': 'Artistic/GPL',
}
### Add proper epochs to perl-dependencies
epochs = ( '5.0.0', '5.6.1', '5.8.0', '5.8.5', '5.8.8' )
def download(url):
filename = os.path.join(tmppath, os.path.basename(url))
try:
st = os.stat(filename)
if st and st.st_mtime + 3600.0 > time.time():
# print >>sys.stderr, "File %s is recent, skip download." % os.path.basename(url)
return True
except:
pass
try:
req = urllib2.Request(url)
fdin = urllib2.urlopen(req)
except Exception, e:
print >>sys.stderr, "Failed to download file from %s, %s" % (url, e)
return False
fdout = open(filename, 'w')
fdout.write(fdin.read())
fdin.close()
fdout.close()
return True
### FIXME: Create own version comparison instead of using RPM's
def vercmp(v1, v2):
return rpm.labelCompare((None, v1, None), (None, v2, None))
def rcut(text, *str):
for s in str:
text = ''.join(text.split(s)[0:-1])
return text
def lcut(text, *str):
for s in str:
text = ''.join(text.split(s)[1:])
return text
def epochify(version):
epoch = 0
for e, v in enumerate(epochs):
if vercmp(str(version), v) >= 0:
epoch = e
return '%s:%s' % (epoch, version)
### WriteMakefile
### NAME => "cpan2rpm",
### VERSION_FROM => "cpan2rpm",
### $] < 5.005 ? () : (
### AUTHOR => 'Erick Calder <ecalder@cpan.org>',
### ABSTRACT_FROM => "cpan2rpm",
### ),
### EXE_FILES => [ "cpan2rpm" ],
### PREREQ_PM => { # e.g., Module::Name => 1.1
### 'ExtUtils::MakeMaker' => 5.4302,
### 'LWP::UserAgent' => 0,
### 'HTTP::Request' => 0,
### },
### dist => {
### COMPRESS => "gzip -9 -vf",
### },
### ;
def parse_makefile(data):
makefile = {}
return makefile
try:
opts, args = getopt.getopt (args, 'acdhno:v',
['create', 'debug', 'help', 'output=', 'version'])
except getopt.error, exc:
print >>sys.stderr, 'dar-perl: %s, try dar-perl.py -h for a list of all the options' % str(exc)
sys.exit(1)
for opt, arg in opts:
if opt in ['-h', '--help']:
pass
elif opt in ['-v', '--version']:
pass
elif opt in ['-d', '--debug']:
debug = True
elif opt in ['-a', '--arch']:
noarch = False
elif opt in ['-o', '--output']:
output = arg
elif opt in ['-c', '--create']:
create = True
if len(args) < 1:
print >>sys.stderr, 'Error: You have to provide a package name.'
sys.exit(1)
package_name = args[0]
package = package_name.replace('::', '-')
modparts = package.split('-')
if len(args) > 1:
package_version = args[1]
if package.startswith('perl-'):
modparts = modparts[1:]
package = '-'.join(modparts)
module = package.replace('-', '::')
### Download latest package list from CPAN
#if not download('ftp://ftp.kulnet.kuleuven.ac.be/pub/mirror/CPAN/modules/02packages.details.txt.gz'):
if not download('http://www.cpan.org/modules/02packages.details.txt.gz'):
print >>sys.stderr, "Error: Failed to download 02packages.details.txt.gz"
sys.exit(1)
### Download latest authors list from CPAN
#if not download('ftp://ftp.kulnet.kuleuven.ac.be/pub/mirror/CPAN/authors/00whois.xml'):
if not download('http://www.cpan.org/authors/00whois.xml'):
print >>sys.stderr, "Error: Failed to download 00whois.xml"
sys.exit(1)
### Find specific package in CPAN package list
modules = []
found = False
fd = gzip.open(os.path.join(tmppath, '02packages.details.txt.gz'), 'r')
for line in fd.readlines():
try:
(pkgmodule, pkgversion, pkgpath) = line.split()
except:
continue
temp = pkgpath.split('/')
temp = temp[-1].split('-')
pkgname = '-'.join(temp[0:-1])
if pkgversion != 'undef' and package == pkgname:
version = pkgversion
module = pkgmodule
module_orig = pkgmodule
path = pkgpath
modules.append(pkgmodule)
found = True
break
elif module == pkgmodule:
print >>sys.stderr, 'Module', module, 'found in package', pkgname
version = None
package = pkgname
path = pkgpath
modules.append(module)
found = True
if not found:
print >>sys.stderr, 'Error: Module', module, 'or package', package, 'not found in CPAN.'
sys.exit(1)
modules.sort()
if package_version:
version = package_version
location = path
#print >>sys.stderr, 'We found package %s with version %s with modules:' % (package, version)
ppath = path.split('/')
mnemo = ppath[2]
### Find specific author in CPAN authors list
tree = ElementTree.ElementTree(file=os.path.join(tmppath, '00whois.xml'))
root = tree.getroot()
for elem in root.getiterator('{http://www.cpan.org/xmlns/whois}cpanid'):
if mnemo == elem.find('{http://www.cpan.org/xmlns/whois}id').text:
try:
authorel = elem.find('{http://www.cpan.org/xmlns/whois}fullname')
emailel = elem.find('{http://www.cpan.org/xmlns/whois}email')
email = emailel.text.replace('@','$').replace('.',',').replace(' at ','$').replace(' dot ',',').replace(' [at] ','$').replace('_dot_',',')
author = "%s <%s>" % (authorel.text, email)
authors.append(author.encode('utf8', 'replace'))
except:
pass
break
### Get the correct version from the source distribution
if version:
sdistname = "%s-%s.tar.gz" % (package, version)
else:
sdistname = None
cdistname = os.path.basename(location)
if not package_version and sdistname != cdistname:
realversion = version
### FIXME: take care of file like Acme-6502-v0.0.6 or something.tgz
### Get the version from the cdistname
m = re.match('[^\d]+([\d\.\w]+).tar.gz', cdistname)
if m:
l = m.groups()
version = l[0]
else:
print >>sys.stderr, 'Warning: Problem retrieving version from %s for package %s.' % (cdistname, package)
# sys.exit(1)
if realversion == 'undef':
print >>sys.stderr, 'Error: Version is undefined. Distribution %s is not a package.' % package
sys.exit(1)
elif realversion == version:
realversion = None
### Try to download distribution
archive = os.path.join(tmppath, cdistname)
#if os.path.isfile(archive):
# os.remove(archive)
source = "http://www.cpan.org/modules/by-module/%s/%s" % (modparts[0], cdistname)
#if not os.path.isfile(archive):
if not download(source):
source = "http://www.cpan.org/authors/id/%s" % location
if not download(source):
source = "http://search.cpan.org/CPAN/authors/id/%s" % location
if not download(source):
print >>sys.stderr, "Error: Failed to download %s" % (source)
### Add %{version} and %{real_version} to source
source = source.replace(version, '%{version}')
if realversion:
source = source.replace(realversion, '%{real_version}')
### Create basedir out of cdistname
#basedir = cdistname.rstrip('.tar.gz')
#basedir = basedir.replace(version, '%{version}').replace(package, '%{real_name}')
#if realversion:
# basedir = basedir.replace(realversion, '%{real_version}')
### Inspect distribution and extract information (%doc, META.yml, arch/noarch)
distfd = tarfile.open(archive, 'r:gz')
### Remove .tar.gz from base (Name-Version)
base = os.path.basename(archive)
base = rcut(base, '.tar', '.zip', '.gz')
docs = []
docsdirs = []
meta = {}
basedir = False
for tarinfo in distfd:
### We assume the first entry is the base directory ?
if not basedir:
if tarinfo.name.endswith('/'):
basedir = tarinfo.name
else:
basedir = base + '/'
### Remove Name-Version/ from filename
filename = lcut(tarinfo.name, basedir)
if filename == tarinfo.name:
print >>sys.stderr, 'Error: Basedir cannot be determined for archive %s.' % archive
sys.exit(1)
### Create %docs directorylist
if tarinfo.isdir() and filename in docdirs:
docsdirs.append(filename)
continue
### Drop directories, test files and empty files
if tarinfo.isdir() or filename.endswith('.t') or tarinfo.size == 0:
continue
### Check if this is a noarch or arch package
if filename.endswith('.c') or filename.endswith('.h') or filename.endswith('.cc') or filename.endswith('.xs'):
noarch = False
continue
### Create %docs filelist based on docfiles
for docre in docfiles:
if docre.endswith('$') and re.search(docre, filename, re.I):
docs.append(filename)
break
elif not docre.endswith('$') and re.search(docre+'[^/]*$', filename, re.I):
docs.append(filename)
break
### Parse META.yml (http://module-build.sourceforge.net/META-spec-current.html)
if filename == 'META.yml':
member = distfd.getmember(tarinfo.name)
try:
meta = yaml.load(distfd.extractfile(member).read())
if debug:
print >>sys.stderr, 'Debug: META.yml contains the following info:'
for key in meta.keys():
print >>sys.stderr, ' %s: %s' % (key, meta[key])
except:
pass
continue
### Check whether we need to use perl(Module::Build)
elif filename == 'Makefile.PL':
package_make = True
member = distfd.getmember(tarinfo.name)
try:
makefile = parse_makefile_pl(distfd.extractfile(member).read())
if debug:
print >>sys.stderr, 'Debug: Makefile.PL contains the following info:'
for key in makefile.keys():
print >>sys.stderr, ' %s: %s' % (key, makefile[key])
except:
pass
elif filename == 'Build.PL':
package_build = True
docs.sort()
docsdirs.sort()
### Now macrofy the basedir for use in the SPEC file and remove trailing /
basedir = basedir.replace(version, '%{version}').replace(package, '%{real_name}')
if realversion:
basedir = basedir.replace(realversion, '%{real_version}')
basedir = rcut(basedir, '/')
#if os.path.isfile(archive):
# os.remove(archive)
### Compare deducted information with META.yml
if meta.has_key('name') and meta['name'] != package:
print >>sys.stderr, 'Warning: Module %s is part of distribution %s. Please use that instead.' % (package, meta['name'])
# sys.exit(1)
if meta.has_key('version') and str(meta['version']) != version:
print >>sys.stderr, 'Warning: Module %s has version mismatch between archive (%s) and META.yml (%s).' % (package, version, meta['version'])
if meta.has_key('type') and meta['type'] != 'module':
print >>sys.stderr, 'Error: Distribution %s is not a package.' % package
sys.exit(1)
if meta.has_key('author'):
authors = []
if isinstance(meta['author'], types.StringType):
author = meta['author'].replace('@','$').replace('.',',').replace(' at ','$').replace(' dot ',',').replace(' [at] ','$').replace('_dot_',',')
authors.append(meta['author'].encode('utf8', 'replace'))
elif isinstance(meta['author'], types.ListType):
for author in meta['author']:
author = author.replace('@','$').replace('.',',').replace(' at ','$').replace(' dot ',',').replace(' [at] ','$').replace('_dot_',',')
authors.append(author.encode('utf8', 'replace'))
### FIXME: The license code assumes a lot
if meta.has_key('license') and meta['license'] in licenses.keys():
license = licenses[meta['license']]
else:
artistic = False
gpl = False
lgpl = False
for doc in docs:
if doc in ('Artistic', 'ARTISTIC', 'Artistic.txt'):
artistic = True
if doc in ('Copying', 'COPYING', 'GNU_GPL.txt', 'GNU_LICENSE'):
gpl = True
if doc in ('GNU_LGPL.txt'):
lgpl = True
if artistic:
license = 'Artistic'
if gpl:
if license: license = license + '/'
license = license + 'GPL'
if lgpl:
if license: license = license + '/'
license = license + 'LGPL'
if not license:
license = 'Artistic/GPL'
print >>sys.stderr, 'Warning: License could not be determined.'
### FIXME: Get description from website
if meta.has_key('abstract') and meta['abstract']:
summary = meta['abstract'].rstrip('.')
description = meta['abstract'].rstrip('.') + ".\n"
else:
summary = "Perl module named %s" % package
description = "perl-%s is a Perl module.\n" % package
print >>sys.stderr, 'Warning: No abstract found.'
#if len(modules) == 1:
# description = description + "\nThis package contains the following Perl module:\n\n " + module + "\n"
#else:
# description = description + "\nThis package contains the following Perl modules:\n\n"
# for module in modules:
# description = description + ' ' + module + "\n"
if meta.has_key('build_requires') and meta['build_requires'] and meta['build_requires'].has_key('perl-Inline'):
noarch = False
if meta.has_key('requires') and meta['requires'] and meta['requires'].has_key('perl-Inline'):
noarch = False
if debug:
print >>sys.stderr, package, version, "perl-%s/perl-%s.spec" % (package, package)
if noarch:
print >>sys.stderr, 'noarch package'
else:
print >>sys.stderr, 'arch package'
if realversion:
print >>sys.stderr, 'source has different version format than CPAN (%s vs %s)' % (version, realversion)
print >>sys.stderr, 'Found following docs:', ' '.join(docs)
print >>sys.stderr, 'Distribution archive %s contains:' % cdistname
for file in distfd.getnames():
print >>sys.stderr, ' ', file
if create:
output = "perl-%s/perl-%s.spec" % (package, package)
### See if we have to write a file or write to stdout
if output:
if os.path.exists(output):
print >>sys.stderr, 'Error: File %s already exists.' % output
sys.exit(1)
outputdir = os.path.dirname(output)
if outputdir and not os.path.exists(outputdir):
os.mkdir(outputdir)
try:
out = open(output, 'w')
except:
print >>sys.stderr, 'Error: Cannot write %s' % output
sys.exit(1)
else:
out = sys.stdout
print >>out, '# $Id$'
print >>out, '# Authority:', logname
for author in authors:
print >>out, "# Upstream: %s" % author
print >>out
print >>out, '%define perl_vendorlib %(eval "`%{__perl} -V:installvendorlib`"; echo $installvendorlib)'
print >>out, '%define perl_vendorarch %(eval "`%{__perl} -V:installvendorarch`"; echo $installvendorarch)'
print >>out
print >>out, '%define real_name', package
if realversion:
print >>out, '%define real_version', realversion
print >>out
print >>out, "Summary: %s" % summary
print >>out, "Name: perl-%s" % package
print >>out, 'Version:', version
print >>out, 'Release: 1%{?dist}'
print >>out, 'License: %s' % license
print >>out, 'Group: Applications/CPAN'
print >>out, "URL: http://search.cpan.org/dist/%s/" % package
print >>out
print >>out, "Source: %s" % source
print >>out, 'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root'
print >>out
if noarch:
print >>out, "BuildArch: noarch"
### FIXME: Add BuildRequires from Makefile.PL ?
if meta.has_key('requires') and meta['requires'] and meta['requires'].has_key('perl'):
### FIXME: lstrip 'v' from version if it is a string
print >>out, "BuildRequires: perl >= %s" % epochify(meta['requires']['perl'])
else:
print >>out, 'BuildRequires: perl'
if package_build and not package_make:
print >>out, 'BuildRequires: perl(Module::Build)'
if meta.has_key('build_requires'):
buildrequires = meta['build_requires'].keys()
buildrequires.sort()
for key in buildrequires:
if meta['build_requires'][key]:
### FIXME: lstrip 'v' from version if it is a string
print >>out, "BuildRequires: perl(%s) >= %s" % (key, meta['build_requires'][key])
else:
print >>out, "BuildRequires: perl(%s)" % key
if meta.has_key('requires') and meta['requires'] and meta['requires'].has_key('perl'):
### FIXME: lstrip 'v' from version if it is a string
print >>out, "Requires: perl >= %s" % epochify(meta['requires']['perl'])
### Requires are extracted by RPM itself
#print "Requires: perl"
#if meta.has_key('requires'):
# for key in meta['requires']:
# if meta['requires'][key]:
# print >>out, "Requires: perl(%s) >= %s" % (key, meta['requires'][key])
# else:
# print >>out, "Requires: perl(%s)" % key
if meta.has_key('conflicts'):
for key in meta['conflicts']:
print >>out, "Conflict: perl(%s)" % key
print >>out
print >>out, "%description"
print >>out, description
print >>out, "%prep"
print >>out, "%%setup -n %s" % basedir
print >>out
print >>out, "%build"
if noarch:
if package_make:
# print >>out, '%{__perl} Makefile.PL INSTALLDIRS="vendor" destdir="%{buildroot}"'
print >>out, '%{__perl} Makefile.PL INSTALLDIRS="vendor" PREFIX="%{buildroot}%{_prefix}"'
print >>out, '%{__make} %{?_smp_mflags}'
else:
print >>out, '#%{__perl} Makefile.PL INSTALLDIRS="vendor" PREFIX="%{buildroot}%{_prefix}"'
print >>out, '#%{__make} %{?_smp_mflags}'
print >>out, '%{__perl} Build.PL'
print >>out, './Build'
else:
if package_make:
# print >>out, 'CFLAGS="%{optflags}" %{__perl} Makefile.PL INSTALLDIRS="vendor" destdir="%{buildroot}"'
print >>out, 'CFLAGS="%{optflags}" %{__perl} Makefile.PL INSTALLDIRS="vendor" PREFIX="%{buildroot}%{_prefix}"'
print >>out, '%{__make} %{?_smp_mflags} OPTIMIZE="%{optflags}"'
else:
print >>out, '#CFLAGS="%{optflags}" %{__perl} Makefile.PL INSTALLDIRS="vendor" PREFIX="%{buildroot}%{_prefix}"'
print >>out, '#%{__make} %{?_smp_mflags} OPTIMIZE="%{optflags}"'
print >>out, 'CFLAGS="%{optflags}" %{__perl} Build.PL'
print >>out, './Build'
print >>out
print >>out, '%install'
print >>out, '%{__rm} -rf %{buildroot}'
if package_make:
# print >>out, '%{__make} install'
print >>out, '%{__make} pure_install'
else:
print >>out, '#%{__make} pure_install'
print >>out, 'PERL_INSTALL_ROOT="%{buildroot}" ./Build install installdirs="vendor"'
print >>out
print >>out, '### Clean up buildroot'
#if noarch:
# print >>out, '%{__rm} -rf %{buildroot}%{perl_archlib} %{buildroot}%{perl_vendorarch}'
#else:
# print >>out, '%{__rm} -rf %{buildroot}%{perl_archlib} %{buildroot}%{perl_vendorarch}/auto/*{,/*{,/*}}/.packlist'
print >>out, 'find %{buildroot} -name .packlist -exec %{__rm} {} \;'
print >>out
if docsdirs:
print >>out, '### Clean up docs'
print >>out, 'find', ' '.join(docsdirs), '-type f -exec %{__chmod} a-x {} \;'
print >>out
print >>out, '%clean'
print >>out, '%{__rm} -rf %{buildroot}'
print >>out
### FIXME: Create %files list based on test-build or source-tree ?
print >>out, '%files'
print >>out, '%defattr(-, root, root, 0755)'
### Check DOCS in archive from "grep -h '^%doc' /dar/rpms/perl*/perl*.spec | grep -v mandir | xargs -n 1 | sort | uniq"
if not docsdirs:
print >>out, '%doc', ' '.join(docs)
else:
print >>out, '%doc', ' '.join(docs), ' '.join(docsdirs)
if len(modules) > 4:
print >>out, '%doc %{_mandir}/man3/*.3pm*'
else:
for module in modules:
print >>out, "%%doc %%{_mandir}/man3/%s.3pm*" % module
### FIXME: Use modules and module_orig to create %files list
if noarch:
### Print directory entries (if any)
if modparts[:-1]:
str = '%dir %{perl_vendorlib}/'
for nr, part in enumerate(modparts[:-1]):
str = str + "%s/" % modparts[nr]
print >>out, str
### Print module directory
str = '#%{perl_vendorlib}/'
for nr, part in enumerate(modparts):
str = str + "%s/" % modparts[nr]
print >>out, str
### Print module
if modparts[:-1]:
str = '%{perl_vendorlib}/'
for nr, part in enumerate(modparts[:-1]):
str = str + "%s/" % modparts[nr]
print >>out, str + "%s.pm" % modparts[-1]
else:
print >>out, '%%{perl_vendorlib}/%s.pm' % modparts[0]
else:
### Print auto directory entries (if any)
if modparts[:-1]:
str = '%dir %{perl_vendorarch}/auto/'
for nr, part in enumerate(modparts[:-1]):
str = str + "%s/" % modparts[nr]
print >>out, str
### Print auto module directory
str = '%{perl_vendorarch}/auto/'
for nr, part in enumerate(modparts):
str = str + "%s/" % modparts[nr]
print >>out, str
### Print directory entries (if any)
if modparts[:-1]:
str = '%dir %{perl_vendorarch}/'
for nr, part in enumerate(modparts[:-1]):
str = str + "%s/" % modparts[nr]
print >>out, str
### Print module directory
str = '%{perl_vendorarch}/'
for nr, part in enumerate(modparts[:-1]):
str = str + "%s/" % modparts[nr]
print >>out, str + "%s.pm" % modparts[-1]
print >>out
print >>out, '%changelog'
print >>out, '* %s %s - %s-1' % (time.strftime('%a %b %d %Y', time.localtime()), authorities[logname], version)
print >>out, '- Initial package. (using DAR)'
if output:
out.close()
sys.exit(0)
Jump to Line
Something went wrong with that request. Please try again.