a35451203e8a3813abe79c0385b57ac7c4130ae0
"""distutils.command.bdist_rpm
Implements the Distutils 'bdist_rpm' command (create RPM source and binary
# This module should be kept compatible with Python 2.1.
__revision__
= "$Id: bdist_rpm.py,v 1.46 2004/11/10 22:23:14 loewis Exp $"
from distutils
.core
import Command
from distutils
.debug
import DEBUG
from distutils
.util
import get_platform
from distutils
.file_util
import write_file
from distutils
.errors
import *
from distutils
import log
class bdist_rpm (Command
):
description
= "create an RPM distribution"
"base directory for creating built distributions"),
"base directory for creating RPMs (defaults to \"rpm\" under "
"--bdist-base; must be specified for RPM 2)"),
"directory to put final RPM files in "
"(and .spec files if --spec-only)"),
"path to Python interpreter to hard-code in the .spec file "
"(default: \"python\")"),
"hard-code the exact path to the current Python interpreter in "
"only regenerate spec file"),
"only generate source RPM"),
"only generate binary RPM"),
"use bzip2 instead of gzip to create source distribution"),
# More meta-data: too RPM-specific to put in the setup script,
# but needs to go in the .spec file -- so we make these options
# to "bdist_rpm". The idea is that packagers would put this
# info in setup.cfg, although they are of course free to
# supply it on the command line.
('distribution-name=', None,
"name of the (Linux) distribution to which this "
"RPM applies (*not* the name of the module distribution!)"),
"package classification [default: \"Development/Libraries\"]"),
"RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") "
"[default: maintainer or author from setup script]"),
"RPM packager (eg. \"Jane Doe <jane@example.net>\")"
"list of documentation files (space or comma-separated)"),
"capabilities provided by this package"),
"capabilities required by this package"),
"capabilities which conflict with this package"),
('build-requires=', None,
"capabilities required to build this package"),
"capabilities made obsolete by this package"),
"do not automatically calculate dependencies"),
# Actions to take when building RPM
"don't clean up RPM build directory"),
"clean up RPM build directory [default]"),
('use-rpm-opt-flags', None,
"compile with RPM_OPT_FLAGS when building from source RPM"),
('no-rpm-opt-flags', None,
"do not pass any RPM CFLAGS to compiler"),
"RPM 3 compatibility mode (default)"),
"RPM 2 compatibility mode"),
# Add the hooks necessary for specifying custom scripts
"Specify a script for the PREP phase of RPM building"),
"Specify a script for the BUILD phase of RPM building"),
"Specify a script for the pre-INSTALL phase of RPM building"),
('install-script=', None,
"Specify a script for the INSTALL phase of RPM building"),
"Specify a script for the post-INSTALL phase of RPM building"),
"Specify a script for the pre-UNINSTALL phase of RPM building"),
('post-uninstall=', None,
"Specify a script for the post-UNINSTALL phase of RPM building"),
"Specify a script for the CLEAN phase of RPM building"),
"Specify a script for the VERIFY phase of the RPM build"),
# Allow a packager to explicitly force an architecture
"Force an architecture onto the RPM build process"),
boolean_options
= ['keep-temp', 'use-rpm-opt-flags', 'rpm3-mode',
negative_opt
= {'no-keep-temp': 'keep-temp',
'no-rpm-opt-flags': 'use-rpm-opt-flags',
'rpm2-mode': 'rpm3-mode'}
def initialize_options (self
):
self
.distribution_name
= None
self
.install_script
= None
self
.verify_script
= None
self
.pre_uninstall
= None
self
.post_uninstall
= None
self
.build_requires
= None
self
.use_rpm_opt_flags
= 1
def finalize_options (self
):
self
.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))
if self
.rpm_base
is None:
raise DistutilsOptionError
, \
"you must specify --rpm-base in RPM 2 mode"
self
.rpm_base
= os
.path
.join(self
.bdist_base
, "rpm")
self
.python
= sys
.executable
raise DistutilsOptionError
, \
"--python and --fix-python are mutually exclusive options"
raise DistutilsPlatformError
, \
("don't know how to create RPM "
"distributions on platform %s" % os
.name
)
if self
.binary_only
and self
.source_only
:
raise DistutilsOptionError
, \
"cannot supply both '--source-only' and '--binary-only'"
# don't pass CFLAGS to pure python distributions
if not self
.distribution
.has_ext_modules():
self
.use_rpm_opt_flags
= 0
self
.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
self
.finalize_package_data()
def finalize_package_data (self
):
self
.ensure_string('group', "Development/Libraries")
self
.ensure_string('vendor',
"%s <%s>" % (self
.distribution
.get_contact(),
self
.distribution
.get_contact_email()))
self
.ensure_string('packager')
self
.ensure_string_list('doc_files')
if type(self
.doc_files
) is ListType
:
for readme
in ('README', 'README.txt'):
if os
.path
.exists(readme
) and readme
not in self
.doc_files
:
self
.doc_files
.append(readme
)
self
.ensure_string('release', "1")
self
.ensure_string('serial') # should it be an int?
self
.ensure_string('distribution_name')
self
.ensure_string('changelog')
# Format changelog correctly
self
.changelog
= self
._format
_changelog
(self
.changelog
)
self
.ensure_filename('icon')
self
.ensure_filename('prep_script')
self
.ensure_filename('build_script')
self
.ensure_filename('install_script')
self
.ensure_filename('clean_script')
self
.ensure_filename('verify_script')
self
.ensure_filename('pre_install')
self
.ensure_filename('post_install')
self
.ensure_filename('pre_uninstall')
self
.ensure_filename('post_uninstall')
# XXX don't forget we punted on summaries and descriptions -- they
# should be handled here eventually!
# Now *this* is some meta-data that belongs in the setup script...
self
.ensure_string_list('provides')
self
.ensure_string_list('requires')
self
.ensure_string_list('conflicts')
self
.ensure_string_list('build_requires')
self
.ensure_string_list('obsoletes')
self
.ensure_string('force_arch')
# finalize_package_data ()
print "before _get_package_data():"
print "vendor =", self
.vendor
print "packager =", self
.packager
print "doc_files =", self
.doc_files
print "changelog =", self
.changelog
for d
in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'):
rpm_dir
[d
] = os
.path
.join(self
.rpm_base
, d
)
spec_dir
= rpm_dir
['SPECS']
# Spec file goes into 'dist_dir' if '--spec-only specified',
# build/rpm.<plat> otherwise.
spec_path
= os
.path
.join(spec_dir
,
"%s.spec" % self
.distribution
.get_name())
"writing '%s'" % spec_path
)
if self
.spec_only
: # stop if requested
# Make a source distribution and copy to SOURCES directory with
sdist
= self
.reinitialize_command('sdist')
sdist
.formats
= ['bztar']
sdist
.formats
= ['gztar']
self
.run_command('sdist')
source
= sdist
.get_archive_files()[0]
source_dir
= rpm_dir
['SOURCES']
self
.copy_file(source
, source_dir
)
if os
.path
.exists(self
.icon
):
self
.copy_file(self
.icon
, source_dir
)
raise DistutilsFileError
, \
"icon file '%s' does not exist" % self
.icon
log
.info("building RPMs")
if os
.path
.exists('/usr/bin/rpmbuild') or \
os
.path
.exists('/bin/rpmbuild'):
if self
.source_only
: # what kind of RPMs?
rpm_cmd
.extend(['--define',
'_topdir %s' % os
.path
.abspath(self
.rpm_base
)])
rpm_cmd
.append('--clean')
rpm_cmd
.append(spec_path
)
# XXX this is a nasty hack -- we really should have a proper way to
# find out the names of the RPM files created; also, this assumes
# that RPM creates exactly one source and one binary RPM.
srpms
= glob
.glob(os
.path
.join(rpm_dir
['SRPMS'], "*.rpm"))
assert len(srpms
) == 1, \
"unexpected number of SRPM files found: %s" % srpms
self
.move_file(srpms
[0], self
.dist_dir
)
rpms
= glob
.glob(os
.path
.join(rpm_dir
['RPMS'], "*/*.rpm"))
debuginfo
= glob
.glob(os
.path
.join(rpm_dir
['RPMS'], \
rpms
.remove(debuginfo
[0])
"unexpected number of RPM files found: %s" % rpms
self
.move_file(rpms
[0], self
.dist_dir
)
self
.move_file(debuginfo
[0], self
.dist_dir
)
def _make_spec_file(self
):
"""Generate the text of an RPM spec file and return it as a
list of strings (one per line).
# definitions and headers
'%define name ' + self
.distribution
.get_name(),
'%define version ' + self
.distribution
.get_version().replace('-','_'),
'%define release ' + self
.release
.replace('-','_'),
'Summary: ' + self
.distribution
.get_description(),
# put locale summaries into spec file
# XXX not supported for now (hard to put a dictionary
# in a config file -- arg!)
#for locale in self.summaries.keys():
# spec_file.append('Summary(%s): %s' % (locale,
# self.summaries[locale]))
# XXX yuck! this filename is available from the "sdist" command,
# but only after it has run: and we create the spec file before
# running "sdist", in case of --spec-only.
spec_file
.append('Source0: %{name}-%{version}.tar.bz2')
spec_file
.append('Source0: %{name}-%{version}.tar.gz')
'License: ' + self
.distribution
.get_license(),
'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot',
# noarch if no extension modules
if not self
.distribution
.has_ext_modules():
spec_file
.append('BuildArch: noarch')
spec_file
.append( 'BuildArch: %s' % self
.force_arch
)
val
= getattr(self
, string
.lower(field
))
if type(val
) is ListType
:
spec_file
.append('%s: %s' % (field
, string
.join(val
)))
spec_file
.append('%s: %s' % (field
, val
))
if self
.distribution
.get_url() != 'UNKNOWN':
spec_file
.append('Url: ' + self
.distribution
.get_url())
if self
.distribution_name
:
spec_file
.append('Distribution: ' + self
.distribution_name
)
spec_file
.append('BuildRequires: ' +
string
.join(self
.build_requires
))
spec_file
.append('Icon: ' + os
.path
.basename(self
.icon
))
spec_file
.append('AutoReq: 0')
self
.distribution
.get_long_description()
# put locale descriptions into spec file
# XXX again, suppressed because config file syntax doesn't
# easily support this ;-(
#for locale in self.descriptions.keys():
# '%description -l ' + locale,
# self.descriptions[locale],
# figure out default build script
def_build
= "%s setup.py build" % self
.python
if self
.use_rpm_opt_flags
:
def_build
= 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build
# insert contents of files
# XXX this is kind of misleading: user-supplied options are files
# that we open and interpolate into the spec file, but the defaults
# are just text that we drop in as-is. Hmmm.
('prep', 'prep_script', "%setup"),
('build', 'build_script', def_build
),
('install', 'install_script',
"--root=$RPM_BUILD_ROOT "
"--record=INSTALLED_FILES") % self
.python
),
('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"),
('verifyscript', 'verify_script', None),
('pre', 'pre_install', None),
('post', 'post_install', None),
('preun', 'pre_uninstall', None),
('postun', 'post_uninstall', None),
for (rpm_opt
, attr
, default
) in script_options
:
# Insert contents of file referred to, if no file is referred to
# use 'default' as contents of script
val
= getattr(self
, attr
)
spec_file
.extend(string
.split(open(val
, 'r').read(), '\n'))
spec_file
.append(default
)
'%files -f INSTALLED_FILES',
spec_file
.append('%doc ' + string
.join(self
.doc_files
))
spec_file
.extend(self
.changelog
)
def _format_changelog(self
, changelog
):
"""Format the changelog correctly and convert it to a list of strings
for line
in string
.split(string
.strip(changelog
), '\n'):
line
= string
.strip(line
)
new_changelog
.extend(['', line
])
new_changelog
.append(line
)
new_changelog
.append(' ' + line
)
# strip trailing newline inserted by first changelog entry