Update to 2.7.3
[profile/ivi/python.git] / Lib / distutils / command / bdist_rpm.py
1 """distutils.command.bdist_rpm
2
3 Implements the Distutils 'bdist_rpm' command (create RPM source and binary
4 distributions)."""
5
6 __revision__ = "$Id$"
7
8 import sys
9 import os
10 import string
11
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
18
19 class bdist_rpm (Command):
20
21     description = "create an RPM distribution"
22
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"),
46
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          "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"),
83         ('no-autoreq', None,
84          "do not automatically calculate dependencies"),
85
86         # Actions to take when building RPM
87         ('keep-temp', 'k',
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"),
95         ('rpm3-mode', None,
96          "RPM 3 compatibility mode (default)"),
97         ('rpm2-mode', None,
98          "RPM 2 compatibility mode"),
99
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"),
105
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"),
112
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"),
117
118         ('clean-script=', None,
119          "Specify a script for the CLEAN phase of RPM building"),
120
121         ('verify-script=', None,
122          "Specify a script for the VERIFY phase of the RPM build"),
123
124         # Allow a packager to explicitly force an architecture
125         ('force-arch=', None,
126          "Force an architecture onto the RPM build process"),
127
128         ('quiet', 'q',
129          "Run the INSTALL phase of RPM building in quiet mode"),
130         ]
131
132     boolean_options = ['keep-temp', 'use-rpm-opt-flags', 'rpm3-mode',
133                        'no-autoreq', 'quiet']
134
135     negative_opt = {'no-keep-temp': 'keep-temp',
136                     'no-rpm-opt-flags': 'use-rpm-opt-flags',
137                     'rpm2-mode': 'rpm3-mode'}
138
139
140     def initialize_options (self):
141         self.bdist_base = None
142         self.rpm_base = None
143         self.dist_dir = None
144         self.python = 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
150
151         self.distribution_name = None
152         self.group = None
153         self.release = None
154         self.serial = None
155         self.vendor = None
156         self.packager = None
157         self.doc_files = None
158         self.changelog = None
159         self.icon = None
160
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
170         self.prep = None
171         self.provides = None
172         self.requires = None
173         self.conflicts = None
174         self.build_requires = None
175         self.obsoletes = None
176
177         self.keep_temp = 0
178         self.use_rpm_opt_flags = 1
179         self.rpm3_mode = 1
180         self.no_autoreq = 0
181
182         self.force_arch = None
183         self.quiet = 0
184
185     # initialize_options()
186
187
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")
195
196         if self.python is None:
197             if self.fix_python:
198                 self.python = sys.executable
199             else:
200                 self.python = "python"
201         elif self.fix_python:
202             raise DistutilsOptionError, \
203                   "--python and --fix-python are mutually exclusive options"
204
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'"
212
213         # don't pass CFLAGS to pure python distributions
214         if not self.distribution.has_ext_modules():
215             self.use_rpm_opt_flags = 0
216
217         self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
218         self.finalize_package_data()
219
220     # finalize_options()
221
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)
233
234         self.ensure_string('release', "1")
235         self.ensure_string('serial')   # should it be an int?
236
237         self.ensure_string('distribution_name')
238
239         self.ensure_string('changelog')
240           # Format changelog correctly
241         self.changelog = self._format_changelog(self.changelog)
242
243         self.ensure_filename('icon')
244
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')
254
255         # XXX don't forget we punted on summaries and descriptions -- they
256         # should be handled here eventually!
257
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')
264
265         self.ensure_string('force_arch')
266     # finalize_package_data ()
267
268
269     def run (self):
270
271         if DEBUG:
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
277
278         # make directories
279         if self.spec_only:
280             spec_dir = self.dist_dir
281             self.mkpath(spec_dir)
282         else:
283             rpm_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']
288
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,
294                      (spec_path,
295                       self._make_spec_file()),
296                      "writing '%s'" % spec_path)
297
298         if self.spec_only: # stop if requested
299             return
300
301         # Make a source distribution and copy to SOURCES directory with
302         # optional icon.
303         saved_dist_files = self.distribution.dist_files[:]
304         sdist = self.reinitialize_command('sdist')
305         if self.use_bzip2:
306             sdist.formats = ['bztar']
307         else:
308             sdist.formats = ['gztar']
309         self.run_command('sdist')
310         self.distribution.dist_files = saved_dist_files
311
312         source = sdist.get_archive_files()[0]
313         source_dir = rpm_dir['SOURCES']
314         self.copy_file(source, source_dir)
315
316         if self.icon:
317             if os.path.exists(self.icon):
318                 self.copy_file(self.icon, source_dir)
319             else:
320                 raise DistutilsFileError, \
321                       "icon file '%s' does not exist" % self.icon
322
323
324         # build package
325         log.info("building RPMs")
326         rpm_cmd = ['rpm']
327         if os.path.exists('/usr/bin/rpmbuild') or \
328            os.path.exists('/bin/rpmbuild'):
329             rpm_cmd = ['rpmbuild']
330
331         if self.source_only: # what kind of RPMs?
332             rpm_cmd.append('-bs')
333         elif self.binary_only:
334             rpm_cmd.append('-bb')
335         else:
336             rpm_cmd.append('-ba')
337         if self.rpm3_mode:
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')
342
343         if self.quiet:
344             rpm_cmd.append('--quiet')
345
346         rpm_cmd.append(spec_path)
347         # Determine the binary rpm names that should be built out of this spec
348         # file
349         # Note that some of these may not be really built (if the file
350         # list is empty)
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)
356
357         out = os.popen(q_cmd)
358         try:
359             binary_rpms = []
360             source_rpm = None
361             while 1:
362                 line = out.readline()
363                 if not line:
364                     break
365                 l = string.split(string.strip(line))
366                 assert(len(l) == 2)
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:
370                     source_rpm = l[0]
371
372             status = out.close()
373             if status:
374                 raise DistutilsExecError("Failed to execute: %s" % repr(q_cmd))
375
376         finally:
377             out.close()
378
379         self.spawn(rpm_cmd)
380
381         if not self.dry_run:
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)
386
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)
392     # run()
393
394     def _dist_path(self, path):
395         return os.path.join(self.dist_dir, os.path.basename(path))
396
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).
400         """
401         # definitions and headers
402         spec_file = [
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('-','_'),
407             '',
408             'Summary: ' + self.distribution.get_description(),
409             ]
410
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]))
417
418         spec_file.extend([
419             'Name: %{name}',
420             'Version: %{version}',
421             'Release: %{release}',])
422
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.
426         if self.use_bzip2:
427             spec_file.append('Source0: %{name}-%{unmangled_version}.tar.bz2')
428         else:
429             spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz')
430
431         spec_file.extend([
432             'License: ' + self.distribution.get_license(),
433             'Group: ' + self.group,
434             'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot',
435             'Prefix: %{_prefix}', ])
436
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')
441         else:
442             spec_file.append( 'BuildArch: %s' % self.force_arch )
443
444         for field in ('Vendor',
445                       'Packager',
446                       'Provides',
447                       'Requires',
448                       'Conflicts',
449                       'Obsoletes',
450                       ):
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))
456
457
458         if self.distribution.get_url() != 'UNKNOWN':
459             spec_file.append('Url: ' + self.distribution.get_url())
460
461         if self.distribution_name:
462             spec_file.append('Distribution: ' + self.distribution_name)
463
464         if self.build_requires:
465             spec_file.append('BuildRequires: ' +
466                              string.join(self.build_requires))
467
468         if self.icon:
469             spec_file.append('Icon: ' + os.path.basename(self.icon))
470
471         if self.no_autoreq:
472             spec_file.append('AutoReq: 0')
473
474         spec_file.extend([
475             '',
476             '%description',
477             self.distribution.get_long_description()
478             ])
479
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():
484         #    spec_file.extend([
485         #        '',
486         #        '%description -l ' + locale,
487         #        self.descriptions[locale],
488         #        ])
489
490         # rpm scripts
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
496
497         # insert contents of files
498
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.
502
503         install_cmd = ('%s install -O1 --root=$RPM_BUILD_ROOT '
504                        '--record=INSTALLED_FILES') % def_setup_call
505
506         script_options = [
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),
516         ]
517
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)
522             if val or default:
523                 spec_file.extend([
524                     '',
525                     '%' + rpm_opt,])
526                 if val:
527                     spec_file.extend(string.split(open(val, 'r').read(), '\n'))
528                 else:
529                     spec_file.append(default)
530
531
532         # files section
533         spec_file.extend([
534             '',
535             '%files -f INSTALLED_FILES',
536             '%defattr(-,root,root)',
537             ])
538
539         if self.doc_files:
540             spec_file.append('%doc ' + string.join(self.doc_files))
541
542         if self.changelog:
543             spec_file.extend([
544                 '',
545                 '%changelog',])
546             spec_file.extend(self.changelog)
547
548         return spec_file
549
550     # _make_spec_file ()
551
552     def _format_changelog(self, changelog):
553         """Format the changelog correctly and convert it to a list of strings
554         """
555         if not changelog:
556             return changelog
557         new_changelog = []
558         for line in string.split(string.strip(changelog), '\n'):
559             line = string.strip(line)
560             if line[0] == '*':
561                 new_changelog.extend(['', line])
562             elif line[0] == '-':
563                 new_changelog.append(line)
564             else:
565                 new_changelog.append('  ' + line)
566
567         # strip trailing newline inserted by first changelog entry
568         if not new_changelog[0]:
569             del new_changelog[0]
570
571         return new_changelog
572
573     # _format_changelog()
574
575 # class bdist_rpm