Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
bdist_rpm.py600 linesDownload Raw Back to command
1"""distutils.command.bdist_rpm2 3Implements the Distutils 'bdist_rpm' command (create RPM source and binary4distributions)."""5 6import os7import subprocess8import sys9from distutils._log import log10 11from ..core import Command12from ..debug import DEBUG13from ..errors import (14    DistutilsExecError,15    DistutilsFileError,16    DistutilsOptionError,17    DistutilsPlatformError,18)19from ..file_util import write_file20from ..sysconfig import get_python_version21 22 23class bdist_rpm(Command):24    description = "create an RPM distribution"25 26    user_options = [27        ('bdist-base=', None, "base directory for creating built distributions"),28        (29            'rpm-base=',30            None,31            "base directory for creating RPMs (defaults to \"rpm\" under "32            "--bdist-base; must be specified for RPM 2)",33        ),34        (35            'dist-dir=',36            'd',37            "directory to put final RPM files in (and .spec files if --spec-only)",38        ),39        (40            'python=',41            None,42            "path to Python interpreter to hard-code in the .spec file "43            "(default: \"python\")",44        ),45        (46            'fix-python',47            None,48            "hard-code the exact path to the current Python interpreter in "49            "the .spec file",50        ),51        ('spec-only', None, "only regenerate spec file"),52        ('source-only', None, "only generate source RPM"),53        ('binary-only', None, "only generate binary RPM"),54        ('use-bzip2', None, "use bzip2 instead of gzip to create source distribution"),55        # More meta-data: too RPM-specific to put in the setup script,56        # but needs to go in the .spec file -- so we make these options57        # to "bdist_rpm".  The idea is that packagers would put this58        # info in setup.cfg, although they are of course free to59        # supply it on the command line.60        (61            'distribution-name=',62            None,63            "name of the (Linux) distribution to which this "64            "RPM applies (*not* the name of the module distribution!)",65        ),66        ('group=', None, "package classification [default: \"Development/Libraries\"]"),67        ('release=', None, "RPM release number"),68        ('serial=', None, "RPM serial number"),69        (70            'vendor=',71            None,72            "RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") "73            "[default: maintainer or author from setup script]",74        ),75        (76            'packager=',77            None,78            "RPM packager (eg. \"Jane Doe <jane@example.net>\") [default: vendor]",79        ),80        ('doc-files=', None, "list of documentation files (space or comma-separated)"),81        ('changelog=', None, "RPM changelog"),82        ('icon=', None, "name of icon file"),83        ('provides=', None, "capabilities provided by this package"),84        ('requires=', None, "capabilities required by this package"),85        ('conflicts=', None, "capabilities which conflict with this package"),86        ('build-requires=', None, "capabilities required to build this package"),87        ('obsoletes=', None, "capabilities made obsolete by this package"),88        ('no-autoreq', None, "do not automatically calculate dependencies"),89        # Actions to take when building RPM90        ('keep-temp', 'k', "don't clean up RPM build directory"),91        ('no-keep-temp', None, "clean up RPM build directory [default]"),92        (93            'use-rpm-opt-flags',94            None,95            "compile with RPM_OPT_FLAGS when building from source RPM",96        ),97        ('no-rpm-opt-flags', None, "do not pass any RPM CFLAGS to compiler"),98        ('rpm3-mode', None, "RPM 3 compatibility mode (default)"),99        ('rpm2-mode', None, "RPM 2 compatibility mode"),100        # Add the hooks necessary for specifying custom scripts101        ('prep-script=', None, "Specify a script for the PREP phase of RPM building"),102        ('build-script=', None, "Specify a script for the BUILD phase of RPM building"),103        (104            'pre-install=',105            None,106            "Specify a script for the pre-INSTALL phase of RPM building",107        ),108        (109            'install-script=',110            None,111            "Specify a script for the INSTALL phase of RPM building",112        ),113        (114            'post-install=',115            None,116            "Specify a script for the post-INSTALL phase of RPM building",117        ),118        (119            'pre-uninstall=',120            None,121            "Specify a script for the pre-UNINSTALL phase of RPM building",122        ),123        (124            'post-uninstall=',125            None,126            "Specify a script for the post-UNINSTALL phase of RPM building",127        ),128        ('clean-script=', None, "Specify a script for the CLEAN phase of RPM building"),129        (130            'verify-script=',131            None,132            "Specify a script for the VERIFY phase of the RPM build",133        ),134        # Allow a packager to explicitly force an architecture135        ('force-arch=', None, "Force an architecture onto the RPM build process"),136        ('quiet', 'q', "Run the INSTALL phase of RPM building in quiet mode"),137    ]138 139    boolean_options = [140        'keep-temp',141        'use-rpm-opt-flags',142        'rpm3-mode',143        'no-autoreq',144        'quiet',145    ]146 147    negative_opt = {148        'no-keep-temp': 'keep-temp',149        'no-rpm-opt-flags': 'use-rpm-opt-flags',150        'rpm2-mode': 'rpm3-mode',151    }152 153    def initialize_options(self):154        self.bdist_base = None155        self.rpm_base = None156        self.dist_dir = None157        self.python = None158        self.fix_python = None159        self.spec_only = None160        self.binary_only = None161        self.source_only = None162        self.use_bzip2 = None163 164        self.distribution_name = None165        self.group = None166        self.release = None167        self.serial = None168        self.vendor = None169        self.packager = None170        self.doc_files = None171        self.changelog = None172        self.icon = None173 174        self.prep_script = None175        self.build_script = None176        self.install_script = None177        self.clean_script = None178        self.verify_script = None179        self.pre_install = None180        self.post_install = None181        self.pre_uninstall = None182        self.post_uninstall = None183        self.prep = None184        self.provides = None185        self.requires = None186        self.conflicts = None187        self.build_requires = None188        self.obsoletes = None189 190        self.keep_temp = 0191        self.use_rpm_opt_flags = 1192        self.rpm3_mode = 1193        self.no_autoreq = 0194 195        self.force_arch = None196        self.quiet = 0197 198    def finalize_options(self):199        self.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))200        if self.rpm_base is None:201            if not self.rpm3_mode:202                raise DistutilsOptionError("you must specify --rpm-base in RPM 2 mode")203            self.rpm_base = os.path.join(self.bdist_base, "rpm")204 205        if self.python is None:206            if self.fix_python:207                self.python = sys.executable208            else:209                self.python = "python3"210        elif self.fix_python:211            raise DistutilsOptionError(212                "--python and --fix-python are mutually exclusive options"213            )214 215        if os.name != 'posix':216            raise DistutilsPlatformError(217                "don't know how to create RPM distributions on platform %s" % os.name218            )219        if self.binary_only and self.source_only:220            raise DistutilsOptionError(221                "cannot supply both '--source-only' and '--binary-only'"222            )223 224        # don't pass CFLAGS to pure python distributions225        if not self.distribution.has_ext_modules():226            self.use_rpm_opt_flags = 0227 228        self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))229        self.finalize_package_data()230 231    def finalize_package_data(self):232        self.ensure_string('group', "Development/Libraries")233        self.ensure_string(234            'vendor',235            f"{self.distribution.get_contact()} <{self.distribution.get_contact_email()}>",236        )237        self.ensure_string('packager')238        self.ensure_string_list('doc_files')239        if isinstance(self.doc_files, list):240            for readme in ('README', 'README.txt'):241                if os.path.exists(readme) and readme not in self.doc_files:242                    self.doc_files.append(readme)243 244        self.ensure_string('release', "1")245        self.ensure_string('serial')  # should it be an int?246 247        self.ensure_string('distribution_name')248 249        self.ensure_string('changelog')250        # Format changelog correctly251        self.changelog = self._format_changelog(self.changelog)252 253        self.ensure_filename('icon')254 255        self.ensure_filename('prep_script')256        self.ensure_filename('build_script')257        self.ensure_filename('install_script')258        self.ensure_filename('clean_script')259        self.ensure_filename('verify_script')260        self.ensure_filename('pre_install')261        self.ensure_filename('post_install')262        self.ensure_filename('pre_uninstall')263        self.ensure_filename('post_uninstall')264 265        # XXX don't forget we punted on summaries and descriptions -- they266        # should be handled here eventually!267 268        # Now *this* is some meta-data that belongs in the setup script...269        self.ensure_string_list('provides')270        self.ensure_string_list('requires')271        self.ensure_string_list('conflicts')272        self.ensure_string_list('build_requires')273        self.ensure_string_list('obsoletes')274 275        self.ensure_string('force_arch')276 277    def run(self):  # noqa: C901278        if DEBUG:279            print("before _get_package_data():")280            print("vendor =", self.vendor)281            print("packager =", self.packager)282            print("doc_files =", self.doc_files)283            print("changelog =", self.changelog)284 285        # make directories286        if self.spec_only:287            spec_dir = self.dist_dir288            self.mkpath(spec_dir)289        else:290            rpm_dir = {}291            for d in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'):292                rpm_dir[d] = os.path.join(self.rpm_base, d)293                self.mkpath(rpm_dir[d])294            spec_dir = rpm_dir['SPECS']295 296        # Spec file goes into 'dist_dir' if '--spec-only specified',297        # build/rpm.<plat> otherwise.298        spec_path = os.path.join(spec_dir, "%s.spec" % self.distribution.get_name())299        self.execute(300            write_file, (spec_path, self._make_spec_file()), "writing '%s'" % spec_path301        )302 303        if self.spec_only:  # stop if requested304            return305 306        # Make a source distribution and copy to SOURCES directory with307        # optional icon.308        saved_dist_files = self.distribution.dist_files[:]309        sdist = self.reinitialize_command('sdist')310        if self.use_bzip2:311            sdist.formats = ['bztar']312        else:313            sdist.formats = ['gztar']314        self.run_command('sdist')315        self.distribution.dist_files = saved_dist_files316 317        source = sdist.get_archive_files()[0]318        source_dir = rpm_dir['SOURCES']319        self.copy_file(source, source_dir)320 321        if self.icon:322            if os.path.exists(self.icon):323                self.copy_file(self.icon, source_dir)324            else:325                raise DistutilsFileError("icon file '%s' does not exist" % self.icon)326 327        # build package328        log.info("building RPMs")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        rpm_cmd.extend(['--define', '__python %s' % self.python])338        if self.rpm3_mode:339            rpm_cmd.extend(['--define', '_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 spec348        # file349        # Note that some of these may not be really built (if the file350        # 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 = rf"rpm -q --qf '{src_rpm} {non_src_rpm}\n' --specfile '{spec_path}'"355 356        out = os.popen(q_cmd)357        try:358            binary_rpms = []359            source_rpm = None360            while True:361                line = out.readline()362                if not line:363                    break364                ell = line.strip().split()365                assert len(ell) == 2366                binary_rpms.append(ell[1])367                # The source rpm is named after the first entry in the spec file368                if source_rpm is None:369                    source_rpm = ell[0]370 371            status = out.close()372            if status:373                raise DistutilsExecError("Failed to execute: %s" % repr(q_cmd))374 375        finally:376            out.close()377 378        self.spawn(rpm_cmd)379 380        if not self.dry_run:381            if self.distribution.has_ext_modules():382                pyversion = get_python_version()383            else:384                pyversion = 'any'385 386            if not self.binary_only:387                srpm = os.path.join(rpm_dir['SRPMS'], source_rpm)388                assert os.path.exists(srpm)389                self.move_file(srpm, self.dist_dir)390                filename = os.path.join(self.dist_dir, source_rpm)391                self.distribution.dist_files.append(('bdist_rpm', pyversion, filename))392 393            if not self.source_only:394                for rpm in binary_rpms:395                    rpm = os.path.join(rpm_dir['RPMS'], rpm)396                    if os.path.exists(rpm):397                        self.move_file(rpm, self.dist_dir)398                        filename = os.path.join(self.dist_dir, os.path.basename(rpm))399                        self.distribution.dist_files.append((400                            'bdist_rpm',401                            pyversion,402                            filename,403                        ))404 405    def _dist_path(self, path):406        return os.path.join(self.dist_dir, os.path.basename(path))407 408    def _make_spec_file(self):  # noqa: C901409        """Generate the text of an RPM spec file and return it as a410        list of strings (one per line).411        """412        # definitions and headers413        spec_file = [414            '%define name ' + self.distribution.get_name(),415            '%define version ' + self.distribution.get_version().replace('-', '_'),416            '%define unmangled_version ' + self.distribution.get_version(),417            '%define release ' + self.release.replace('-', '_'),418            '',419            'Summary: ' + (self.distribution.get_description() or "UNKNOWN"),420        ]421 422        # Workaround for #14443 which affects some RPM based systems such as423        # RHEL6 (and probably derivatives)424        vendor_hook = subprocess.getoutput('rpm --eval %{__os_install_post}')425        # Generate a potential replacement value for __os_install_post (whilst426        # normalizing the whitespace to simplify the test for whether the427        # invocation of brp-python-bytecompile passes in __python):428        vendor_hook = '\n'.join([429            '  %s \\' % line.strip() for line in vendor_hook.splitlines()430        ])431        problem = "brp-python-bytecompile \\\n"432        fixed = "brp-python-bytecompile %{__python} \\\n"433        fixed_hook = vendor_hook.replace(problem, fixed)434        if fixed_hook != vendor_hook:435            spec_file.append('# Workaround for https://bugs.python.org/issue14443')436            spec_file.append('%define __os_install_post ' + fixed_hook + '\n')437 438        # put locale summaries into spec file439        # XXX not supported for now (hard to put a dictionary440        # in a config file -- arg!)441        # for locale in self.summaries.keys():442        #    spec_file.append('Summary(%s): %s' % (locale,443        #                                          self.summaries[locale]))444 445        spec_file.extend([446            'Name: %{name}',447            'Version: %{version}',448            'Release: %{release}',449        ])450 451        # XXX yuck! this filename is available from the "sdist" command,452        # but only after it has run: and we create the spec file before453        # running "sdist", in case of --spec-only.454        if self.use_bzip2:455            spec_file.append('Source0: %{name}-%{unmangled_version}.tar.bz2')456        else:457            spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz')458 459        spec_file.extend([460            'License: ' + (self.distribution.get_license() or "UNKNOWN"),461            'Group: ' + self.group,462            'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot',463            'Prefix: %{_prefix}',464        ])465 466        if not self.force_arch:467            # noarch if no extension modules468            if not self.distribution.has_ext_modules():469                spec_file.append('BuildArch: noarch')470        else:471            spec_file.append('BuildArch: %s' % self.force_arch)472 473        for field in (474            'Vendor',475            'Packager',476            'Provides',477            'Requires',478            'Conflicts',479            'Obsoletes',480        ):481            val = getattr(self, field.lower())482            if isinstance(val, list):483                spec_file.append('{}: {}'.format(field, ' '.join(val)))484            elif val is not None:485                spec_file.append(f'{field}: {val}')486 487        if self.distribution.get_url():488            spec_file.append('Url: ' + self.distribution.get_url())489 490        if self.distribution_name:491            spec_file.append('Distribution: ' + self.distribution_name)492 493        if self.build_requires:494            spec_file.append('BuildRequires: ' + ' '.join(self.build_requires))495 496        if self.icon:497            spec_file.append('Icon: ' + os.path.basename(self.icon))498 499        if self.no_autoreq:500            spec_file.append('AutoReq: 0')501 502        spec_file.extend([503            '',504            '%description',505            self.distribution.get_long_description() or "",506        ])507 508        # put locale descriptions into spec file509        # XXX again, suppressed because config file syntax doesn't510        # easily support this ;-(511        # for locale in self.descriptions.keys():512        #    spec_file.extend([513        #        '',514        #        '%description -l ' + locale,515        #        self.descriptions[locale],516        #        ])517 518        # rpm scripts519        # figure out default build script520        def_setup_call = f"{self.python} {os.path.basename(sys.argv[0])}"521        def_build = "%s build" % def_setup_call522        if self.use_rpm_opt_flags:523            def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build524 525        # insert contents of files526 527        # XXX this is kind of misleading: user-supplied options are files528        # that we open and interpolate into the spec file, but the defaults529        # are just text that we drop in as-is.  Hmmm.530 531        install_cmd = (532            '%s install -O1 --root=$RPM_BUILD_ROOT ' '--record=INSTALLED_FILES'533        ) % def_setup_call534 535        script_options = [536            ('prep', 'prep_script', "%setup -n %{name}-%{unmangled_version}"),537            ('build', 'build_script', def_build),538            ('install', 'install_script', install_cmd),539            ('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"),540            ('verifyscript', 'verify_script', None),541            ('pre', 'pre_install', None),542            ('post', 'post_install', None),543            ('preun', 'pre_uninstall', None),544            ('postun', 'post_uninstall', None),545        ]546 547        for rpm_opt, attr, default in script_options:548            # Insert contents of file referred to, if no file is referred to549            # use 'default' as contents of script550            val = getattr(self, attr)551            if val or default:552                spec_file.extend([553                    '',554                    '%' + rpm_opt,555                ])556                if val:557                    with open(val) as f:558                        spec_file.extend(f.read().split('\n'))559                else:560                    spec_file.append(default)561 562        # files section563        spec_file.extend([564            '',565            '%files -f INSTALLED_FILES',566            '%defattr(-,root,root)',567        ])568 569        if self.doc_files:570            spec_file.append('%doc ' + ' '.join(self.doc_files))571 572        if self.changelog:573            spec_file.extend([574                '',575                '%changelog',576            ])577            spec_file.extend(self.changelog)578 579        return spec_file580 581    def _format_changelog(self, changelog):582        """Format the changelog correctly and convert it to a list of strings"""583        if not changelog:584            return changelog585        new_changelog = []586        for line in changelog.strip().split('\n'):587            line = line.strip()588            if line[0] == '*':589                new_changelog.extend(['', line])590            elif line[0] == '-':591                new_changelog.append(line)592            else:593                new_changelog.append('  ' + line)594 595        # strip trailing newline inserted by first changelog entry596        if not new_changelog[0]:597            del new_changelog[0]598 599        return new_changelog600 
codekingpro/portable-devtools · Team Ai