- Got rid of newmodule.c
[python/dscho.git] / Lib / distutils / command / bdist_rpm.py
blob808ddc14cbe3de9329a7167518142b4ec6c0fd4a
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 *
17 from distutils import log
19 class bdist_rpm (Command):
21 description = "create an RPM distribution"
23 user_options = [
24 ('bdist-base=', None,
25 "base directory for creating built distributions"),
26 ('rpm-base=', None,
27 "base directory for creating RPMs (defaults to \"rpm\" under "
28 "--bdist-base; must be specified for RPM 2)"),
29 ('dist-dir=', 'd',
30 "directory to put final RPM files in "
31 "(and .spec files if --spec-only)"),
32 ('python=', None,
33 "path to Python interpreter to hard-code in the .spec file "
34 "(default: \"python\")"),
35 ('fix-python', None,
36 "hard-code the exact path to the current Python interpreter in "
37 "the .spec file"),
38 ('spec-only', None,
39 "only regenerate spec file"),
40 ('source-only', None,
41 "only generate source RPM"),
42 ('binary-only', None,
43 "only generate binary RPM"),
44 ('use-bzip2', None,
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!)"),
55 ('group=', None,
56 "package classification [default: \"Development/Libraries\"]"),
57 ('release=', None,
58 "RPM release number"),
59 ('serial=', None,
60 "RPM serial number"),
61 ('vendor=', None,
62 "RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") "
63 "[default: maintainer or author from setup script]"),
64 ('packager=', None,
65 "RPM packager (eg. \"Jane Doe <jane@example.net>\")"
66 "[default: vendor]"),
67 ('doc-files=', None,
68 "list of documentation files (space or comma-separated)"),
69 ('changelog=', None,
70 "path to RPM changelog"),
71 ('icon=', None,
72 "name of icon file"),
73 ('provides=', None,
74 "capabilities provided by this package"),
75 ('requires=', None,
76 "capabilities required by this package"),
77 ('conflicts=', None,
78 "capabilities which conflict with this package"),
79 ('build-requires=', None,
80 "capabilities required to build this package"),
81 ('obsoletes=', None,
82 "capabilities made obsolete by this package"),
84 # Actions to take when building RPM
85 ('keep-temp', 'k',
86 "don't clean up RPM build directory"),
87 ('no-keep-temp', None,
88 "clean up RPM build directory [default]"),
89 ('use-rpm-opt-flags', None,
90 "compile with RPM_OPT_FLAGS when building from source RPM"),
91 ('no-rpm-opt-flags', None,
92 "do not pass any RPM CFLAGS to compiler"),
93 ('rpm3-mode', None,
94 "RPM 3 compatibility mode (default)"),
95 ('rpm2-mode', None,
96 "RPM 2 compatibility mode"),
99 boolean_options = ['keep-temp', 'use-rpm-opt-flags', 'rpm3-mode']
101 negative_opt = {'no-keep-temp': 'keep-temp',
102 'no-rpm-opt-flags': 'use-rpm-opt-flags',
103 'rpm2-mode': 'rpm3-mode'}
106 def initialize_options (self):
107 self.bdist_base = None
108 self.rpm_base = None
109 self.dist_dir = None
110 self.python = None
111 self.fix_python = None
112 self.spec_only = None
113 self.binary_only = None
114 self.source_only = None
115 self.use_bzip2 = None
117 self.distribution_name = None
118 self.group = None
119 self.release = None
120 self.serial = None
121 self.vendor = None
122 self.packager = None
123 self.doc_files = None
124 self.changelog = None
125 self.icon = None
127 self.prep_script = None
128 self.build_script = None
129 self.install_script = None
130 self.clean_script = None
131 self.pre_install = None
132 self.post_install = None
133 self.pre_uninstall = None
134 self.post_uninstall = None
135 self.prep = None
136 self.provides = None
137 self.requires = None
138 self.conflicts = None
139 self.build_requires = None
140 self.obsoletes = None
142 self.keep_temp = 0
143 self.use_rpm_opt_flags = 1
144 self.rpm3_mode = 1
146 # initialize_options()
149 def finalize_options (self):
150 self.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))
151 if self.rpm_base is None:
152 if not self.rpm3_mode:
153 raise DistutilsOptionError, \
154 "you must specify --rpm-base in RPM 2 mode"
155 self.rpm_base = os.path.join(self.bdist_base, "rpm")
157 if self.python is None:
158 if self.fix_python:
159 self.python = sys.executable
160 else:
161 self.python = "python"
162 elif self.fix_python:
163 raise DistutilsOptionError, \
164 "--python and --fix-python are mutually exclusive options"
166 if os.name != 'posix':
167 raise DistutilsPlatformError, \
168 ("don't know how to create RPM "
169 "distributions on platform %s" % os.name)
170 if self.binary_only and self.source_only:
171 raise DistutilsOptionError, \
172 "cannot supply both '--source-only' and '--binary-only'"
174 # don't pass CFLAGS to pure python distributions
175 if not self.distribution.has_ext_modules():
176 self.use_rpm_opt_flags = 0
178 self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
179 self.finalize_package_data()
181 # finalize_options()
183 def finalize_package_data (self):
184 self.ensure_string('group', "Development/Libraries")
185 self.ensure_string('vendor',
186 "%s <%s>" % (self.distribution.get_contact(),
187 self.distribution.get_contact_email()))
188 self.ensure_string('packager')
189 self.ensure_string_list('doc_files')
190 if type(self.doc_files) is ListType:
191 for readme in ('README', 'README.txt'):
192 if os.path.exists(readme) and readme not in self.doc_files:
193 self.doc_files.append(readme)
195 self.ensure_string('release', "1")
196 self.ensure_string('serial') # should it be an int?
198 self.ensure_string('distribution_name')
200 self.ensure_string('changelog')
201 # Format changelog correctly
202 self.changelog = self._format_changelog(self.changelog)
204 self.ensure_filename('icon')
206 self.ensure_filename('prep_script')
207 self.ensure_filename('build_script')
208 self.ensure_filename('install_script')
209 self.ensure_filename('clean_script')
210 self.ensure_filename('pre_install')
211 self.ensure_filename('post_install')
212 self.ensure_filename('pre_uninstall')
213 self.ensure_filename('post_uninstall')
215 # XXX don't forget we punted on summaries and descriptions -- they
216 # should be handled here eventually!
218 # Now *this* is some meta-data that belongs in the setup script...
219 self.ensure_string_list('provides')
220 self.ensure_string_list('requires')
221 self.ensure_string_list('conflicts')
222 self.ensure_string_list('build_requires')
223 self.ensure_string_list('obsoletes')
225 # finalize_package_data ()
228 def run (self):
230 if DEBUG:
231 print "before _get_package_data():"
232 print "vendor =", self.vendor
233 print "packager =", self.packager
234 print "doc_files =", self.doc_files
235 print "changelog =", self.changelog
237 # make directories
238 if self.spec_only:
239 spec_dir = self.dist_dir
240 self.mkpath(spec_dir)
241 else:
242 rpm_dir = {}
243 for d in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'):
244 rpm_dir[d] = os.path.join(self.rpm_base, d)
245 self.mkpath(rpm_dir[d])
246 spec_dir = rpm_dir['SPECS']
248 # Spec file goes into 'dist_dir' if '--spec-only specified',
249 # build/rpm.<plat> otherwise.
250 spec_path = os.path.join(spec_dir,
251 "%s.spec" % self.distribution.get_name())
252 self.execute(write_file,
253 (spec_path,
254 self._make_spec_file()),
255 "writing '%s'" % spec_path)
257 if self.spec_only: # stop if requested
258 return
260 # Make a source distribution and copy to SOURCES directory with
261 # optional icon.
262 sdist = self.reinitialize_command('sdist')
263 if self.use_bzip2:
264 sdist.formats = ['bztar']
265 else:
266 sdist.formats = ['gztar']
267 self.run_command('sdist')
269 source = sdist.get_archive_files()[0]
270 source_dir = rpm_dir['SOURCES']
271 self.copy_file(source, source_dir)
273 if self.icon:
274 if os.path.exists(self.icon):
275 self.copy_file(self.icon, source_dir)
276 else:
277 raise DistutilsFileError, \
278 "icon file '%s' does not exist" % self.icon
281 # build package
282 log.info("building RPMs")
283 rpm_cmd = ['rpm']
284 if self.source_only: # what kind of RPMs?
285 rpm_cmd.append('-bs')
286 elif self.binary_only:
287 rpm_cmd.append('-bb')
288 else:
289 rpm_cmd.append('-ba')
290 if self.rpm3_mode:
291 rpm_cmd.extend(['--define',
292 '_topdir %s/%s' % (os.getcwd(), self.rpm_base),])
293 if not self.keep_temp:
294 rpm_cmd.append('--clean')
295 rpm_cmd.append(spec_path)
296 self.spawn(rpm_cmd)
298 # XXX this is a nasty hack -- we really should have a proper way to
299 # find out the names of the RPM files created; also, this assumes
300 # that RPM creates exactly one source and one binary RPM.
301 if not self.dry_run:
302 if not self.binary_only:
303 srpms = glob.glob(os.path.join(rpm_dir['SRPMS'], "*.rpm"))
304 assert len(srpms) == 1, \
305 "unexpected number of SRPM files found: %s" % srpms
306 self.move_file(srpms[0], self.dist_dir)
308 if not self.source_only:
309 rpms = glob.glob(os.path.join(rpm_dir['RPMS'], "*/*.rpm"))
310 assert len(rpms) == 1, \
311 "unexpected number of RPM files found: %s" % rpms
312 self.move_file(rpms[0], self.dist_dir)
314 # run()
317 def _make_spec_file(self):
318 """Generate the text of an RPM spec file and return it as a
319 list of strings (one per line).
321 # definitions and headers
322 spec_file = [
323 '%define name ' + self.distribution.get_name(),
324 '%define version ' + self.distribution.get_version(),
325 '%define release ' + self.release,
327 'Summary: ' + self.distribution.get_description(),
330 # put locale summaries into spec file
331 # XXX not supported for now (hard to put a dictionary
332 # in a config file -- arg!)
333 #for locale in self.summaries.keys():
334 # spec_file.append('Summary(%s): %s' % (locale,
335 # self.summaries[locale]))
337 spec_file.extend([
338 'Name: %{name}',
339 'Version: %{version}',
340 'Release: %{release}',])
342 # XXX yuck! this filename is available from the "sdist" command,
343 # but only after it has run: and we create the spec file before
344 # running "sdist", in case of --spec-only.
345 if self.use_bzip2:
346 spec_file.append('Source0: %{name}-%{version}.tar.bz2')
347 else:
348 spec_file.append('Source0: %{name}-%{version}.tar.gz')
350 spec_file.extend([
351 'Copyright: ' + self.distribution.get_license(),
352 'Group: ' + self.group,
353 'BuildRoot: %{_tmppath}/%{name}-buildroot',
354 'Prefix: %{_prefix}', ])
356 # noarch if no extension modules
357 if not self.distribution.has_ext_modules():
358 spec_file.append('BuildArchitectures: noarch')
360 for field in ('Vendor',
361 'Packager',
362 'Provides',
363 'Requires',
364 'Conflicts',
365 'Obsoletes',
367 val = getattr(self, string.lower(field))
368 if type(val) is ListType:
369 spec_file.append('%s: %s' % (field, string.join(val)))
370 elif val is not None:
371 spec_file.append('%s: %s' % (field, val))
374 if self.distribution.get_url() != 'UNKNOWN':
375 spec_file.append('Url: ' + self.distribution.get_url())
377 if self.distribution_name:
378 spec_file.append('Distribution: ' + self.distribution_name)
380 if self.build_requires:
381 spec_file.append('BuildRequires: ' +
382 string.join(self.build_requires))
384 if self.icon:
385 spec_file.append('Icon: ' + os.path.basename(self.icon))
387 spec_file.extend([
389 '%description',
390 self.distribution.get_long_description()
393 # put locale descriptions into spec file
394 # XXX again, suppressed because config file syntax doesn't
395 # easily support this ;-(
396 #for locale in self.descriptions.keys():
397 # spec_file.extend([
398 # '',
399 # '%description -l ' + locale,
400 # self.descriptions[locale],
401 # ])
403 # rpm scripts
404 # figure out default build script
405 def_build = "%s setup.py build" % self.python
406 if self.use_rpm_opt_flags:
407 def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build
409 # insert contents of files
411 # XXX this is kind of misleading: user-supplied options are files
412 # that we open and interpolate into the spec file, but the defaults
413 # are just text that we drop in as-is. Hmmm.
415 script_options = [
416 ('prep', 'prep_script', "%setup"),
417 ('build', 'build_script', def_build),
418 ('install', 'install_script',
419 ("%s setup.py install "
420 "--root=$RPM_BUILD_ROOT "
421 "--record=INSTALLED_FILES") % self.python),
422 ('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"),
423 ('pre', 'pre_install', None),
424 ('post', 'post_install', None),
425 ('preun', 'pre_uninstall', None),
426 ('postun', 'post_uninstall', None),
429 for (rpm_opt, attr, default) in script_options:
430 # Insert contents of file referred to, if no file is refered to
431 # use 'default' as contents of script
432 val = getattr(self, attr)
433 if val or default:
434 spec_file.extend([
436 '%' + rpm_opt,])
437 if val:
438 spec_file.extend(string.split(open(val, 'r').read(), '\n'))
439 else:
440 spec_file.append(default)
443 # files section
444 spec_file.extend([
446 '%files -f INSTALLED_FILES',
447 '%defattr(-,root,root)',
450 if self.doc_files:
451 spec_file.append('%doc ' + string.join(self.doc_files))
453 if self.changelog:
454 spec_file.extend([
456 '%changelog',])
457 spec_file.extend(self.changelog)
459 return spec_file
461 # _make_spec_file ()
463 def _format_changelog(self, changelog):
464 """Format the changelog correctly and convert it to a list of strings
466 if not changelog:
467 return changelog
468 new_changelog = []
469 for line in string.split(string.strip(changelog), '\n'):
470 line = string.strip(line)
471 if line[0] == '*':
472 new_changelog.extend(['', line])
473 elif line[0] == '-':
474 new_changelog.append(line)
475 else:
476 new_changelog.append(' ' + line)
478 # strip trailing newline inserted by first changelog entry
479 if not new_changelog[0]:
480 del new_changelog[0]
482 return new_changelog
484 # _format_changelog()
486 # class bdist_rpm