1
"""distutils.command.bdist_rpm
3
Implements the Distutils 'bdist_rpm' command (create RPM source and binary
12
from distutils.core import Command
13
from distutils.debug import DEBUG
14
from distutils.file_util import write_file
15
from distutils.errors import (DistutilsOptionError, DistutilsPlatformError,
16
DistutilsFileError, DistutilsExecError)
17
from distutils import log
19
class bdist_rpm (Command):
21
description = "create an RPM distribution"
25
"base directory for creating built distributions"),
27
"base directory for creating RPMs (defaults to \"rpm\" under "
28
"--bdist-base; must be specified for RPM 2)"),
30
"directory to put final RPM files in "
31
"(and .spec files if --spec-only)"),
33
"path to Python interpreter to hard-code in the .spec file "
34
"(default: \"python\")"),
36
"hard-code the exact path to the current Python interpreter in "
39
"only regenerate spec file"),
41
"only generate source RPM"),
43
"only generate binary RPM"),
45
"use bzip2 instead of gzip to create source distribution"),
47
# More meta-data: too RPM-specific to put in the setup script,
48
# but needs to go in the .spec file -- so we make these options
49
# to "bdist_rpm". The idea is that packagers would put this
50
# info in setup.cfg, although they are of course free to
51
# supply it on the command line.
52
('distribution-name=', None,
53
"name of the (Linux) distribution to which this "
54
"RPM applies (*not* the name of the module distribution!)"),
56
"package classification [default: \"Development/Libraries\"]"),
58
"RPM release number"),
62
"RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") "
63
"[default: maintainer or author from setup script]"),
65
"RPM packager (eg. \"Jane Doe <jane@example.net>\")"
68
"list of documentation files (space or comma-separated)"),
74
"capabilities provided by this package"),
76
"capabilities required by this package"),
78
"capabilities which conflict with this package"),
79
('build-requires=', None,
80
"capabilities required to build this package"),
82
"capabilities made obsolete by this package"),
84
"do not automatically calculate dependencies"),
86
# Actions to take when building RPM
88
"don't clean up RPM build directory"),
89
('no-keep-temp', None,
90
"clean up RPM build directory [default]"),
91
('use-rpm-opt-flags', None,
92
"compile with RPM_OPT_FLAGS when building from source RPM"),
93
('no-rpm-opt-flags', None,
94
"do not pass any RPM CFLAGS to compiler"),
96
"RPM 3 compatibility mode (default)"),
98
"RPM 2 compatibility mode"),
100
# Add the hooks necessary for specifying custom scripts
101
('prep-script=', None,
102
"Specify a script for the PREP phase of RPM building"),
103
('build-script=', None,
104
"Specify a script for the BUILD phase of RPM building"),
106
('pre-install=', None,
107
"Specify a script for the pre-INSTALL phase of RPM building"),
108
('install-script=', None,
109
"Specify a script for the INSTALL phase of RPM building"),
110
('post-install=', None,
111
"Specify a script for the post-INSTALL phase of RPM building"),
113
('pre-uninstall=', None,
114
"Specify a script for the pre-UNINSTALL phase of RPM building"),
115
('post-uninstall=', None,
116
"Specify a script for the post-UNINSTALL phase of RPM building"),
118
('clean-script=', None,
119
"Specify a script for the CLEAN phase of RPM building"),
121
('verify-script=', None,
122
"Specify a script for the VERIFY phase of the RPM build"),
124
# Allow a packager to explicitly force an architecture
125
('force-arch=', None,
126
"Force an architecture onto the RPM build process"),
129
"Run the INSTALL phase of RPM building in quiet mode"),
132
boolean_options = ['keep-temp', 'use-rpm-opt-flags', 'rpm3-mode',
133
'no-autoreq', 'quiet']
135
negative_opt = {'no-keep-temp': 'keep-temp',
136
'no-rpm-opt-flags': 'use-rpm-opt-flags',
137
'rpm2-mode': 'rpm3-mode'}
140
def initialize_options (self):
141
self.bdist_base = None
145
self.fix_python = None
146
self.spec_only = None
147
self.binary_only = None
148
self.source_only = None
149
self.use_bzip2 = None
151
self.distribution_name = None
157
self.doc_files = None
158
self.changelog = None
161
self.prep_script = None
162
self.build_script = None
163
self.install_script = None
164
self.clean_script = None
165
self.verify_script = None
166
self.pre_install = None
167
self.post_install = None
168
self.pre_uninstall = None
169
self.post_uninstall = None
173
self.conflicts = None
174
self.build_requires = None
175
self.obsoletes = None
178
self.use_rpm_opt_flags = 1
182
self.force_arch = None
185
# initialize_options()
188
def finalize_options (self):
189
self.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))
190
if self.rpm_base is None:
191
if not self.rpm3_mode:
192
raise DistutilsOptionError, \
193
"you must specify --rpm-base in RPM 2 mode"
194
self.rpm_base = os.path.join(self.bdist_base, "rpm")
196
if self.python is None:
198
self.python = sys.executable
200
self.python = "python"
201
elif self.fix_python:
202
raise DistutilsOptionError, \
203
"--python and --fix-python are mutually exclusive options"
205
if os.name != 'posix':
206
raise DistutilsPlatformError, \
207
("don't know how to create RPM "
208
"distributions on platform %s" % os.name)
209
if self.binary_only and self.source_only:
210
raise DistutilsOptionError, \
211
"cannot supply both '--source-only' and '--binary-only'"
213
# don't pass CFLAGS to pure python distributions
214
if not self.distribution.has_ext_modules():
215
self.use_rpm_opt_flags = 0
217
self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
218
self.finalize_package_data()
222
def finalize_package_data (self):
223
self.ensure_string('group', "Development/Libraries")
224
self.ensure_string('vendor',
225
"%s <%s>" % (self.distribution.get_contact(),
226
self.distribution.get_contact_email()))
227
self.ensure_string('packager')
228
self.ensure_string_list('doc_files')
229
if isinstance(self.doc_files, list):
230
for readme in ('README', 'README.txt'):
231
if os.path.exists(readme) and readme not in self.doc_files:
232
self.doc_files.append(readme)
234
self.ensure_string('release', "1")
235
self.ensure_string('serial') # should it be an int?
237
self.ensure_string('distribution_name')
239
self.ensure_string('changelog')
240
# Format changelog correctly
241
self.changelog = self._format_changelog(self.changelog)
243
self.ensure_filename('icon')
245
self.ensure_filename('prep_script')
246
self.ensure_filename('build_script')
247
self.ensure_filename('install_script')
248
self.ensure_filename('clean_script')
249
self.ensure_filename('verify_script')
250
self.ensure_filename('pre_install')
251
self.ensure_filename('post_install')
252
self.ensure_filename('pre_uninstall')
253
self.ensure_filename('post_uninstall')
255
# XXX don't forget we punted on summaries and descriptions -- they
256
# should be handled here eventually!
258
# Now *this* is some meta-data that belongs in the setup script...
259
self.ensure_string_list('provides')
260
self.ensure_string_list('requires')
261
self.ensure_string_list('conflicts')
262
self.ensure_string_list('build_requires')
263
self.ensure_string_list('obsoletes')
265
self.ensure_string('force_arch')
266
# finalize_package_data ()
272
print "before _get_package_data():"
273
print "vendor =", self.vendor
274
print "packager =", self.packager
275
print "doc_files =", self.doc_files
276
print "changelog =", self.changelog
280
spec_dir = self.dist_dir
281
self.mkpath(spec_dir)
284
for d in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'):
285
rpm_dir[d] = os.path.join(self.rpm_base, d)
286
self.mkpath(rpm_dir[d])
287
spec_dir = rpm_dir['SPECS']
289
# Spec file goes into 'dist_dir' if '--spec-only specified',
290
# build/rpm.<plat> otherwise.
291
spec_path = os.path.join(spec_dir,
292
"%s.spec" % self.distribution.get_name())
293
self.execute(write_file,
295
self._make_spec_file()),
296
"writing '%s'" % spec_path)
298
if self.spec_only: # stop if requested
301
# Make a source distribution and copy to SOURCES directory with
303
saved_dist_files = self.distribution.dist_files[:]
304
sdist = self.reinitialize_command('sdist')
306
sdist.formats = ['bztar']
308
sdist.formats = ['gztar']
309
self.run_command('sdist')
310
self.distribution.dist_files = saved_dist_files
312
source = sdist.get_archive_files()[0]
313
source_dir = rpm_dir['SOURCES']
314
self.copy_file(source, source_dir)
317
if os.path.exists(self.icon):
318
self.copy_file(self.icon, source_dir)
320
raise DistutilsFileError, \
321
"icon file '%s' does not exist" % self.icon
325
log.info("building RPMs")
327
if os.path.exists('/usr/bin/rpmbuild') or \
328
os.path.exists('/bin/rpmbuild'):
329
rpm_cmd = ['rpmbuild']
331
if self.source_only: # what kind of RPMs?
332
rpm_cmd.append('-bs')
333
elif self.binary_only:
334
rpm_cmd.append('-bb')
336
rpm_cmd.append('-ba')
338
rpm_cmd.extend(['--define',
339
'_topdir %s' % os.path.abspath(self.rpm_base)])
340
if not self.keep_temp:
341
rpm_cmd.append('--clean')
344
rpm_cmd.append('--quiet')
346
rpm_cmd.append(spec_path)
347
# Determine the binary rpm names that should be built out of this spec
349
# Note that some of these may not be really built (if the file
351
nvr_string = "%{name}-%{version}-%{release}"
352
src_rpm = nvr_string + ".src.rpm"
353
non_src_rpm = "%{arch}/" + nvr_string + ".%{arch}.rpm"
354
q_cmd = r"rpm -q --qf '%s %s\n' --specfile '%s'" % (
355
src_rpm, non_src_rpm, spec_path)
357
out = os.popen(q_cmd)
362
line = out.readline()
365
l = string.split(string.strip(line))
367
binary_rpms.append(l[1])
368
# The source rpm is named after the first entry in the spec file
369
if source_rpm is None:
374
raise DistutilsExecError("Failed to execute: %s" % repr(q_cmd))
382
if not self.binary_only:
383
srpm = os.path.join(rpm_dir['SRPMS'], source_rpm)
384
assert(os.path.exists(srpm))
385
self.move_file(srpm, self.dist_dir)
387
if not self.source_only:
388
for rpm in binary_rpms:
389
rpm = os.path.join(rpm_dir['RPMS'], rpm)
390
if os.path.exists(rpm):
391
self.move_file(rpm, self.dist_dir)
394
def _dist_path(self, path):
395
return os.path.join(self.dist_dir, os.path.basename(path))
397
def _make_spec_file(self):
398
"""Generate the text of an RPM spec file and return it as a
399
list of strings (one per line).
401
# definitions and headers
403
'%define name ' + self.distribution.get_name(),
404
'%define version ' + self.distribution.get_version().replace('-','_'),
405
'%define unmangled_version ' + self.distribution.get_version(),
406
'%define release ' + self.release.replace('-','_'),
408
'Summary: ' + self.distribution.get_description(),
411
# put locale summaries into spec file
412
# XXX not supported for now (hard to put a dictionary
413
# in a config file -- arg!)
414
#for locale in self.summaries.keys():
415
# spec_file.append('Summary(%s): %s' % (locale,
416
# self.summaries[locale]))
420
'Version: %{version}',
421
'Release: %{release}',])
423
# XXX yuck! this filename is available from the "sdist" command,
424
# but only after it has run: and we create the spec file before
425
# running "sdist", in case of --spec-only.
427
spec_file.append('Source0: %{name}-%{unmangled_version}.tar.bz2')
429
spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz')
432
'License: ' + self.distribution.get_license(),
433
'Group: ' + self.group,
434
'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot',
435
'Prefix: %{_prefix}', ])
437
if not self.force_arch:
438
# noarch if no extension modules
439
if not self.distribution.has_ext_modules():
440
spec_file.append('BuildArch: noarch')
442
spec_file.append( 'BuildArch: %s' % self.force_arch )
444
for field in ('Vendor',
451
val = getattr(self, string.lower(field))
452
if isinstance(val, list):
453
spec_file.append('%s: %s' % (field, string.join(val)))
454
elif val is not None:
455
spec_file.append('%s: %s' % (field, val))
458
if self.distribution.get_url() != 'UNKNOWN':
459
spec_file.append('Url: ' + self.distribution.get_url())
461
if self.distribution_name:
462
spec_file.append('Distribution: ' + self.distribution_name)
464
if self.build_requires:
465
spec_file.append('BuildRequires: ' +
466
string.join(self.build_requires))
469
spec_file.append('Icon: ' + os.path.basename(self.icon))
472
spec_file.append('AutoReq: 0')
477
self.distribution.get_long_description()
480
# put locale descriptions into spec file
481
# XXX again, suppressed because config file syntax doesn't
482
# easily support this ;-(
483
#for locale in self.descriptions.keys():
486
# '%description -l ' + locale,
487
# self.descriptions[locale],
491
# figure out default build script
492
def_setup_call = "%s %s" % (self.python,os.path.basename(sys.argv[0]))
493
def_build = "%s build" % def_setup_call
494
if self.use_rpm_opt_flags:
495
def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build
497
# insert contents of files
499
# XXX this is kind of misleading: user-supplied options are files
500
# that we open and interpolate into the spec file, but the defaults
501
# are just text that we drop in as-is. Hmmm.
503
install_cmd = ('%s install -O1 --root=$RPM_BUILD_ROOT '
504
'--record=INSTALLED_FILES') % def_setup_call
507
('prep', 'prep_script', "%setup -n %{name}-%{unmangled_version}"),
508
('build', 'build_script', def_build),
509
('install', 'install_script', install_cmd),
510
('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"),
511
('verifyscript', 'verify_script', None),
512
('pre', 'pre_install', None),
513
('post', 'post_install', None),
514
('preun', 'pre_uninstall', None),
515
('postun', 'post_uninstall', None),
518
for (rpm_opt, attr, default) in script_options:
519
# Insert contents of file referred to, if no file is referred to
520
# use 'default' as contents of script
521
val = getattr(self, attr)
527
spec_file.extend(string.split(open(val, 'r').read(), '\n'))
529
spec_file.append(default)
535
'%files -f INSTALLED_FILES',
536
'%defattr(-,root,root)',
540
spec_file.append('%doc ' + string.join(self.doc_files))
546
spec_file.extend(self.changelog)
552
def _format_changelog(self, changelog):
553
"""Format the changelog correctly and convert it to a list of strings
558
for line in string.split(string.strip(changelog), '\n'):
559
line = string.strip(line)
561
new_changelog.extend(['', line])
563
new_changelog.append(line)
565
new_changelog.append(' ' + line)
567
# strip trailing newline inserted by first changelog entry
568
if not new_changelog[0]:
573
# _format_changelog()