Updated for 2.1a3
[python/dscho.git] / Lib / distutils / command / bdist_rpm.py
blobf421590488956fe7ae4ca9fc4b51ddf61ecd1769
1 """distutils.command.bdist_rpm
3 Implements the Distutils 'bdist_rpm' command (create RPM source and binary
4 distributions)."""
6 # created 2000/04/25, by Harry Henry Gebel
8 __revision__ = "$Id$"
10 import sys, os, string
11 import glob
12 from types import *
13 from distutils.core import Command, DEBUG
14 from distutils.util import get_platform
15 from distutils.file_util import write_file
16 from distutils.errors import *
18 class bdist_rpm (Command):
20 description = "create an RPM distribution"
22 user_options = [
23 ('bdist-base=', None,
24 "base directory for creating built distributions"),
25 ('rpm-base=', None,
26 "base directory for creating RPMs (defaults to \"rpm\" under "
27 "--bdist-base; must be specified for RPM 2)"),
28 ('dist-dir=', 'd',
29 "directory to put final RPM files in "
30 "(and .spec files if --spec-only)"),
31 ('python=', None,
32 "path to Python interpreter to hard-code in the .spec file "
33 "(default: \"python\")"),
34 ('fix-python', None,
35 "hard-code the exact path to the current Python interpreter in "
36 "the .spec file"),
37 ('spec-only', None,
38 "only regenerate spec file"),
39 ('source-only', None,
40 "only generate source RPM"),
41 ('binary-only', None,
42 "only generate binary RPM"),
43 ('use-bzip2', None,
44 "use bzip2 instead of gzip to create source distribution"),
46 # More meta-data: too RPM-specific to put in the setup script,
47 # but needs to go in the .spec file -- so we make these options
48 # to "bdist_rpm". The idea is that packagers would put this
49 # info in setup.cfg, although they are of course free to
50 # supply it on the command line.
51 ('distribution-name=', None,
52 "name of the (Linux) distribution to which this "
53 "RPM applies (*not* the name of the module distribution!)"),
54 ('group=', None,
55 "package classification [default: \"Development/Libraries\"]"),
56 ('release=', None,
57 "RPM release number"),
58 ('serial=', None,
59 "RPM serial number"),
60 ('vendor=', None,
61 "RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") "
62 "[default: maintainer or author from setup script]"),
63 ('packager=', None,
64 "RPM packager (eg. \"Jane Doe <jane@example.net>\")"
65 "[default: vendor]"),
66 ('doc-files=', None,
67 "list of documentation files (space or comma-separated)"),
68 ('changelog=', None,
69 "path to RPM changelog"),
70 ('icon=', None,
71 "name of icon file"),
72 ('provides=', None,
73 "capabilities provided by this package"),
74 ('requires=', None,
75 "capabilities required by this package"),
76 ('conflicts=', None,
77 "capabilities which conflict with this package"),
78 ('build-requires=', None,
79 "capabilities required to build this package"),
80 ('obsoletes=', None,
81 "capabilities made obsolete by this package"),
83 # Actions to take when building RPM
84 ('keep-temp', 'k',
85 "don't clean up RPM build directory"),
86 ('no-keep-temp', None,
87 "clean up RPM build directory [default]"),
88 ('use-rpm-opt-flags', None,
89 "compile with RPM_OPT_FLAGS when building from source RPM"),
90 ('no-rpm-opt-flags', None,
91 "do not pass any RPM CFLAGS to compiler"),
92 ('rpm3-mode', None,
93 "RPM 3 compatibility mode (default)"),
94 ('rpm2-mode', None,
95 "RPM 2 compatibility mode"),
98 boolean_options = ['keep-temp', 'rpm2-mode']
100 negative_opt = {'no-keep-temp': 'keep-temp',
101 'no-rpm-opt-flags': 'use-rpm-opt-flags',
102 'rpm2-mode': 'rpm3-mode'}
105 def initialize_options (self):
106 self.bdist_base = None
107 self.rpm_base = None
108 self.dist_dir = None
109 self.python = None
110 self.fix_python = None
111 self.spec_only = None
112 self.binary_only = None
113 self.source_only = None
114 self.use_bzip2 = None
116 self.distribution_name = None
117 self.group = None
118 self.release = None
119 self.serial = None
120 self.vendor = None
121 self.packager = None
122 self.doc_files = None
123 self.changelog = None
124 self.icon = None
126 self.prep_script = None
127 self.build_script = None
128 self.install_script = None
129 self.clean_script = None
130 self.pre_install = None
131 self.post_install = None
132 self.pre_uninstall = None
133 self.post_uninstall = None
134 self.prep = None
135 self.provides = None
136 self.requires = None
137 self.conflicts = None
138 self.build_requires = None
139 self.obsoletes = None
141 self.keep_temp = 0
142 self.use_rpm_opt_flags = 1
143 self.rpm3_mode = 1
145 # initialize_options()
148 def finalize_options (self):
149 self.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))
150 if self.rpm_base is None:
151 if not self.rpm3_mode:
152 raise DistutilsOptionError, \
153 "you must specify --rpm-base in RPM 2 mode"
154 self.rpm_base = os.path.join(self.bdist_base, "rpm")
156 if self.python is None:
157 if self.fix_python:
158 self.python = sys.executable
159 else:
160 self.python = "python"
161 elif self.fix_python:
162 raise DistutilsOptionError, \
163 "--python and --fix-python are mutually exclusive options"
165 if os.name != 'posix':
166 raise DistutilsPlatformError, \
167 ("don't know how to create RPM "
168 "distributions on platform %s" % os.name)
169 if self.binary_only and self.source_only:
170 raise DistutilsOptionsError, \
171 "cannot supply both '--source-only' and '--binary-only'"
173 # don't pass CFLAGS to pure python distributions
174 if not self.distribution.has_ext_modules():
175 self.use_rpm_opt_flags = 0
177 self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
178 self.finalize_package_data()
180 # finalize_options()
182 def finalize_package_data (self):
183 self.ensure_string('group', "Development/Libraries")
184 self.ensure_string('vendor',
185 "%s <%s>" % (self.distribution.get_contact(),
186 self.distribution.get_contact_email()))
187 self.ensure_string('packager')
188 self.ensure_string_list('doc_files')
189 if type(self.doc_files) is ListType:
190 for readme in ('README', 'README.txt'):
191 if os.path.exists(readme) and readme not in self.doc_files:
192 self.doc.append(readme)
194 self.ensure_string('release', "1")
195 self.ensure_string('serial') # should it be an int?
197 self.ensure_string('distribution_name')
199 self.ensure_string('changelog')
200 # Format changelog correctly
201 self.changelog = self._format_changelog(self.changelog)
203 self.ensure_filename('icon')
205 self.ensure_filename('prep_script')
206 self.ensure_filename('build_script')
207 self.ensure_filename('install_script')
208 self.ensure_filename('clean_script')
209 self.ensure_filename('pre_install')
210 self.ensure_filename('post_install')
211 self.ensure_filename('pre_uninstall')
212 self.ensure_filename('post_uninstall')
214 # XXX don't forget we punted on summaries and descriptions -- they
215 # should be handled here eventually!
217 # Now *this* is some meta-data that belongs in the setup script...
218 self.ensure_string_list('provides')
219 self.ensure_string_list('requires')
220 self.ensure_string_list('conflicts')
221 self.ensure_string_list('build_requires')
222 self.ensure_string_list('obsoletes')
224 # finalize_package_data ()
227 def run (self):
229 if DEBUG:
230 print "before _get_package_data():"
231 print "vendor =", self.vendor
232 print "packager =", self.packager
233 print "doc_files =", self.doc_files
234 print "changelog =", self.changelog
236 # make directories
237 if self.spec_only:
238 spec_dir = self.dist_dir
239 self.mkpath(spec_dir)
240 else:
241 rpm_dir = {}
242 for d in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'):
243 rpm_dir[d] = os.path.join(self.rpm_base, d)
244 self.mkpath(rpm_dir[d])
245 spec_dir = rpm_dir['SPECS']
247 # Spec file goes into 'dist_dir' if '--spec-only specified',
248 # build/rpm.<plat> otherwise.
249 spec_path = os.path.join(spec_dir,
250 "%s.spec" % self.distribution.get_name())
251 self.execute(write_file,
252 (spec_path,
253 self._make_spec_file()),
254 "writing '%s'" % spec_path)
256 if self.spec_only: # stop if requested
257 return
259 # Make a source distribution and copy to SOURCES directory with
260 # optional icon.
261 sdist = self.reinitialize_command('sdist')
262 if self.use_bzip2:
263 sdist.formats = ['bztar']
264 else:
265 sdist.formats = ['gztar']
266 self.run_command('sdist')
268 source = sdist.get_archive_files()[0]
269 source_dir = rpm_dir['SOURCES']
270 self.copy_file(source, source_dir)
272 if self.icon:
273 if os.path.exists(self.icon):
274 self.copy_file(self.icon, source_dir)
275 else:
276 raise DistutilsFileError, \
277 "icon file '%s' does not exist" % self.icon
280 # build package
281 self.announce('building RPMs')
282 rpm_cmd = ['rpm']
283 if self.source_only: # what kind of RPMs?
284 rpm_cmd.append('-bs')
285 elif self.binary_only:
286 rpm_cmd.append('-bb')
287 else:
288 rpm_cmd.append('-ba')
289 if self.rpm3_mode:
290 rpm_cmd.extend(['--define',
291 '_topdir %s/%s' % (os.getcwd(), self.rpm_base),])
292 if not self.keep_temp:
293 rpm_cmd.append('--clean')
294 rpm_cmd.append(spec_path)
295 self.spawn(rpm_cmd)
297 # XXX this is a nasty hack -- we really should have a proper way to
298 # find out the names of the RPM files created; also, this assumes
299 # that RPM creates exactly one source and one binary RPM.
300 if not self.dry_run:
301 if not self.binary_only:
302 srpms = glob.glob(os.path.join(rpm_dir['SRPMS'], "*.rpm"))
303 assert len(srpms) == 1, \
304 "unexpected number of SRPM files found: %s" % srpms
305 self.move_file(srpms[0], self.dist_dir)
307 if not self.source_only:
308 rpms = glob.glob(os.path.join(rpm_dir['RPMS'], "*/*.rpm"))
309 assert len(rpms) == 1, \
310 "unexpected number of RPM files found: %s" % rpms
311 self.move_file(rpms[0], self.dist_dir)
313 # run()
316 def _make_spec_file(self):
317 """Generate the text of an RPM spec file and return it as a
318 list of strings (one per line).
320 # definitions and headers
321 spec_file = [
322 '%define name ' + self.distribution.get_name(),
323 '%define version ' + self.distribution.get_version(),
324 '%define release ' + self.release,
326 'Summary: ' + self.distribution.get_description(),
329 # put locale summaries into spec file
330 # XXX not supported for now (hard to put a dictionary
331 # in a config file -- arg!)
332 #for locale in self.summaries.keys():
333 # spec_file.append('Summary(%s): %s' % (locale,
334 # self.summaries[locale]))
336 spec_file.extend([
337 'Name: %{name}',
338 'Version: %{version}',
339 'Release: %{release}',])
341 # XXX yuck! this filename is available from the "sdist" command,
342 # but only after it has run: and we create the spec file before
343 # running "sdist", in case of --spec-only.
344 if self.use_bzip2:
345 spec_file.append('Source0: %{name}-%{version}.tar.bz2')
346 else:
347 spec_file.append('Source0: %{name}-%{version}.tar.gz')
349 spec_file.extend([
350 'Copyright: ' + self.distribution.get_licence(),
351 'Group: ' + self.group,
352 'BuildRoot: %{_tmppath}/%{name}-buildroot',
353 'Prefix: %{_prefix}', ])
355 # noarch if no extension modules
356 if not self.distribution.has_ext_modules():
357 spec_file.append('BuildArchitectures: noarch')
359 for field in ('Vendor',
360 'Packager',
361 'Provides',
362 'Requires',
363 'Conflicts',
364 'Obsoletes',
366 val = getattr(self, string.lower(field))
367 if type(val) is ListType:
368 spec_file.append('%s: %s' % (field, string.join(val)))
369 elif val is not None:
370 spec_file.append('%s: %s' % (field, val))
373 if self.distribution.get_url() != 'UNKNOWN':
374 spec_file.append('Url: ' + self.distribution.get_url())
376 if self.distribution_name:
377 spec_file.append('Distribution: ' + self.distribution_name)
379 if self.build_requires:
380 spec_file.append('BuildRequires: ' +
381 string.join(self.build_requires))
383 if self.icon:
384 spec_file.append('Icon: ' + os.path.basename(self.icon))
386 spec_file.extend([
388 '%description',
389 self.distribution.get_long_description()
392 # put locale descriptions into spec file
393 # XXX again, suppressed because config file syntax doesn't
394 # easily support this ;-(
395 #for locale in self.descriptions.keys():
396 # spec_file.extend([
397 # '',
398 # '%description -l ' + locale,
399 # self.descriptions[locale],
400 # ])
402 # rpm scripts
403 # figure out default build script
404 def_build = "%s setup.py build" % self.python
405 if self.use_rpm_opt_flags:
406 def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build
408 # insert contents of files
410 # XXX this is kind of misleading: user-supplied options are files
411 # that we open and interpolate into the spec file, but the defaults
412 # are just text that we drop in as-is. Hmmm.
414 script_options = [
415 ('prep', 'prep_script', "%setup"),
416 ('build', 'build_script', def_build),
417 ('install', 'install_script',
418 ("%s setup.py install "
419 "--root=$RPM_BUILD_ROOT "
420 "--record=INSTALLED_FILES") % self.python),
421 ('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"),
422 ('pre', 'pre_install', None),
423 ('post', 'post_install', None),
424 ('preun', 'pre_uninstall', None),
425 ('postun', 'post_uninstall', None),
428 for (rpm_opt, attr, default) in script_options:
429 # Insert contents of file referred to, if no file is refered to
430 # use 'default' as contents of script
431 val = getattr(self, attr)
432 if val or default:
433 spec_file.extend([
435 '%' + rpm_opt,])
436 if val:
437 spec_file.extend(string.split(open(val, 'r').read(), '\n'))
438 else:
439 spec_file.append(default)
442 # files section
443 spec_file.extend([
445 '%files -f INSTALLED_FILES',
446 '%defattr(-,root,root)',
449 if self.doc_files:
450 spec_file.append('%doc ' + string.join(self.doc_files))
452 if self.changelog:
453 spec_file.extend([
455 '%changelog',])
456 spec_file.extend(self.changelog)
458 return spec_file
460 # _make_spec_file ()
462 def _format_changelog(self, changelog):
463 """Format the changelog correctly and convert it to a list of strings
465 if not changelog:
466 return changelog
467 new_changelog = []
468 for line in string.split(string.strip(changelog), '\n'):
469 line = string.strip(line)
470 if line[0] == '*':
471 new_changelog.extend(['', line])
472 elif line[0] == '-':
473 new_changelog.append(line)
474 else:
475 new_changelog.append(' ' + line)
477 # strip trailing newline inserted by first changelog entry
478 if not new_changelog[0]:
479 del new_changelog[0]
481 return new_changelog
483 # _format_changelog()
485 # class bdist_rpm