0001 """distutils.command.bdist_rpm 0002 0003 Implements the Distutils 'bdist_rpm' command (create RPM source and binary 0004 distributions).""" 0005 0006 # This module should be kept compatible with Python 1.5.2. 0007 0008 __revision__ = "$Id: bdist_rpm.py,v 1.45 2004/09/17 08:34:12 jafo Exp $" 0009 0010 import sys, os, string 0011 import glob 0012 from types import * 0013 from distutils.core import Command 0014 from distutils.debug import DEBUG 0015 from distutils.util import get_platform 0016 from distutils.file_util import write_file 0017 from distutils.errors import * 0018 from distutils import log 0019 0020 class bdist_rpm (Command): 0021 0022 description = "create an RPM distribution" 0023 0024 user_options = [ 0025 ('bdist-base=', None, 0026 "base directory for creating built distributions"), 0027 ('rpm-base=', None, 0028 "base directory for creating RPMs (defaults to \"rpm\" under " 0029 "--bdist-base; must be specified for RPM 2)"), 0030 ('dist-dir=', 'd', 0031 "directory to put final RPM files in " 0032 "(and .spec files if --spec-only)"), 0033 ('python=', None, 0034 "path to Python interpreter to hard-code in the .spec file " 0035 "(default: \"python\")"), 0036 ('fix-python', None, 0037 "hard-code the exact path to the current Python interpreter in " 0038 "the .spec file"), 0039 ('spec-only', None, 0040 "only regenerate spec file"), 0041 ('source-only', None, 0042 "only generate source RPM"), 0043 ('binary-only', None, 0044 "only generate binary RPM"), 0045 ('use-bzip2', None, 0046 "use bzip2 instead of gzip to create source distribution"), 0047 0048 # More meta-data: too RPM-specific to put in the setup script, 0049 # but needs to go in the .spec file -- so we make these options 0050 # to "bdist_rpm". The idea is that packagers would put this 0051 # info in setup.cfg, although they are of course free to 0052 # supply it on the command line. 0053 ('distribution-name=', None, 0054 "name of the (Linux) distribution to which this " 0055 "RPM applies (*not* the name of the module distribution!)"), 0056 ('group=', None, 0057 "package classification [default: \"Development/Libraries\"]"), 0058 ('release=', None, 0059 "RPM release number"), 0060 ('serial=', None, 0061 "RPM serial number"), 0062 ('vendor=', None, 0063 "RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") " 0064 "[default: maintainer or author from setup script]"), 0065 ('packager=', None, 0066 "RPM packager (eg. \"Jane Doe <jane@example.net>\")" 0067 "[default: vendor]"), 0068 ('doc-files=', None, 0069 "list of documentation files (space or comma-separated)"), 0070 ('changelog=', None, 0071 "RPM changelog"), 0072 ('icon=', None, 0073 "name of icon file"), 0074 ('provides=', None, 0075 "capabilities provided by this package"), 0076 ('requires=', None, 0077 "capabilities required by this package"), 0078 ('conflicts=', None, 0079 "capabilities which conflict with this package"), 0080 ('build-requires=', None, 0081 "capabilities required to build this package"), 0082 ('obsoletes=', None, 0083 "capabilities made obsolete by this package"), 0084 ('no-autoreq', None, 0085 "do not automatically calculate dependencies"), 0086 0087 # Actions to take when building RPM 0088 ('keep-temp', 'k', 0089 "don't clean up RPM build directory"), 0090 ('no-keep-temp', None, 0091 "clean up RPM build directory [default]"), 0092 ('use-rpm-opt-flags', None, 0093 "compile with RPM_OPT_FLAGS when building from source RPM"), 0094 ('no-rpm-opt-flags', None, 0095 "do not pass any RPM CFLAGS to compiler"), 0096 ('rpm3-mode', None, 0097 "RPM 3 compatibility mode (default)"), 0098 ('rpm2-mode', None, 0099 "RPM 2 compatibility mode"), 0100 0101 # Add the hooks necessary for specifying custom scripts 0102 ('prep-script=', None, 0103 "Specify a script for the PREP phase of RPM building"), 0104 ('build-script=', None, 0105 "Specify a script for the BUILD phase of RPM building"), 0106 0107 ('pre-install=', None, 0108 "Specify a script for the pre-INSTALL phase of RPM building"), 0109 ('install-script=', None, 0110 "Specify a script for the INSTALL phase of RPM building"), 0111 ('post-install=', None, 0112 "Specify a script for the post-INSTALL phase of RPM building"), 0113 0114 ('pre-uninstall=', None, 0115 "Specify a script for the pre-UNINSTALL phase of RPM building"), 0116 ('post-uninstall=', None, 0117 "Specify a script for the post-UNINSTALL phase of RPM building"), 0118 0119 ('clean-script=', None, 0120 "Specify a script for the CLEAN phase of RPM building"), 0121 0122 ('verify-script=', None, 0123 "Specify a script for the VERIFY phase of the RPM build"), 0124 0125 # Allow a packager to explicitly force an architecture 0126 ('force-arch=', None, 0127 "Force an architecture onto the RPM build process"), 0128 ] 0129 0130 boolean_options = ['keep-temp', 'use-rpm-opt-flags', 'rpm3-mode', 0131 'no-autoreq'] 0132 0133 negative_opt = {'no-keep-temp': 'keep-temp', 0134 'no-rpm-opt-flags': 'use-rpm-opt-flags', 0135 'rpm2-mode': 'rpm3-mode'} 0136 0137 0138 def initialize_options (self): 0139 self.bdist_base = None 0140 self.rpm_base = None 0141 self.dist_dir = None 0142 self.python = None 0143 self.fix_python = None 0144 self.spec_only = None 0145 self.binary_only = None 0146 self.source_only = None 0147 self.use_bzip2 = None 0148 0149 self.distribution_name = None 0150 self.group = None 0151 self.release = None 0152 self.serial = None 0153 self.vendor = None 0154 self.packager = None 0155 self.doc_files = None 0156 self.changelog = None 0157 self.icon = None 0158 0159 self.prep_script = None 0160 self.build_script = None 0161 self.install_script = None 0162 self.clean_script = None 0163 self.verify_script = None 0164 self.pre_install = None 0165 self.post_install = None 0166 self.pre_uninstall = None 0167 self.post_uninstall = None 0168 self.prep = None 0169 self.provides = None 0170 self.requires = None 0171 self.conflicts = None 0172 self.build_requires = None 0173 self.obsoletes = None 0174 0175 self.keep_temp = 0 0176 self.use_rpm_opt_flags = 1 0177 self.rpm3_mode = 1 0178 self.no_autoreq = 0 0179 0180 self.force_arch = None 0181 0182 # initialize_options() 0183 0184 0185 def finalize_options (self): 0186 self.set_undefined_options('bdist', ('bdist_base', 'bdist_base')) 0187 if self.rpm_base is None: 0188 if not self.rpm3_mode: 0189 raise DistutilsOptionError, \ 0190 "you must specify --rpm-base in RPM 2 mode" 0191 self.rpm_base = os.path.join(self.bdist_base, "rpm") 0192 0193 if self.python is None: 0194 if self.fix_python: 0195 self.python = sys.executable 0196 else: 0197 self.python = "python" 0198 elif self.fix_python: 0199 raise DistutilsOptionError, \ 0200 "--python and --fix-python are mutually exclusive options" 0201 0202 if os.name != 'posix': 0203 raise DistutilsPlatformError, \ 0204 ("don't know how to create RPM " 0205 "distributions on platform %s" % os.name) 0206 if self.binary_only and self.source_only: 0207 raise DistutilsOptionError, \ 0208 "cannot supply both '--source-only' and '--binary-only'" 0209 0210 # don't pass CFLAGS to pure python distributions 0211 if not self.distribution.has_ext_modules(): 0212 self.use_rpm_opt_flags = 0 0213 0214 self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) 0215 self.finalize_package_data() 0216 0217 # finalize_options() 0218 0219 def finalize_package_data (self): 0220 self.ensure_string('group', "Development/Libraries") 0221 self.ensure_string('vendor', 0222 "%s <%s>" % (self.distribution.get_contact(), 0223 self.distribution.get_contact_email())) 0224 self.ensure_string('packager') 0225 self.ensure_string_list('doc_files') 0226 if type(self.doc_files) is ListType: 0227 for readme in ('README', 'README.txt'): 0228 if os.path.exists(readme) and readme not in self.doc_files: 0229 self.doc_files.append(readme) 0230 0231 self.ensure_string('release', "1") 0232 self.ensure_string('serial') # should it be an int? 0233 0234 self.ensure_string('distribution_name') 0235 0236 self.ensure_string('changelog') 0237 # Format changelog correctly 0238 self.changelog = self._format_changelog(self.changelog) 0239 0240 self.ensure_filename('icon') 0241 0242 self.ensure_filename('prep_script') 0243 self.ensure_filename('build_script') 0244 self.ensure_filename('install_script') 0245 self.ensure_filename('clean_script') 0246 self.ensure_filename('verify_script') 0247 self.ensure_filename('pre_install') 0248 self.ensure_filename('post_install') 0249 self.ensure_filename('pre_uninstall') 0250 self.ensure_filename('post_uninstall') 0251 0252 # XXX don't forget we punted on summaries and descriptions -- they 0253 # should be handled here eventually! 0254 0255 # Now *this* is some meta-data that belongs in the setup script... 0256 self.ensure_string_list('provides') 0257 self.ensure_string_list('requires') 0258 self.ensure_string_list('conflicts') 0259 self.ensure_string_list('build_requires') 0260 self.ensure_string_list('obsoletes') 0261 0262 self.ensure_string('force_arch') 0263 # finalize_package_data () 0264 0265 0266 def run (self): 0267 0268 if DEBUG: 0269 print "before _get_package_data():" 0270 print "vendor =", self.vendor 0271 print "packager =", self.packager 0272 print "doc_files =", self.doc_files 0273 print "changelog =", self.changelog 0274 0275 # make directories 0276 if self.spec_only: 0277 spec_dir = self.dist_dir 0278 self.mkpath(spec_dir) 0279 else: 0280 rpm_dir = {} 0281 for d in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'): 0282 rpm_dir[d] = os.path.join(self.rpm_base, d) 0283 self.mkpath(rpm_dir[d]) 0284 spec_dir = rpm_dir['SPECS'] 0285 0286 # Spec file goes into 'dist_dir' if '--spec-only specified', 0287 # build/rpm.<plat> otherwise. 0288 spec_path = os.path.join(spec_dir, 0289 "%s.spec" % self.distribution.get_name()) 0290 self.execute(write_file, 0291 (spec_path, 0292 self._make_spec_file()), 0293 "writing '%s'" % spec_path) 0294 0295 if self.spec_only: # stop if requested 0296 return 0297 0298 # Make a source distribution and copy to SOURCES directory with 0299 # optional icon. 0300 sdist = self.reinitialize_command('sdist') 0301 if self.use_bzip2: 0302 sdist.formats = ['bztar'] 0303 else: 0304 sdist.formats = ['gztar'] 0305 self.run_command('sdist') 0306 0307 source = sdist.get_archive_files()[0] 0308 source_dir = rpm_dir['SOURCES'] 0309 self.copy_file(source, source_dir) 0310 0311 if self.icon: 0312 if os.path.exists(self.icon): 0313 self.copy_file(self.icon, source_dir) 0314 else: 0315 raise DistutilsFileError, \ 0316 "icon file '%s' does not exist" % self.icon 0317 0318 0319 # build package 0320 log.info("building RPMs") 0321 rpm_cmd = ['rpm'] 0322 if os.path.exists('/usr/bin/rpmbuild') or \ 0323 os.path.exists('/bin/rpmbuild'): 0324 rpm_cmd = ['rpmbuild'] 0325 if self.source_only: # what kind of RPMs? 0326 rpm_cmd.append('-bs') 0327 elif self.binary_only: 0328 rpm_cmd.append('-bb') 0329 else: 0330 rpm_cmd.append('-ba') 0331 if self.rpm3_mode: 0332 rpm_cmd.extend(['--define', 0333 '_topdir %s' % os.path.abspath(self.rpm_base)]) 0334 if not self.keep_temp: 0335 rpm_cmd.append('--clean') 0336 rpm_cmd.append(spec_path) 0337 self.spawn(rpm_cmd) 0338 0339 # XXX this is a nasty hack -- we really should have a proper way to 0340 # find out the names of the RPM files created; also, this assumes 0341 # that RPM creates exactly one source and one binary RPM. 0342 if not self.dry_run: 0343 if not self.binary_only: 0344 srpms = glob.glob(os.path.join(rpm_dir['SRPMS'], "*.rpm")) 0345 assert len(srpms) == 1, \ 0346 "unexpected number of SRPM files found: %s" % srpms 0347 self.move_file(srpms[0], self.dist_dir) 0348 0349 if not self.source_only: 0350 rpms = glob.glob(os.path.join(rpm_dir['RPMS'], "*/*.rpm")) 0351 debuginfo = glob.glob(os.path.join(rpm_dir['RPMS'], \ 0352 "*/*debuginfo*.rpm")) 0353 if debuginfo: 0354 rpms.remove(debuginfo[0]) 0355 assert len(rpms) == 1, \ 0356 "unexpected number of RPM files found: %s" % rpms 0357 self.move_file(rpms[0], self.dist_dir) 0358 if debuginfo: 0359 self.move_file(debuginfo[0], self.dist_dir) 0360 # run() 0361 0362 0363 def _make_spec_file(self): 0364 """Generate the text of an RPM spec file and return it as a 0365 list of strings (one per line). 0366 """ 0367 # definitions and headers 0368 spec_file = [ 0369 '%define name ' + self.distribution.get_name(), 0370 '%define version ' + self.distribution.get_version().replace('-','_'), 0371 '%define release ' + self.release.replace('-','_'), 0372 '', 0373 'Summary: ' + self.distribution.get_description(), 0374 ] 0375 0376 # put locale summaries into spec file 0377 # XXX not supported for now (hard to put a dictionary 0378 # in a config file -- arg!) 0379 #for locale in self.summaries.keys(): 0380 # spec_file.append('Summary(%s): %s' % (locale, 0381 # self.summaries[locale])) 0382 0383 spec_file.extend([ 0384 'Name: %{name}', 0385 'Version: %{version}', 0386 'Release: %{release}',]) 0387 0388 # XXX yuck! this filename is available from the "sdist" command, 0389 # but only after it has run: and we create the spec file before 0390 # running "sdist", in case of --spec-only. 0391 if self.use_bzip2: 0392 spec_file.append('Source0: %{name}-%{version}.tar.bz2') 0393 else: 0394 spec_file.append('Source0: %{name}-%{version}.tar.gz') 0395 0396 spec_file.extend([ 0397 'License: ' + self.distribution.get_license(), 0398 'Group: ' + self.group, 0399 'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot', 0400 'Prefix: %{_prefix}', ]) 0401 0402 if not self.force_arch: 0403 # noarch if no extension modules 0404 if not self.distribution.has_ext_modules(): 0405 spec_file.append('BuildArch: noarch') 0406 else: 0407 spec_file.append( 'BuildArch: %s' % self.force_arch ) 0408 0409 for field in ('Vendor', 0410 'Packager', 0411 'Provides', 0412 'Requires', 0413 'Conflicts', 0414 'Obsoletes', 0415 ): 0416 val = getattr(self, string.lower(field)) 0417 if type(val) is ListType: 0418 spec_file.append('%s: %s' % (field, string.join(val))) 0419 elif val is not None: 0420 spec_file.append('%s: %s' % (field, val)) 0421 0422 0423 if self.distribution.get_url() != 'UNKNOWN': 0424 spec_file.append('Url: ' + self.distribution.get_url()) 0425 0426 if self.distribution_name: 0427 spec_file.append('Distribution: ' + self.distribution_name) 0428 0429 if self.build_requires: 0430 spec_file.append('BuildRequires: ' + 0431 string.join(self.build_requires)) 0432 0433 if self.icon: 0434 spec_file.append('Icon: ' + os.path.basename(self.icon)) 0435 0436 if self.no_autoreq: 0437 spec_file.append('AutoReq: 0') 0438 0439 spec_file.extend([ 0440 '', 0441 '%description', 0442 self.distribution.get_long_description() 0443 ]) 0444 0445 # put locale descriptions into spec file 0446 # XXX again, suppressed because config file syntax doesn't 0447 # easily support this ;-( 0448 #for locale in self.descriptions.keys(): 0449 # spec_file.extend([ 0450 # '', 0451 # '%description -l ' + locale, 0452 # self.descriptions[locale], 0453 # ]) 0454 0455 # rpm scripts 0456 # figure out default build script 0457 def_build = "%s setup.py build" % self.python 0458 if self.use_rpm_opt_flags: 0459 def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build 0460 0461 # insert contents of files 0462 0463 # XXX this is kind of misleading: user-supplied options are files 0464 # that we open and interpolate into the spec file, but the defaults 0465 # are just text that we drop in as-is. Hmmm. 0466 0467 script_options = [ 0468 ('prep', 'prep_script', "%setup"), 0469 ('build', 'build_script', def_build), 0470 ('install', 'install_script', 0471 ("%s setup.py install " 0472 "--root=$RPM_BUILD_ROOT " 0473 "--record=INSTALLED_FILES") % self.python), 0474 ('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"), 0475 ('verifyscript', 'verify_script', None), 0476 ('pre', 'pre_install', None), 0477 ('post', 'post_install', None), 0478 ('preun', 'pre_uninstall', None), 0479 ('postun', 'post_uninstall', None), 0480 ] 0481 0482 for (rpm_opt, attr, default) in script_options: 0483 # Insert contents of file referred to, if no file is referred to 0484 # use 'default' as contents of script 0485 val = getattr(self, attr) 0486 if val or default: 0487 spec_file.extend([ 0488 '', 0489 '%' + rpm_opt,]) 0490 if val: 0491 spec_file.extend(string.split(open(val, 'r').read(), '\n')) 0492 else: 0493 spec_file.append(default) 0494 0495 0496 # files section 0497 spec_file.extend([ 0498 '', 0499 '%files -f INSTALLED_FILES', 0500 '%defattr(-,root,root)', 0501 ]) 0502 0503 if self.doc_files: 0504 spec_file.append('%doc ' + string.join(self.doc_files)) 0505 0506 if self.changelog: 0507 spec_file.extend([ 0508 '', 0509 '%changelog',]) 0510 spec_file.extend(self.changelog) 0511 0512 return spec_file 0513 0514 # _make_spec_file () 0515 0516 def _format_changelog(self, changelog): 0517 """Format the changelog correctly and convert it to a list of strings 0518 """ 0519 if not changelog: 0520 return changelog 0521 new_changelog = [] 0522 for line in string.split(string.strip(changelog), '\n'): 0523 line = string.strip(line) 0524 if line[0] == '*': 0525 new_changelog.extend(['', line]) 0526 elif line[0] == '-': 0527 new_changelog.append(line) 0528 else: 0529 new_changelog.append(' ' + line) 0530 0531 # strip trailing newline inserted by first changelog entry 0532 if not new_changelog[0]: 0533 del new_changelog[0] 0534 0535 return new_changelog 0536 0537 # _format_changelog() 0538 0539 # class bdist_rpm 0540
Generated by PyXR 0.9.4