blob: 3302eea24beff953fe741975b7d1d43fa96d3c00 [file] [log] [blame]
Gregory P. Smith86ee81e2000-05-13 03:11:40 +00001"""distutils.command.bdist_rpm
2
3Implements the Distutils 'bdist_rpm' command (create RPM source and binary
Greg Ward4fb29e52000-05-27 17:27:23 +00004distributions)."""
Gregory P. Smith86ee81e2000-05-13 03:11:40 +00005
6# created 2000/04/25, by Harry Henry Gebel
7
8__revision__ = "$Id$"
9
Greg Ward4227dc12000-06-04 14:20:57 +000010import os, string
Greg Ward4fb29e52000-05-27 17:27:23 +000011from types import *
Greg Wardefbd0712000-06-02 02:01:51 +000012from distutils.core import Command, DEBUG
Greg Ward01a46942000-06-01 00:40:25 +000013from distutils.util import get_platform, write_file
Gregory P. Smith86ee81e2000-05-13 03:11:40 +000014from distutils.errors import *
Gregory P. Smith86ee81e2000-05-13 03:11:40 +000015
16class bdist_rpm (Command):
17
18 description = "create an RPM distribution"
19
20 user_options = [
Greg Ward7ce6d072000-06-04 15:30:35 +000021 ('bdist-base=', None,
Greg Ward01a46942000-06-01 00:40:25 +000022 "base directory for creating built distributions"),
Greg Ward7ce6d072000-06-04 15:30:35 +000023 ('rpm-base=', None,
24 "base directory for creating RPMs (defaults to \"rpm\" under "
25 "--bdist-base; must be specified for RPM 2)"),
Gregory P. Smith86ee81e2000-05-13 03:11:40 +000026 ('spec-only', None,
Greg Ward69413da2000-05-31 23:56:45 +000027 "only regenerate spec file"),
Gregory P. Smith86ee81e2000-05-13 03:11:40 +000028 ('source-only', None,
Greg Ward69413da2000-05-31 23:56:45 +000029 "only generate source RPM"),
Gregory P. Smith86ee81e2000-05-13 03:11:40 +000030 ('binary-only', None,
Greg Ward69413da2000-05-31 23:56:45 +000031 "only generate binary RPM"),
Gregory P. Smith86ee81e2000-05-13 03:11:40 +000032 ('use-bzip2', None,
Greg Ward69413da2000-05-31 23:56:45 +000033 "use bzip2 instead of gzip to create source distribution"),
Greg Ward12593922000-06-02 01:49:58 +000034
35 # More meta-data: too RPM-specific to put in the setup script,
36 # but needs to go in the .spec file -- so we make these options
37 # to "bdist_rpm". The idea is that packagers would put this
38 # info in setup.cfg, although they are of course free to
39 # supply it on the command line.
40 ('distribution-name', None,
41 "name of the (Linux) distribution name to which this "
42 "RPM applies (*not* the name of the module distribution!)"),
43 ('group', None,
44 "package classification [default: \"Development/Libraries\"]"),
45 ('release', None,
46 "RPM release number"),
47 ('serial', None,
Greg Warda68c93a2000-06-04 15:00:34 +000048 "RPM serial number"),
Greg Ward12593922000-06-02 01:49:58 +000049 ('vendor', None,
50 "RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") "
51 "[default: maintainer or author from setup script]"),
52 ('packager', None,
53 "RPM packager (eg. \"Jane Doe <jane@example.net>\")"
54 "[default: vendor]"),
55 ('doc-files', None,
56 "list of documentation files (space or comma-separated)"),
57 ('changelog', None,
Greg Warda68c93a2000-06-04 15:00:34 +000058 "path to RPM changelog"),
Greg Ward12593922000-06-02 01:49:58 +000059 ('icon', None,
60 "name of icon file"),
Greg Warda68c93a2000-06-04 15:00:34 +000061 ('prep-script', None,
62 "pre-build script (Bourne shell code)"),
63 ('build-script', None,
64 "build script (Bourne shell code)"),
65 ('install-script', None,
66 "installation script (Bourne shell code)"),
67 ('clean-script', None,
68 "clean script (Bourne shell code)"),
Greg Ward12593922000-06-02 01:49:58 +000069 ('pre-install', None,
70 "pre-install script (Bourne shell code)"),
71 ('post-install', None,
72 "post-install script (Bourne shell code)"),
73 ('pre-uninstall', None,
74 "pre-uninstall script (Bourne shell code)"),
75 ('post-uninstall', None,
76 "post-uninstall script (Bourne shell code)"),
Greg Ward12593922000-06-02 01:49:58 +000077 ('provides', None,
Greg Warda68c93a2000-06-04 15:00:34 +000078 "capabilities provided by this package"),
Greg Ward12593922000-06-02 01:49:58 +000079 ('requires', None,
Greg Warda68c93a2000-06-04 15:00:34 +000080 "capabilities required by this package"),
Greg Ward12593922000-06-02 01:49:58 +000081 ('conflicts', None,
Greg Warda68c93a2000-06-04 15:00:34 +000082 "capabilities which conflict with this package"),
Greg Ward12593922000-06-02 01:49:58 +000083 ('build-requires', None,
Greg Warda68c93a2000-06-04 15:00:34 +000084 "capabilities required to build this package"),
Greg Ward12593922000-06-02 01:49:58 +000085 ('obsoletes', None,
Greg Warda68c93a2000-06-04 15:00:34 +000086 "capabilities made obsolete by this package"),
Greg Ward12593922000-06-02 01:49:58 +000087
88 # Actions to take when building RPM
Greg Ward69413da2000-05-31 23:56:45 +000089 ('clean', None,
90 "clean up RPM build directory [default]"),
Gregory P. Smith86ee81e2000-05-13 03:11:40 +000091 ('no-clean', None,
Greg Ward69413da2000-05-31 23:56:45 +000092 "don't clean up RPM build directory"),
93 ('use-rpm-opt-flags', None,
94 "compile with RPM_OPT_FLAGS when building from source RPM"),
Gregory P. Smith86ee81e2000-05-13 03:11:40 +000095 ('no-rpm-opt-flags', None,
Greg Ward69413da2000-05-31 23:56:45 +000096 "do not pass any RPM CFLAGS to compiler"),
Greg Warda68c93a2000-06-04 15:00:34 +000097 ('rpm3-mode', None,
98 "RPM 3 compatibility mode (default)"),
99 ('rpm2-mode', None,
100 "RPM 2 compatibility mode"),
Greg Ward69413da2000-05-31 23:56:45 +0000101 ]
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000102
Greg Ward69413da2000-05-31 23:56:45 +0000103 negative_opt = {'no-clean': 'clean',
Greg Warda68c93a2000-06-04 15:00:34 +0000104 'no-rpm-opt-flags': 'use-rpm-opt-flags',
105 'rpm2-mode': 'rpm3-mode'}
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000106
Greg Ward69413da2000-05-31 23:56:45 +0000107
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000108 def initialize_options (self):
Greg Ward01a46942000-06-01 00:40:25 +0000109 self.bdist_base = None
Greg Ward7ce6d072000-06-04 15:30:35 +0000110 self.rpm_base = None
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000111 self.spec_only = None
112 self.binary_only = None
113 self.source_only = None
114 self.use_bzip2 = None
Greg Ward12593922000-06-02 01:49:58 +0000115
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
125
Greg Warda68c93a2000-06-04 15:00:34 +0000126 self.prep_script = None
127 self.build_script = None
128 self.install_script = None
129 self.clean_script = None
Greg Ward12593922000-06-02 01:49:58 +0000130 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
140
Greg Ward69413da2000-05-31 23:56:45 +0000141 self.clean = 1
142 self.use_rpm_opt_flags = 1
Greg Warda68c93a2000-06-04 15:00:34 +0000143 self.rpm3_mode = 1
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000144
145 # initialize_options()
146
147
148 def finalize_options (self):
Greg Ward01a46942000-06-01 00:40:25 +0000149 self.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))
Greg Ward7ce6d072000-06-04 15:30:35 +0000150 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")
155
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000156 if os.name != 'posix':
157 raise DistutilsPlatformError, \
158 ("don't know how to create RPM "
159 "distributions on platform %s" % os.name)
160 if self.binary_only and self.source_only:
161 raise DistutilsOptionsError, \
Greg Ward01a46942000-06-01 00:40:25 +0000162 "cannot supply both '--source-only' and '--binary-only'"
Greg Ward12593922000-06-02 01:49:58 +0000163
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000164 # don't pass CFLAGS to pure python distributions
165 if not self.distribution.has_ext_modules():
Greg Ward69413da2000-05-31 23:56:45 +0000166 self.use_rpm_opt_flags = 0
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000167
Greg Ward12593922000-06-02 01:49:58 +0000168 self.finalize_package_data()
169
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000170 # finalize_options()
171
Greg Ward12593922000-06-02 01:49:58 +0000172 def finalize_package_data (self):
173 self.ensure_string('group', "Development/Libraries")
174 self.ensure_string('vendor',
175 "%s <%s>" % (self.distribution.get_contact(),
176 self.distribution.get_contact_email()))
Greg Warda68c93a2000-06-04 15:00:34 +0000177 self.ensure_string('packager')
Greg Ward12593922000-06-02 01:49:58 +0000178 self.ensure_string_list('doc_files')
179 if type(self.doc_files) is ListType:
180 for readme in ('README', 'README.txt'):
181 if os.path.exists(readme) and readme not in self.doc_files:
182 self.doc.append(readme)
183
Greg Warda68c93a2000-06-04 15:00:34 +0000184 self.ensure_string('release', "1")
Greg Ward12593922000-06-02 01:49:58 +0000185 self.ensure_string('serial') # should it be an int?
186
Greg Ward12593922000-06-02 01:49:58 +0000187 self.ensure_string('distribution_name')
188
Greg Warda68c93a2000-06-04 15:00:34 +0000189 self.ensure_string('changelog')
190 # Format changelog correctly
191 self.changelog = self._format_changelog(self.changelog)
Greg Ward12593922000-06-02 01:49:58 +0000192
Greg Warda68c93a2000-06-04 15:00:34 +0000193 self.ensure_filename('icon')
194
195 self.ensure_filename('prep_script')
196 self.ensure_filename('build_script')
197 self.ensure_filename('install_script')
198 self.ensure_filename('clean_script')
Greg Ward12593922000-06-02 01:49:58 +0000199 self.ensure_filename('pre_install')
200 self.ensure_filename('post_install')
201 self.ensure_filename('pre_uninstall')
202 self.ensure_filename('post_uninstall')
203
204 # XXX don't forget we punted on summaries and descriptions -- they
205 # should be handled here eventually!
206
207 # Now *this* is some meta-data that belongs in the setup script...
208 self.ensure_string_list('provides')
209 self.ensure_string_list('requires')
210 self.ensure_string_list('conflicts')
211 self.ensure_string_list('build_requires')
212 self.ensure_string_list('obsoletes')
213
214 # finalize_package_data ()
215
216
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000217 def run (self):
Greg Ward12593922000-06-02 01:49:58 +0000218
Greg Wardefbd0712000-06-02 02:01:51 +0000219 if DEBUG:
220 print "before _get_package_data():"
221 print "vendor =", self.vendor
222 print "packager =", self.packager
223 print "doc_files =", self.doc_files
224 print "changelog =", self.changelog
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000225
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000226 # make directories
227 if self.spec_only:
Greg Ward01a46942000-06-01 00:40:25 +0000228 spec_dir = "dist"
229 self.mkpath(spec_dir) # XXX should be configurable
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000230 else:
Greg Ward01a46942000-06-01 00:40:25 +0000231 rpm_dir = {}
Greg Ward4fb29e52000-05-27 17:27:23 +0000232 for d in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'):
Greg Ward7ce6d072000-06-04 15:30:35 +0000233 rpm_dir[d] = os.path.join(self.rpm_base, d)
Greg Ward01a46942000-06-01 00:40:25 +0000234 self.mkpath(rpm_dir[d])
235 spec_dir = rpm_dir['SPECS']
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000236
Greg Ward01a46942000-06-01 00:40:25 +0000237 # Spec file goes into 'dist' directory if '--spec-only specified',
238 # into build/rpm.<plat> otherwise.
239 spec_path = os.path.join(spec_dir,
240 "%s.spec" % self.distribution.get_name())
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000241 self.execute(write_file,
242 (spec_path,
243 self._make_spec_file()),
Greg Ward01a46942000-06-01 00:40:25 +0000244 "writing '%s'" % spec_path)
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000245
246 if self.spec_only: # stop if requested
247 return
248
Greg Ward01a46942000-06-01 00:40:25 +0000249 # Make a source distribution and copy to SOURCES directory with
250 # optional icon.
251 sdist = self.reinitialize_command ('sdist')
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000252 if self.use_bzip2:
253 sdist.formats = ['bztar']
254 else:
255 sdist.formats = ['gztar']
Greg Ward4fb29e52000-05-27 17:27:23 +0000256 self.run_command('sdist')
Greg Ward01a46942000-06-01 00:40:25 +0000257
258 source = sdist.get_archive_files()[0]
259 source_dir = rpm_dir['SOURCES']
260 self.copy_file(source, source_dir)
261
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000262 if self.icon:
Greg Ward4fb29e52000-05-27 17:27:23 +0000263 if os.path.exists(self.icon):
Greg Ward01a46942000-06-01 00:40:25 +0000264 self.copy_file(self.icon, source_dir)
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000265 else:
266 raise DistutilsFileError, \
Greg Ward01a46942000-06-01 00:40:25 +0000267 "icon file '%s' does not exist" % self.icon
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000268
269
270 # build package
271 self.announce('Building RPMs')
272 rpm_args = ['rpm',]
273 if self.source_only: # what kind of RPMs?
274 rpm_args.append('-bs')
275 elif self.binary_only:
276 rpm_args.append('-bb')
277 else:
278 rpm_args.append('-ba')
Greg Warda68c93a2000-06-04 15:00:34 +0000279 if self.rpm3_mode:
280 rpm_args.extend(['--define',
Greg Ward7ce6d072000-06-04 15:30:35 +0000281 '_topdir %s/%s' % (os.getcwd(), self.rpm_base),])
Greg Ward69413da2000-05-31 23:56:45 +0000282 if self.clean:
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000283 rpm_args.append('--clean')
284 rpm_args.append(spec_path)
285 self.spawn(rpm_args)
286
287 # run()
288
289
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000290 def _make_spec_file(self):
Greg Ward12593922000-06-02 01:49:58 +0000291 """Generate the text of an RPM spec file and return it as a
292 list of strings (one per line).
293 """
294 # definitions and headers
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000295 spec_file = [
296 '%define name ' + self.distribution.get_name(),
297 '%define version ' + self.distribution.get_version(),
298 '%define release ' + self.release,
299 '',
300 'Summary: ' + self.distribution.get_description(),
301 ]
302
303 # put locale summaries into spec file
Greg Ward12593922000-06-02 01:49:58 +0000304 # XXX not supported for now (hard to put a dictionary
305 # in a config file -- arg!)
306 #for locale in self.summaries.keys():
307 # spec_file.append('Summary(%s): %s' % (locale,
308 # self.summaries[locale]))
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000309
310 spec_file.extend([
311 'Name: %{name}',
312 'Version: %{version}',
313 'Release: %{release}',])
Greg Ward12593922000-06-02 01:49:58 +0000314
315 # XXX yuck! this filename is available from the "sdist" command,
316 # but only after it has run: and we create the spec file before
317 # running "sdist", in case of --spec-only.
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000318 if self.use_bzip2:
319 spec_file.append('Source0: %{name}-%{version}.tar.bz2')
320 else:
321 spec_file.append('Source0: %{name}-%{version}.tar.gz')
Greg Ward12593922000-06-02 01:49:58 +0000322
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000323 spec_file.extend([
324 'Copyright: ' + self.distribution.get_licence(),
325 'Group: ' + self.group,
326 'BuildRoot: %{_tmppath}/%{name}-buildroot',
327 'Prefix: %{_prefix}', ])
328
329 # noarch if no extension modules
330 if not self.distribution.has_ext_modules():
331 spec_file.append('BuildArchitectures: noarch')
332
333 for field in ('Vendor',
334 'Packager',
335 'Provides',
336 'Requires',
337 'Conflicts',
338 'Obsoletes',
339 ):
Greg Ward12593922000-06-02 01:49:58 +0000340 val = getattr(self, string.lower(field))
341 if type(val) is ListType:
342 spec_file.append('%s: %s' % (field, string.join(val)))
343 elif val is not None:
344 spec_file.append('%s: %s' % (field, val))
345
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000346
347 if self.distribution.get_url() != 'UNKNOWN':
348 spec_file.append('Url: ' + self.distribution.get_url())
349
350 if self.distribution_name:
351 spec_file.append('Distribution: ' + self.distribution_name)
352
353 if self.build_requires:
Greg Ward12593922000-06-02 01:49:58 +0000354 spec_file.append('BuildRequires: ' +
355 string.join(self.build_requires))
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000356
357 if self.icon:
Greg Ward4fb29e52000-05-27 17:27:23 +0000358 spec_file.append('Icon: ' + os.path.basename(self.icon))
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000359
360 spec_file.extend([
361 '',
362 '%description',
363 self.distribution.get_long_description()
364 ])
365
366 # put locale descriptions into spec file
Greg Ward12593922000-06-02 01:49:58 +0000367 # XXX again, suppressed because config file syntax doesn't
368 # easily support this ;-(
369 #for locale in self.descriptions.keys():
370 # spec_file.extend([
371 # '',
372 # '%description -l ' + locale,
373 # self.descriptions[locale],
374 # ])
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000375
376 # rpm scripts
Greg Warda68c93a2000-06-04 15:00:34 +0000377 # figure out default build script
378 if self.use_rpm_opt_flags:
379 def_build = 'env CFLAGS="$RPM_OPT_FLAGS" python setup.py build'
380 else:
381 def_build = 'python setup.py build'
382 # insert contents of files
383
384 # XXX this is kind of misleading: user-supplied options are files
385 # that we open and interpolate into the spec file, but the defaults
386 # are just text that we drop in as-is. Hmmm.
387
388 script_options = [
389 ('prep', 'prep_script', "%setup"),
390 ('build', 'build_script', def_build),
391 ('install', 'install_script',
392 "python setup.py install "
393 "--root=$RPM_BUILD_ROOT "
394 "--record=INSTALLED_FILES"),
395 ('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"),
396 ('pre', 'pre_install', None),
397 ('post', 'post_install', None),
398 ('preun', 'pre_uninstall', None),
Greg Ward7ce6d072000-06-04 15:30:35 +0000399 ('postun', 'post_uninstall', None),
Greg Warda68c93a2000-06-04 15:00:34 +0000400 ]
401
402 for (rpm_opt, attr, default) in script_options:
Greg Ward612eb9f2000-07-27 02:13:20 +0000403 # Insert contents of file referred to, if no file is refered to
Greg Warda68c93a2000-06-04 15:00:34 +0000404 # use 'default' as contents of script
Greg Ward12593922000-06-02 01:49:58 +0000405 val = getattr(self, attr)
Greg Warda68c93a2000-06-04 15:00:34 +0000406 if val or default:
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000407 spec_file.extend([
408 '',
Greg Warda68c93a2000-06-04 15:00:34 +0000409 '%' + rpm_opt,])
410 if val:
411 spec_file.extend(string.split(open(val, 'r').read(), '\n'))
412 else:
413 spec_file.append(default)
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000414
Greg Warda68c93a2000-06-04 15:00:34 +0000415
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000416 # files section
417 spec_file.extend([
418 '',
419 '%files -f INSTALLED_FILES',
420 '%defattr(-,root,root)',
421 ])
422
Greg Ward12593922000-06-02 01:49:58 +0000423 if self.doc_files:
424 spec_file.append('%doc ' + string.join(self.doc_files))
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000425
426 if self.changelog:
427 spec_file.extend([
428 '',
Greg Warda68c93a2000-06-04 15:00:34 +0000429 '%changelog',])
430 spec_file.extend(self.changelog)
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000431
432 return spec_file
433
Greg Ward67392582000-06-02 01:52:04 +0000434 # _make_spec_file ()
Gregory P. Smith86ee81e2000-05-13 03:11:40 +0000435
Greg Warda68c93a2000-06-04 15:00:34 +0000436 def _format_changelog(self, changelog):
437 """Format the changelog correctly and convert it to a list of strings
438 """
Greg Wardd2412a32000-06-08 14:21:23 +0000439 if not changelog:
440 return changelog
Greg Warda68c93a2000-06-04 15:00:34 +0000441 new_changelog = []
442 for line in string.split(string.strip(changelog), '\n'):
443 line = string.strip(line)
444 if line[0] == '*':
445 new_changelog.extend(['', line])
446 elif line[0] == '-':
447 new_changelog.append(line)
448 else:
449 new_changelog.append(' ' + line)
450
451 # strip trailing newline inserted by first changelog entry
452 if not new_changelog[0]:
453 del new_changelog[0]
454
455 return new_changelog
456
457 # _format_changelog()
458
Greg Ward67392582000-06-02 01:52:04 +0000459# class bdist_rpm