/
opt
/
cloudlinux
/
venv
/
lib
/
python3.11
/
site-packages
/
setuptools
/
_distutils
/
command
/
Upload Filee
HOME
"""distutils.command.bdist_rpm Implements the Distutils 'bdist_rpm' command (create RPM source and binary distributions).""" import os import subprocess import sys from distutils._log import log from ..core import Command from ..debug import DEBUG from ..errors import ( DistutilsExecError, DistutilsFileError, DistutilsOptionError, DistutilsPlatformError, ) from ..file_util import write_file from ..sysconfig import get_python_version class bdist_rpm(Command): description = "create an RPM distribution" user_options = [ ('bdist-base=', None, "base directory for creating built distributions"), ( 'rpm-base=', None, "base directory for creating RPMs (defaults to \"rpm\" under " "--bdist-base; must be specified for RPM 2)", ), ( 'dist-dir=', 'd', "directory to put final RPM files in (and .spec files if --spec-only)", ), ( 'python=', None, "path to Python interpreter to hard-code in the .spec file " "[default: \"python\"]", ), ( 'fix-python', None, "hard-code the exact path to the current Python interpreter in " "the .spec file", ), ('spec-only', None, "only regenerate spec file"), ('source-only', None, "only generate source RPM"), ('binary-only', None, "only generate binary RPM"), ('use-bzip2', None, "use bzip2 instead of gzip to create source distribution"), # More meta-data: too RPM-specific to put in the setup script, # but needs to go in the .spec file -- so we make these options # to "bdist_rpm". The idea is that packagers would put this # info in setup.cfg, although they are of course free to # supply it on the command line. ( 'distribution-name=', None, "name of the (Linux) distribution to which this " "RPM applies (*not* the name of the module distribution!)", ), ('group=', None, "package classification [default: \"Development/Libraries\"]"), ('release=', None, "RPM release number"), ('serial=', None, "RPM serial number"), ( 'vendor=', None, "RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") " "[default: maintainer or author from setup script]", ), ( 'packager=', None, "RPM packager (eg. \"Jane Doe <jane@example.net>\") [default: vendor]", ), ('doc-files=', None, "list of documentation files (space or comma-separated)"), ('changelog=', None, "RPM changelog"), ('icon=', None, "name of icon file"), ('provides=', None, "capabilities provided by this package"), ('requires=', None, "capabilities required by this package"), ('conflicts=', None, "capabilities which conflict with this package"), ('build-requires=', None, "capabilities required to build this package"), ('obsoletes=', None, "capabilities made obsolete by this package"), ('no-autoreq', None, "do not automatically calculate dependencies"), # Actions to take when building RPM ('keep-temp', 'k', "don't clean up RPM build directory"), ('no-keep-temp', None, "clean up RPM build directory [default]"), ( 'use-rpm-opt-flags', None, "compile with RPM_OPT_FLAGS when building from source RPM", ), ('no-rpm-opt-flags', None, "do not pass any RPM CFLAGS to compiler"), ('rpm3-mode', None, "RPM 3 compatibility mode (default)"), ('rpm2-mode', None, "RPM 2 compatibility mode"), # Add the hooks necessary for specifying custom scripts ('prep-script=', None, "Specify a script for the PREP phase of RPM building"), ('build-script=', None, "Specify a script for the BUILD phase of RPM building"), ( 'pre-install=', None, "Specify a script for the pre-INSTALL phase of RPM building", ), ( 'install-script=', None, "Specify a script for the INSTALL phase of RPM building", ), ( 'post-install=', None, "Specify a script for the post-INSTALL phase of RPM building", ), ( 'pre-uninstall=', None, "Specify a script for the pre-UNINSTALL phase of RPM building", ), ( 'post-uninstall=', None, "Specify a script for the post-UNINSTALL phase of RPM building", ), ('clean-script=', None, "Specify a script for the CLEAN phase of RPM building"), ( 'verify-script=', None, "Specify a script for the VERIFY phase of the RPM build", ), # Allow a packager to explicitly force an architecture ('force-arch=', None, "Force an architecture onto the RPM build process"), ('quiet', 'q', "Run the INSTALL phase of RPM building in quiet mode"), ] boolean_options = [ 'keep-temp', 'use-rpm-opt-flags', 'rpm3-mode', 'no-autoreq', 'quiet', ] negative_opt = { 'no-keep-temp': 'keep-temp', 'no-rpm-opt-flags': 'use-rpm-opt-flags', 'rpm2-mode': 'rpm3-mode', } def initialize_options(self): self.bdist_base = None self.rpm_base = None self.dist_dir = None self.python = None self.fix_python = None self.spec_only = None self.binary_only = None self.source_only = None self.use_bzip2 = None self.distribution_name = None self.group = None self.release = None self.serial = None self.vendor = None self.packager = None self.doc_files = None self.changelog = None self.icon = None self.prep_script = None self.build_script = None self.install_script = None self.clean_script = None self.verify_script = None self.pre_install = None self.post_install = None self.pre_uninstall = None self.post_uninstall = None self.prep = None self.provides = None self.requires = None self.conflicts = None self.build_requires = None self.obsoletes = None self.keep_temp = False self.use_rpm_opt_flags = True self.rpm3_mode = True self.no_autoreq = False self.force_arch = None self.quiet = False def finalize_options(self): self.set_undefined_options('bdist', ('bdist_base', 'bdist_base')) if self.rpm_base is None: if not self.rpm3_mode: raise DistutilsOptionError("you must specify --rpm-base in RPM 2 mode") self.rpm_base = os.path.join(self.bdist_base, "rpm") if self.python is None: if self.fix_python: self.python = sys.executable else: self.python = "python3" elif self.fix_python: raise DistutilsOptionError( "--python and --fix-python are mutually exclusive options" ) if os.name != 'posix': raise DistutilsPlatformError( f"don't know how to create RPM distributions on platform {os.name}" ) if self.binary_only and self.source_only: raise DistutilsOptionError( "cannot supply both '--source-only' and '--binary-only'" ) # don't pass CFLAGS to pure python distributions if not self.distribution.has_ext_modules(): self.use_rpm_opt_flags = False self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) self.finalize_package_data() def finalize_package_data(self): self.ensure_string('group', "Development/Libraries") self.ensure_string( 'vendor', f"{self.distribution.get_contact()} <{self.distribution.get_contact_email()}>", ) self.ensure_string('packager') self.ensure_string_list('doc_files') if isinstance(self.doc_files, list): for readme in ('README', 'README.txt'): if os.path.exists(readme) and readme not in self.doc_files: self.doc_files.append(readme) self.ensure_string('release', "1") self.ensure_string('serial') # should it be an int? self.ensure_string('distribution_name') self.ensure_string('changelog') # Format changelog correctly self.changelog = self._format_changelog(self.changelog) self.ensure_filename('icon') self.ensure_filename('prep_script') self.ensure_filename('build_script') self.ensure_filename('install_script') self.ensure_filename('clean_script') self.ensure_filename('verify_script') self.ensure_filename('pre_install') self.ensure_filename('post_install') self.ensure_filename('pre_uninstall') self.ensure_filename('post_uninstall') # XXX don't forget we punted on summaries and descriptions -- they # should be handled here eventually! # Now *this* is some meta-data that belongs in the setup script... self.ensure_string_list('provides') self.ensure_string_list('requires') self.ensure_string_list('conflicts') self.ensure_string_list('build_requires') self.ensure_string_list('obsoletes') self.ensure_string('force_arch') def run(self): # noqa: C901 if DEBUG: print("before _get_package_data():") print("vendor =", self.vendor) print("packager =", self.packager) print("doc_files =", self.doc_files) print("changelog =", self.changelog) # make directories if self.spec_only: spec_dir = self.dist_dir self.mkpath(spec_dir) else: rpm_dir = {} for d in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'): rpm_dir[d] = os.path.join(self.rpm_base, d) self.mkpath(rpm_dir[d]) spec_dir = rpm_dir['SPECS'] # Spec file goes into 'dist_dir' if '--spec-only specified', # build/rpm.<plat> otherwise. spec_path = os.path.join(spec_dir, f"{self.distribution.get_name()}.spec") self.execute( write_file, (spec_path, self._make_spec_file()), f"writing '{spec_path}'" ) if self.spec_only: # stop if requested return # Make a source distribution and copy to SOURCES directory with # optional icon. saved_dist_files = self.distribution.dist_files[:] sdist = self.reinitialize_command('sdist') if self.use_bzip2: sdist.formats = ['bztar'] else: sdist.formats = ['gztar'] self.run_command('sdist') self.distribution.dist_files = saved_dist_files source = sdist.get_archive_files()[0] source_dir = rpm_dir['SOURCES'] self.copy_file(source, source_dir) if self.icon: if os.path.exists(self.icon): self.copy_file(self.icon, source_dir) else: raise DistutilsFileError(f"icon file '{self.icon}' does not exist") # build package log.info("building RPMs") rpm_cmd = ['rpmbuild'] if self.source_only: # what kind of RPMs? rpm_cmd.append('-bs') elif self.binary_only: rpm_cmd.append('-bb') else: rpm_cmd.append('-ba') rpm_cmd.extend(['--define', f'__python {self.python}']) if self.rpm3_mode: rpm_cmd.extend(['--define', f'_topdir {os.path.abspath(self.rpm_base)}']) if not self.keep_temp: rpm_cmd.append('--clean') if self.quiet: rpm_cmd.append('--quiet') rpm_cmd.append(spec_path) # Determine the binary rpm names that should be built out of this spec # file # Note that some of these may not be really built (if the file # list is empty) nvr_string = "%{name}-%{version}-%{release}" src_rpm = nvr_string + ".src.rpm" non_src_rpm = "%{arch}/" + nvr_string + ".%{arch}.rpm" q_cmd = rf"rpm -q --qf '{src_rpm} {non_src_rpm}\n' --specfile '{spec_path}'" out = os.popen(q_cmd) try: binary_rpms = [] source_rpm = None while True: line = out.readline() if not line: break ell = line.strip().split() assert len(ell) == 2 binary_rpms.append(ell[1]) # The source rpm is named after the first entry in the spec file if source_rpm is None: source_rpm = ell[0] status = out.close() if status: raise DistutilsExecError(f"Failed to execute: {q_cmd!r}") finally: out.close() self.spawn(rpm_cmd) if not self.dry_run: if self.distribution.has_ext_modules(): pyversion = get_python_version() else: pyversion = 'any' if not self.binary_only: srpm = os.path.join(rpm_dir['SRPMS'], source_rpm) assert os.path.exists(srpm) self.move_file(srpm, self.dist_dir) filename = os.path.join(self.dist_dir, source_rpm) self.distribution.dist_files.append(('bdist_rpm', pyversion, filename)) if not self.source_only: for rpm in binary_rpms: rpm = os.path.join(rpm_dir['RPMS'], rpm) if os.path.exists(rpm): self.move_file(rpm, self.dist_dir) filename = os.path.join(self.dist_dir, os.path.basename(rpm)) self.distribution.dist_files.append(( 'bdist_rpm', pyversion, filename, )) def _dist_path(self, path): return os.path.join(self.dist_dir, os.path.basename(path)) def _make_spec_file(self): # noqa: C901 """Generate the text of an RPM spec file and return it as a list of strings (one per line). """ # definitions and headers spec_file = [ '%define name ' + self.distribution.get_name(), '%define version ' + self.distribution.get_version().replace('-', '_'), '%define unmangled_version ' + self.distribution.get_version(), '%define release ' + self.release.replace('-', '_'), '', 'Summary: ' + (self.distribution.get_description() or "UNKNOWN"), ] # Workaround for #14443 which affects some RPM based systems such as # RHEL6 (and probably derivatives) vendor_hook = subprocess.getoutput('rpm --eval %{__os_install_post}') # Generate a potential replacement value for __os_install_post (whilst # normalizing the whitespace to simplify the test for whether the # invocation of brp-python-bytecompile passes in __python): vendor_hook = '\n'.join([ f' {line.strip()} \\' for line in vendor_hook.splitlines() ]) problem = "brp-python-bytecompile \\\n" fixed = "brp-python-bytecompile %{__python} \\\n" fixed_hook = vendor_hook.replace(problem, fixed) if fixed_hook != vendor_hook: spec_file.append('# Workaround for https://bugs.python.org/issue14443') spec_file.append('%define __os_install_post ' + fixed_hook + '\n') # put locale summaries into spec file # XXX not supported for now (hard to put a dictionary # in a config file -- arg!) # for locale in self.summaries.keys(): # spec_file.append('Summary(%s): %s' % (locale, # self.summaries[locale])) spec_file.extend([ 'Name: %{name}', 'Version: %{version}', 'Release: %{release}', ]) # XXX yuck! this filename is available from the "sdist" command, # but only after it has run: and we create the spec file before # running "sdist", in case of --spec-only. if self.use_bzip2: spec_file.append('Source0: %{name}-%{unmangled_version}.tar.bz2') else: spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz') spec_file.extend([ 'License: ' + (self.distribution.get_license() or "UNKNOWN"), 'Group: ' + self.group, 'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot', 'Prefix: %{_prefix}', ]) if not self.force_arch: # noarch if no extension modules if not self.distribution.has_ext_modules(): spec_file.append('BuildArch: noarch') else: spec_file.append(f'BuildArch: {self.force_arch}') for field in ( 'Vendor', 'Packager', 'Provides', 'Requires', 'Conflicts', 'Obsoletes', ): val = getattr(self, field.lower()) if isinstance(val, list): spec_file.append('{}: {}'.format(field, ' '.join(val))) elif val is not None: spec_file.append(f'{field}: {val}') if self.distribution.get_url(): spec_file.append('Url: ' + self.distribution.get_url()) if self.distribution_name: spec_file.append('Distribution: ' + self.distribution_name) if self.build_requires: spec_file.append('BuildRequires: ' + ' '.join(self.build_requires)) if self.icon: spec_file.append('Icon: ' + os.path.basename(self.icon)) if self.no_autoreq: spec_file.append('AutoReq: 0') spec_file.extend([ '', '%description', self.distribution.get_long_description() or "", ]) # put locale descriptions into spec file # XXX again, suppressed because config file syntax doesn't # easily support this ;-( # for locale in self.descriptions.keys(): # spec_file.extend([ # '', # '%description -l ' + locale, # self.descriptions[locale], # ]) # rpm scripts # figure out default build script def_setup_call = f"{self.python} {os.path.basename(sys.argv[0])}" def_build = f"{def_setup_call} build" if self.use_rpm_opt_flags: def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build # insert contents of files # XXX this is kind of misleading: user-supplied options are files # that we open and interpolate into the spec file, but the defaults # are just text that we drop in as-is. Hmmm. install_cmd = f'{def_setup_call} install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES' script_options = [ ('prep', 'prep_script', "%setup -n %{name}-%{unmangled_version}"), ('build', 'build_script', def_build), ('install', 'install_script', install_cmd), ('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"), ('verifyscript', 'verify_script', None), ('pre', 'pre_install', None), ('post', 'post_install', None), ('preun', 'pre_uninstall', None), ('postun', 'post_uninstall', None), ] for rpm_opt, attr, default in script_options: # Insert contents of file referred to, if no file is referred to # use 'default' as contents of script val = getattr(self, attr) if val or default: spec_file.extend([ '', '%' + rpm_opt, ]) if val: with open(val) as f: spec_file.extend(f.read().split('\n')) else: spec_file.append(default) # files section spec_file.extend([ '', '%files -f INSTALLED_FILES', '%defattr(-,root,root)', ]) if self.doc_files: spec_file.append('%doc ' + ' '.join(self.doc_files)) if self.changelog: spec_file.extend([ '', '%changelog', ]) spec_file.extend(self.changelog) return spec_file def _format_changelog(self, changelog): """Format the changelog correctly and convert it to a list of strings""" if not changelog: return changelog new_changelog = [] for line in changelog.strip().split('\n'): line = line.strip() if line[0] == '*': new_changelog.extend(['', line]) elif line[0] == '-': new_changelog.append(line) else: new_changelog.append(' ' + line) # strip trailing newline inserted by first changelog entry if not new_changelog[0]: del new_changelog[0] return new_changelog