ยปCore Development>Code coverage>Lib/distutils/command/bdist_rpm.py

Python code coverage for Lib/distutils/command/bdist_rpm.py

#countcontent
1n/a"""distutils.command.bdist_rpm
2n/a
3n/aImplements the Distutils 'bdist_rpm' command (create RPM source and binary
4n/adistributions)."""
5n/a
6n/aimport subprocess, sys, os
7n/afrom distutils.core import Command
8n/afrom distutils.debug import DEBUG
9n/afrom distutils.util import get_platform
10n/afrom distutils.file_util import write_file
11n/afrom distutils.errors import *
12n/afrom distutils.sysconfig import get_python_version
13n/afrom distutils import log
14n/a
15n/aclass bdist_rpm(Command):
16n/a
17n/a description = "create an RPM distribution"
18n/a
19n/a user_options = [
20n/a ('bdist-base=', None,
21n/a "base directory for creating built distributions"),
22n/a ('rpm-base=', None,
23n/a "base directory for creating RPMs (defaults to \"rpm\" under "
24n/a "--bdist-base; must be specified for RPM 2)"),
25n/a ('dist-dir=', 'd',
26n/a "directory to put final RPM files in "
27n/a "(and .spec files if --spec-only)"),
28n/a ('python=', None,
29n/a "path to Python interpreter to hard-code in the .spec file "
30n/a "(default: \"python\")"),
31n/a ('fix-python', None,
32n/a "hard-code the exact path to the current Python interpreter in "
33n/a "the .spec file"),
34n/a ('spec-only', None,
35n/a "only regenerate spec file"),
36n/a ('source-only', None,
37n/a "only generate source RPM"),
38n/a ('binary-only', None,
39n/a "only generate binary RPM"),
40n/a ('use-bzip2', None,
41n/a "use bzip2 instead of gzip to create source distribution"),
42n/a
43n/a # More meta-data: too RPM-specific to put in the setup script,
44n/a # but needs to go in the .spec file -- so we make these options
45n/a # to "bdist_rpm". The idea is that packagers would put this
46n/a # info in setup.cfg, although they are of course free to
47n/a # supply it on the command line.
48n/a ('distribution-name=', None,
49n/a "name of the (Linux) distribution to which this "
50n/a "RPM applies (*not* the name of the module distribution!)"),
51n/a ('group=', None,
52n/a "package classification [default: \"Development/Libraries\"]"),
53n/a ('release=', None,
54n/a "RPM release number"),
55n/a ('serial=', None,
56n/a "RPM serial number"),
57n/a ('vendor=', None,
58n/a "RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") "
59n/a "[default: maintainer or author from setup script]"),
60n/a ('packager=', None,
61n/a "RPM packager (eg. \"Jane Doe <jane@example.net>\")"
62n/a "[default: vendor]"),
63n/a ('doc-files=', None,
64n/a "list of documentation files (space or comma-separated)"),
65n/a ('changelog=', None,
66n/a "RPM changelog"),
67n/a ('icon=', None,
68n/a "name of icon file"),
69n/a ('provides=', None,
70n/a "capabilities provided by this package"),
71n/a ('requires=', None,
72n/a "capabilities required by this package"),
73n/a ('conflicts=', None,
74n/a "capabilities which conflict with this package"),
75n/a ('build-requires=', None,
76n/a "capabilities required to build this package"),
77n/a ('obsoletes=', None,
78n/a "capabilities made obsolete by this package"),
79n/a ('no-autoreq', None,
80n/a "do not automatically calculate dependencies"),
81n/a
82n/a # Actions to take when building RPM
83n/a ('keep-temp', 'k',
84n/a "don't clean up RPM build directory"),
85n/a ('no-keep-temp', None,
86n/a "clean up RPM build directory [default]"),
87n/a ('use-rpm-opt-flags', None,
88n/a "compile with RPM_OPT_FLAGS when building from source RPM"),
89n/a ('no-rpm-opt-flags', None,
90n/a "do not pass any RPM CFLAGS to compiler"),
91n/a ('rpm3-mode', None,
92n/a "RPM 3 compatibility mode (default)"),
93n/a ('rpm2-mode', None,
94n/a "RPM 2 compatibility mode"),
95n/a
96n/a # Add the hooks necessary for specifying custom scripts
97n/a ('prep-script=', None,
98n/a "Specify a script for the PREP phase of RPM building"),
99n/a ('build-script=', None,
100n/a "Specify a script for the BUILD phase of RPM building"),
101n/a
102n/a ('pre-install=', None,
103n/a "Specify a script for the pre-INSTALL phase of RPM building"),
104n/a ('install-script=', None,
105n/a "Specify a script for the INSTALL phase of RPM building"),
106n/a ('post-install=', None,
107n/a "Specify a script for the post-INSTALL phase of RPM building"),
108n/a
109n/a ('pre-uninstall=', None,
110n/a "Specify a script for the pre-UNINSTALL phase of RPM building"),
111n/a ('post-uninstall=', None,
112n/a "Specify a script for the post-UNINSTALL phase of RPM building"),
113n/a
114n/a ('clean-script=', None,
115n/a "Specify a script for the CLEAN phase of RPM building"),
116n/a
117n/a ('verify-script=', None,
118n/a "Specify a script for the VERIFY phase of the RPM build"),
119n/a
120n/a # Allow a packager to explicitly force an architecture
121n/a ('force-arch=', None,
122n/a "Force an architecture onto the RPM build process"),
123n/a
124n/a ('quiet', 'q',
125n/a "Run the INSTALL phase of RPM building in quiet mode"),
126n/a ]
127n/a
128n/a boolean_options = ['keep-temp', 'use-rpm-opt-flags', 'rpm3-mode',
129n/a 'no-autoreq', 'quiet']
130n/a
131n/a negative_opt = {'no-keep-temp': 'keep-temp',
132n/a 'no-rpm-opt-flags': 'use-rpm-opt-flags',
133n/a 'rpm2-mode': 'rpm3-mode'}
134n/a
135n/a
136n/a def initialize_options(self):
137n/a self.bdist_base = None
138n/a self.rpm_base = None
139n/a self.dist_dir = None
140n/a self.python = None
141n/a self.fix_python = None
142n/a self.spec_only = None
143n/a self.binary_only = None
144n/a self.source_only = None
145n/a self.use_bzip2 = None
146n/a
147n/a self.distribution_name = None
148n/a self.group = None
149n/a self.release = None
150n/a self.serial = None
151n/a self.vendor = None
152n/a self.packager = None
153n/a self.doc_files = None
154n/a self.changelog = None
155n/a self.icon = None
156n/a
157n/a self.prep_script = None
158n/a self.build_script = None
159n/a self.install_script = None
160n/a self.clean_script = None
161n/a self.verify_script = None
162n/a self.pre_install = None
163n/a self.post_install = None
164n/a self.pre_uninstall = None
165n/a self.post_uninstall = None
166n/a self.prep = None
167n/a self.provides = None
168n/a self.requires = None
169n/a self.conflicts = None
170n/a self.build_requires = None
171n/a self.obsoletes = None
172n/a
173n/a self.keep_temp = 0
174n/a self.use_rpm_opt_flags = 1
175n/a self.rpm3_mode = 1
176n/a self.no_autoreq = 0
177n/a
178n/a self.force_arch = None
179n/a self.quiet = 0
180n/a
181n/a def finalize_options(self):
182n/a self.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))
183n/a if self.rpm_base is None:
184n/a if not self.rpm3_mode:
185n/a raise DistutilsOptionError(
186n/a "you must specify --rpm-base in RPM 2 mode")
187n/a self.rpm_base = os.path.join(self.bdist_base, "rpm")
188n/a
189n/a if self.python is None:
190n/a if self.fix_python:
191n/a self.python = sys.executable
192n/a else:
193n/a self.python = "python3"
194n/a elif self.fix_python:
195n/a raise DistutilsOptionError(
196n/a "--python and --fix-python are mutually exclusive options")
197n/a
198n/a if os.name != 'posix':
199n/a raise DistutilsPlatformError("don't know how to create RPM "
200n/a "distributions on platform %s" % os.name)
201n/a if self.binary_only and self.source_only:
202n/a raise DistutilsOptionError(
203n/a "cannot supply both '--source-only' and '--binary-only'")
204n/a
205n/a # don't pass CFLAGS to pure python distributions
206n/a if not self.distribution.has_ext_modules():
207n/a self.use_rpm_opt_flags = 0
208n/a
209n/a self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
210n/a self.finalize_package_data()
211n/a
212n/a def finalize_package_data(self):
213n/a self.ensure_string('group', "Development/Libraries")
214n/a self.ensure_string('vendor',
215n/a "%s <%s>" % (self.distribution.get_contact(),
216n/a self.distribution.get_contact_email()))
217n/a self.ensure_string('packager')
218n/a self.ensure_string_list('doc_files')
219n/a if isinstance(self.doc_files, list):
220n/a for readme in ('README', 'README.txt'):
221n/a if os.path.exists(readme) and readme not in self.doc_files:
222n/a self.doc_files.append(readme)
223n/a
224n/a self.ensure_string('release', "1")
225n/a self.ensure_string('serial') # should it be an int?
226n/a
227n/a self.ensure_string('distribution_name')
228n/a
229n/a self.ensure_string('changelog')
230n/a # Format changelog correctly
231n/a self.changelog = self._format_changelog(self.changelog)
232n/a
233n/a self.ensure_filename('icon')
234n/a
235n/a self.ensure_filename('prep_script')
236n/a self.ensure_filename('build_script')
237n/a self.ensure_filename('install_script')
238n/a self.ensure_filename('clean_script')
239n/a self.ensure_filename('verify_script')
240n/a self.ensure_filename('pre_install')
241n/a self.ensure_filename('post_install')
242n/a self.ensure_filename('pre_uninstall')
243n/a self.ensure_filename('post_uninstall')
244n/a
245n/a # XXX don't forget we punted on summaries and descriptions -- they
246n/a # should be handled here eventually!
247n/a
248n/a # Now *this* is some meta-data that belongs in the setup script...
249n/a self.ensure_string_list('provides')
250n/a self.ensure_string_list('requires')
251n/a self.ensure_string_list('conflicts')
252n/a self.ensure_string_list('build_requires')
253n/a self.ensure_string_list('obsoletes')
254n/a
255n/a self.ensure_string('force_arch')
256n/a
257n/a def run(self):
258n/a if DEBUG:
259n/a print("before _get_package_data():")
260n/a print("vendor =", self.vendor)
261n/a print("packager =", self.packager)
262n/a print("doc_files =", self.doc_files)
263n/a print("changelog =", self.changelog)
264n/a
265n/a # make directories
266n/a if self.spec_only:
267n/a spec_dir = self.dist_dir
268n/a self.mkpath(spec_dir)
269n/a else:
270n/a rpm_dir = {}
271n/a for d in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'):
272n/a rpm_dir[d] = os.path.join(self.rpm_base, d)
273n/a self.mkpath(rpm_dir[d])
274n/a spec_dir = rpm_dir['SPECS']
275n/a
276n/a # Spec file goes into 'dist_dir' if '--spec-only specified',
277n/a # build/rpm.<plat> otherwise.
278n/a spec_path = os.path.join(spec_dir,
279n/a "%s.spec" % self.distribution.get_name())
280n/a self.execute(write_file,
281n/a (spec_path,
282n/a self._make_spec_file()),
283n/a "writing '%s'" % spec_path)
284n/a
285n/a if self.spec_only: # stop if requested
286n/a return
287n/a
288n/a # Make a source distribution and copy to SOURCES directory with
289n/a # optional icon.
290n/a saved_dist_files = self.distribution.dist_files[:]
291n/a sdist = self.reinitialize_command('sdist')
292n/a if self.use_bzip2:
293n/a sdist.formats = ['bztar']
294n/a else:
295n/a sdist.formats = ['gztar']
296n/a self.run_command('sdist')
297n/a self.distribution.dist_files = saved_dist_files
298n/a
299n/a source = sdist.get_archive_files()[0]
300n/a source_dir = rpm_dir['SOURCES']
301n/a self.copy_file(source, source_dir)
302n/a
303n/a if self.icon:
304n/a if os.path.exists(self.icon):
305n/a self.copy_file(self.icon, source_dir)
306n/a else:
307n/a raise DistutilsFileError(
308n/a "icon file '%s' does not exist" % self.icon)
309n/a
310n/a # build package
311n/a log.info("building RPMs")
312n/a rpm_cmd = ['rpm']
313n/a if os.path.exists('/usr/bin/rpmbuild') or \
314n/a os.path.exists('/bin/rpmbuild'):
315n/a rpm_cmd = ['rpmbuild']
316n/a
317n/a if self.source_only: # what kind of RPMs?
318n/a rpm_cmd.append('-bs')
319n/a elif self.binary_only:
320n/a rpm_cmd.append('-bb')
321n/a else:
322n/a rpm_cmd.append('-ba')
323n/a rpm_cmd.extend(['--define', '__python %s' % self.python])
324n/a if self.rpm3_mode:
325n/a rpm_cmd.extend(['--define',
326n/a '_topdir %s' % os.path.abspath(self.rpm_base)])
327n/a if not self.keep_temp:
328n/a rpm_cmd.append('--clean')
329n/a
330n/a if self.quiet:
331n/a rpm_cmd.append('--quiet')
332n/a
333n/a rpm_cmd.append(spec_path)
334n/a # Determine the binary rpm names that should be built out of this spec
335n/a # file
336n/a # Note that some of these may not be really built (if the file
337n/a # list is empty)
338n/a nvr_string = "%{name}-%{version}-%{release}"
339n/a src_rpm = nvr_string + ".src.rpm"
340n/a non_src_rpm = "%{arch}/" + nvr_string + ".%{arch}.rpm"
341n/a q_cmd = r"rpm -q --qf '%s %s\n' --specfile '%s'" % (
342n/a src_rpm, non_src_rpm, spec_path)
343n/a
344n/a out = os.popen(q_cmd)
345n/a try:
346n/a binary_rpms = []
347n/a source_rpm = None
348n/a while True:
349n/a line = out.readline()
350n/a if not line:
351n/a break
352n/a l = line.strip().split()
353n/a assert(len(l) == 2)
354n/a binary_rpms.append(l[1])
355n/a # The source rpm is named after the first entry in the spec file
356n/a if source_rpm is None:
357n/a source_rpm = l[0]
358n/a
359n/a status = out.close()
360n/a if status:
361n/a raise DistutilsExecError("Failed to execute: %s" % repr(q_cmd))
362n/a
363n/a finally:
364n/a out.close()
365n/a
366n/a self.spawn(rpm_cmd)
367n/a
368n/a if not self.dry_run:
369n/a if self.distribution.has_ext_modules():
370n/a pyversion = get_python_version()
371n/a else:
372n/a pyversion = 'any'
373n/a
374n/a if not self.binary_only:
375n/a srpm = os.path.join(rpm_dir['SRPMS'], source_rpm)
376n/a assert(os.path.exists(srpm))
377n/a self.move_file(srpm, self.dist_dir)
378n/a filename = os.path.join(self.dist_dir, source_rpm)
379n/a self.distribution.dist_files.append(
380n/a ('bdist_rpm', pyversion, filename))
381n/a
382n/a if not self.source_only:
383n/a for rpm in binary_rpms:
384n/a rpm = os.path.join(rpm_dir['RPMS'], rpm)
385n/a if os.path.exists(rpm):
386n/a self.move_file(rpm, self.dist_dir)
387n/a filename = os.path.join(self.dist_dir,
388n/a os.path.basename(rpm))
389n/a self.distribution.dist_files.append(
390n/a ('bdist_rpm', pyversion, filename))
391n/a
392n/a def _dist_path(self, path):
393n/a return os.path.join(self.dist_dir, os.path.basename(path))
394n/a
395n/a def _make_spec_file(self):
396n/a """Generate the text of an RPM spec file and return it as a
397n/a list of strings (one per line).
398n/a """
399n/a # definitions and headers
400n/a spec_file = [
401n/a '%define name ' + self.distribution.get_name(),
402n/a '%define version ' + self.distribution.get_version().replace('-','_'),
403n/a '%define unmangled_version ' + self.distribution.get_version(),
404n/a '%define release ' + self.release.replace('-','_'),
405n/a '',
406n/a 'Summary: ' + self.distribution.get_description(),
407n/a ]
408n/a
409n/a # Workaround for #14443 which affects some RPM based systems such as
410n/a # RHEL6 (and probably derivatives)
411n/a vendor_hook = subprocess.getoutput('rpm --eval %{__os_install_post}')
412n/a # Generate a potential replacement value for __os_install_post (whilst
413n/a # normalizing the whitespace to simplify the test for whether the
414n/a # invocation of brp-python-bytecompile passes in __python):
415n/a vendor_hook = '\n'.join([' %s \\' % line.strip()
416n/a for line in vendor_hook.splitlines()])
417n/a problem = "brp-python-bytecompile \\\n"
418n/a fixed = "brp-python-bytecompile %{__python} \\\n"
419n/a fixed_hook = vendor_hook.replace(problem, fixed)
420n/a if fixed_hook != vendor_hook:
421n/a spec_file.append('# Workaround for http://bugs.python.org/issue14443')
422n/a spec_file.append('%define __os_install_post ' + fixed_hook + '\n')
423n/a
424n/a # put locale summaries into spec file
425n/a # XXX not supported for now (hard to put a dictionary
426n/a # in a config file -- arg!)
427n/a #for locale in self.summaries.keys():
428n/a # spec_file.append('Summary(%s): %s' % (locale,
429n/a # self.summaries[locale]))
430n/a
431n/a spec_file.extend([
432n/a 'Name: %{name}',
433n/a 'Version: %{version}',
434n/a 'Release: %{release}',])
435n/a
436n/a # XXX yuck! this filename is available from the "sdist" command,
437n/a # but only after it has run: and we create the spec file before
438n/a # running "sdist", in case of --spec-only.
439n/a if self.use_bzip2:
440n/a spec_file.append('Source0: %{name}-%{unmangled_version}.tar.bz2')
441n/a else:
442n/a spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz')
443n/a
444n/a spec_file.extend([
445n/a 'License: ' + self.distribution.get_license(),
446n/a 'Group: ' + self.group,
447n/a 'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot',
448n/a 'Prefix: %{_prefix}', ])
449n/a
450n/a if not self.force_arch:
451n/a # noarch if no extension modules
452n/a if not self.distribution.has_ext_modules():
453n/a spec_file.append('BuildArch: noarch')
454n/a else:
455n/a spec_file.append( 'BuildArch: %s' % self.force_arch )
456n/a
457n/a for field in ('Vendor',
458n/a 'Packager',
459n/a 'Provides',
460n/a 'Requires',
461n/a 'Conflicts',
462n/a 'Obsoletes',
463n/a ):
464n/a val = getattr(self, field.lower())
465n/a if isinstance(val, list):
466n/a spec_file.append('%s: %s' % (field, ' '.join(val)))
467n/a elif val is not None:
468n/a spec_file.append('%s: %s' % (field, val))
469n/a
470n/a
471n/a if self.distribution.get_url() != 'UNKNOWN':
472n/a spec_file.append('Url: ' + self.distribution.get_url())
473n/a
474n/a if self.distribution_name:
475n/a spec_file.append('Distribution: ' + self.distribution_name)
476n/a
477n/a if self.build_requires:
478n/a spec_file.append('BuildRequires: ' +
479n/a ' '.join(self.build_requires))
480n/a
481n/a if self.icon:
482n/a spec_file.append('Icon: ' + os.path.basename(self.icon))
483n/a
484n/a if self.no_autoreq:
485n/a spec_file.append('AutoReq: 0')
486n/a
487n/a spec_file.extend([
488n/a '',
489n/a '%description',
490n/a self.distribution.get_long_description()
491n/a ])
492n/a
493n/a # put locale descriptions into spec file
494n/a # XXX again, suppressed because config file syntax doesn't
495n/a # easily support this ;-(
496n/a #for locale in self.descriptions.keys():
497n/a # spec_file.extend([
498n/a # '',
499n/a # '%description -l ' + locale,
500n/a # self.descriptions[locale],
501n/a # ])
502n/a
503n/a # rpm scripts
504n/a # figure out default build script
505n/a def_setup_call = "%s %s" % (self.python,os.path.basename(sys.argv[0]))
506n/a def_build = "%s build" % def_setup_call
507n/a if self.use_rpm_opt_flags:
508n/a def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build
509n/a
510n/a # insert contents of files
511n/a
512n/a # XXX this is kind of misleading: user-supplied options are files
513n/a # that we open and interpolate into the spec file, but the defaults
514n/a # are just text that we drop in as-is. Hmmm.
515n/a
516n/a install_cmd = ('%s install -O1 --root=$RPM_BUILD_ROOT '
517n/a '--record=INSTALLED_FILES') % def_setup_call
518n/a
519n/a script_options = [
520n/a ('prep', 'prep_script', "%setup -n %{name}-%{unmangled_version}"),
521n/a ('build', 'build_script', def_build),
522n/a ('install', 'install_script', install_cmd),
523n/a ('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"),
524n/a ('verifyscript', 'verify_script', None),
525n/a ('pre', 'pre_install', None),
526n/a ('post', 'post_install', None),
527n/a ('preun', 'pre_uninstall', None),
528n/a ('postun', 'post_uninstall', None),
529n/a ]
530n/a
531n/a for (rpm_opt, attr, default) in script_options:
532n/a # Insert contents of file referred to, if no file is referred to
533n/a # use 'default' as contents of script
534n/a val = getattr(self, attr)
535n/a if val or default:
536n/a spec_file.extend([
537n/a '',
538n/a '%' + rpm_opt,])
539n/a if val:
540n/a spec_file.extend(open(val, 'r').read().split('\n'))
541n/a else:
542n/a spec_file.append(default)
543n/a
544n/a
545n/a # files section
546n/a spec_file.extend([
547n/a '',
548n/a '%files -f INSTALLED_FILES',
549n/a '%defattr(-,root,root)',
550n/a ])
551n/a
552n/a if self.doc_files:
553n/a spec_file.append('%doc ' + ' '.join(self.doc_files))
554n/a
555n/a if self.changelog:
556n/a spec_file.extend([
557n/a '',
558n/a '%changelog',])
559n/a spec_file.extend(self.changelog)
560n/a
561n/a return spec_file
562n/a
563n/a def _format_changelog(self, changelog):
564n/a """Format the changelog correctly and convert it to a list of strings
565n/a """
566n/a if not changelog:
567n/a return changelog
568n/a new_changelog = []
569n/a for line in changelog.strip().split('\n'):
570n/a line = line.strip()
571n/a if line[0] == '*':
572n/a new_changelog.extend(['', line])
573n/a elif line[0] == '-':
574n/a new_changelog.append(line)
575n/a else:
576n/a new_changelog.append(' ' + line)
577n/a
578n/a # strip trailing newline inserted by first changelog entry
579n/a if not new_changelog[0]:
580n/a del new_changelog[0]
581n/a
582n/a return new_changelog