Blame src/centpkg/__init__.py

Brian Stinson 6e343c
# pylint: disable=line-too-long,abstract-class-not-used
Brian Stinson 6e343c
85cce4
"""
85a850
    Top level function library for centpkg
85cce4
"""
Brian Stinson 6e343c
85a850
# Author(s):
85a850
#            Jesse Keating <jkeating@redhat.com>
85a850
#            Pat Riehecky <riehecky@fnal.gov>
85a850
#            Brian Stinson <bstinson@ksu.edu>
85a850
#
85a850
# This program is free software; you can redistribute it and/or modify it
85a850
# under the terms of the GNU General Public License as published by the
85a850
# Free Software Foundation; either version 2 of the License, or (at your
85a850
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
85a850
# the full text of the license.
85a850
85a850
Miro Hrončok 12342a
import os
Brian Stinson ca61eb
import re
e7fd56
import warnings
Brian Stinson 2fb8a5
b201d4
import git
Miro Hrončok 12342a
import rpm
ebeaf1
from pyrpkg import Commands, rpkgError
b201d4
from pyrpkg.utils import cached_property
b201d4
96fedb
# doc/centpkg_man_page.py uses the 'cli' import
96fedb
from . import cli  # noqa
e1698b
from .lookaside import StreamLookasideCache, SIGLookasideCache, CLLookasideCache
b201d4
b201d4
85cce4
_DEFAULT_VERSION = "9"
Brian Stinson 2fb8a5
05c687
7ae64c
class DistGitDirectory(object):
7ae64c
86b0d3
    signame = None
b201d4
    centosversion = _DEFAULT_VERSION
86b0d3
    projectname = None
86b0d3
    releasename = None
639034
    distrobranch = False
e1698b
    sigbranch = False
b201d4
    repo = None
85cce4
    git_origin_substr = "git@gitlab.com/redhat/centos-stream"
86b0d3
b201d4
    def __init__(self, branchtext, repo_path=None):
b201d4
        if repo_path:
4588ec
            # self.repo = git.cmd.Git(repo_path)
4588ec
            self.repo = git.repo.Repo(repo_path)
85cce4
        rhelbranchre = r"rhel-(?P<major>\d+)\.(?P<minor>\d+)(?:\.(?P<appstream>\d+))?"
85cce4
        sigtobranchre = r"c(?P<centosversion>\d+[s]?)-sig-(?P<signame>\w+)-?(?P<projectname>\w+)?-?(?P<releasename>\w+)?"
85cce4
        distrobranchre = r"c(?P<centosversion>\d+)-?(?P<projectname>\w+)?"
85cce4
        javabranchre = r"openjdk-portable-centos-(?P<centosversion>\d+)"
85cce4
        oldbranchre = r"(?P<signame>\w+)(?P<centosversion>\d)"
bb525a
        rhelmatch = re.search(rhelbranchre, branchtext)
639034
        sigmatch = re.match(sigtobranchre, branchtext)
639034
        distromatch = re.match(distrobranchre, branchtext)
0fb26c
        javamatch = re.match(javabranchre, branchtext)
e7fd56
        oldbranchmatch = re.match(oldbranchre, branchtext)
bb525a
        if rhelmatch:
bb525a
            gd = rhelmatch.groupdict()
bb525a
            self.distrobranch = True
85cce4
            self.signame = "centos"
85cce4
            self.centosversion = gd["major"]
bb525a
        elif sigmatch:
639034
            gd = sigmatch.groupdict()
e1698b
            self.sigbranch = True
85cce4
            self.signame = gd["signame"]
85cce4
            self.centosversion = gd["centosversion"]
7ae64c
35f2c1
            # Users have the option to specify (or not specify) common in their
35f2c1
            # git repos. Ww need to handle these cases because common is not a
35f2c1
            # project nor is it a release.
85cce4
            if gd["projectname"] != "common":
85cce4
                self.projectname = gd["projectname"]
85cce4
            if gd["releasename"] != "common":
85cce4
                self.releasename = gd["releasename"]
639034
        elif distromatch:
639034
            gd = distromatch.groupdict()
639034
            self.distrobranch = True
85cce4
            self.signame = "centos"
85cce4
            self.centosversion = gd["centosversion"]
639034
85cce4
            if gd["projectname"] != "common":
85cce4
                self.projectname = gd["projectname"]
0fb26c
        elif javamatch:
0fb26c
            gd = javamatch.groupdict()
0fb26c
            self.distrobranch = True
85cce4
            self.signame = "centos"
85cce4
            self.centosversion = gd["centosversion"]
e7fd56
        elif oldbranchmatch:
85cce4
            warnings.warn(
85cce4
                "This branch is deprecated and will be removed soon", DeprecationWarning
85cce4
            )
639034
        else:
b201d4
            if not self.is_fork():
85cce4
                warnings.warn(
85cce4
                    "Unable to determine if this is a fork or not. Proceeding, but you should double check."
85cce4
                )
b201d4
            else:
b201d4
                self.distrobranch = True
85cce4
                self.signame = "centos"
85cce4
                self.projectname = self.get_origin().split("_")[-1].replace(".git", "")
b201d4
85cce4
                warnings.warn(
85cce4
                    'Remote "origin" was detected as a fork, ignoring branch name checking'
85cce4
                )
b201d4
b201d4
    def get_origin(self):
b201d4
        if self.repo is None:
85cce4
            return ""
85cce4
        if "origin" not in self.repo.remotes:
85cce4
            return ""
85cce4
        urls = [u for u in self.repo.remotes["origin"].urls]
4588ec
        if len(urls) == 0:
85cce4
            return ""
4588ec
        return urls[0]
b201d4
b201d4
    def is_fork(self):
b201d4
        """
b201d4
        Check if origin remote repository is using a fork url.
b201d4
b201d4
        Returns
b201d4
        bool
b201d4
            A boolean flag indicating if origin remote url is using
b201d4
            a forked repository url.
b201d4
        """
b201d4
        # git+ssh://git@gitlab.com/redhat/centos-stream/rpms/binutils.git
b201d4
        if self.repo is None:
b201d4
            return False
b201d4
        return self.git_origin_substr not in self.get_origin()
b201d4
9af8c4
    @property
9af8c4
    def target(self):
9af8c4
        projectorcommon = self.projectname
9af8c4
        releaseorcommon = self.releasename
9af8c4
639034
        if self.distrobranch:
85cce4
            if self.centosversion not in ("6", "7"):
85cce4
                return "c{}s-candidate".format(self.centosversion)
Brian Stinson 3edf28
            else:
85cce4
                return "-".join(
85cce4
                    filter(None, ["c" + self.centosversion, projectorcommon])
85cce4
                )
Brian Stinson 3edf28
9af8c4
        if not releaseorcommon:
85cce4
            if not projectorcommon or projectorcommon == "common":
85cce4
                projectorcommon = "common"
9af8c4
            else:
85cce4
                releaseorcommon = "common"
9af8c4
85cce4
        return "-".join(
85cce4
            filter(
85cce4
                None,
85cce4
                [self.signame + self.centosversion, projectorcommon, releaseorcommon],
85cce4
            )
85cce4
        ) + "-el{0}".format(self.centosversion)
9af8c4
a394b1
9af8c4
class Commands(Commands):
85cce4
    """
85cce4
    For the pyrpkg commands with centpkg behavior
85cce4
    """
85cce4
a394b1
    def __init__(self, *args, **kwargs):
85cce4
        """
85cce4
        Init the object and some configuration details.
85cce4
        """
a394b1
        super(Commands, self).__init__(*args, **kwargs)
James Antill ed1405
        # For MD5 we want to use the old format of source files, the BSD format
James Antill ed1405
        # should only be used when configured for SHA512
85cce4
        self.source_entry_type = "bsd" if self.lookasidehash != "md5" else "old"
Miro Hrončok d624da
        self.branchre = r"c\d{1,}(s)?(tream)?|master"
a394b1
a394b1
    @property
a394b1
    def distgitdir(self):
b201d4
        return DistGitDirectory(self.branch_merge, repo_path=self.path)
Brian Stinson 2fb8a5
a394b1
    @cached_property
a394b1
    def lookasidecache(self):
e1698b
        if self.layout.sources_file_template == "sources":
85cce4
            return StreamLookasideCache(
85cce4
                self.lookasidehash,
85cce4
                self.lookaside,
85cce4
                self.lookaside_cgi,
85cce4
            )
Brian Stinson 3edf28
        else:
e1698b
            if self.distgitdir.sigbranch:
85cce4
                return SIGLookasideCache(
85cce4
                    self.lookasidehash,
85cce4
                    self.lookaside,
85cce4
                    self.lookaside_cgi,
85cce4
                    self.repo_name,
85cce4
                    self.branch_merge,
85cce4
                )
e1698b
            else:
85cce4
                return CLLookasideCache(
85cce4
                    self.lookasidehash,
85cce4
                    self.lookaside,
85cce4
                    self.lookaside_cgi,
85cce4
                    self.repo_name,
85cce4
                    self.branch_merge,
85cce4
                )
639034
Miro Hrončok 12342a
    def _define_patchn_compatiblity_macros(self):
Miro Hrončok 12342a
        """
Miro Hrončok 12342a
        RPM 4.19 deprecated the %patchN macro. RPM 4.20 removed it completely.
Miro Hrončok 12342a
        The macro works on c8s, c9s, c10s, but does not work on Fedora 41+.
Miro Hrončok 12342a
        We can no longer even parse RPM spec files with the %patchN macros.
Miro Hrončok 12342a
        When we build for old streams, we define the %patchN macros manually as %patch -P N.
Miro Hrončok 12342a
        Since N can be any number including zero-prefixed numbers,
Miro Hrončok 12342a
        we regex-search the spec file for %patchN uses and define only the macros found.
Miro Hrončok 12342a
        """
Miro Hrončok 12342a
        # Only do this on RPM 4.19.90+ (4.19.9x were pre-releases of 4.20)
Miro Hrončok 12342a
        if tuple(int(i) for i in rpm.__version_info__) < (4, 19, 90):
Miro Hrončok 12342a
            return
Miro Hrončok 12342a
        # Only do this when building for CentOS Stream version with RPM < 4.20
Miro Hrončok 12342a
        try:
Miro Hrončok 12342a
            if int(self._distval.split("_")[0]) > 10:
Miro Hrončok 12342a
                return
Miro Hrončok 12342a
        except ValueError as e:
Miro Hrončok 12342a
            self.log.debug(
Miro Hrončok 12342a
                "Cannot parse major dist version as int: %s",
Miro Hrončok 12342a
                self._distval.split("_")[0],
Miro Hrončok 12342a
                exc_info=e,
Miro Hrončok 12342a
            )
Miro Hrončok 12342a
            return
Miro Hrončok 12342a
        defined_patchn = False
Miro Hrončok 12342a
        try:
Miro Hrončok 12342a
            specfile_path = os.path.join(self.layout.specdir, self.spec)
Miro Hrončok 12342a
            with open(specfile_path, "rb") as specfile:
Miro Hrončok 12342a
                # Find all uses of %patchN in the spec files
Miro Hrončok 12342a
                # Using a benevolent regex: commented out macros, etc. match as well
Miro Hrončok 12342a
                for patch in re.findall(rb"%{?patch(\d+)\b", specfile.read()):
Miro Hrončok 12342a
                    # We operate on bytes becasue we don't know the spec encoding
Miro Hrončok 12342a
                    # but the matched part only includes ASCII digits
Miro Hrončok 12342a
                    patch = patch.decode("ascii")
Miro Hrončok 12342a
                    self._rpmdefines.extend(
Miro Hrončok 12342a
                        [
Miro Hrončok 12342a
                            "--define",
Miro Hrončok 12342a
                            # defines parametric macro %patchN which passes all arguments to %patch -P N
Miro Hrončok 12342a
                            "patch%s(-) %%patch -P %s %%{?**}" % (patch, patch),
Miro Hrončok 12342a
                        ]
Miro Hrončok 12342a
                    )
Miro Hrončok 12342a
                    defined_patchn = True
Miro Hrončok 12342a
        except OSError as e:
Miro Hrončok 12342a
            self.log.debug("Cannot read spec.", exc_info=e)
Miro Hrončok 12342a
        if defined_patchn:
d8635c
            self.log.warning(
Miro Hrončok 12342a
                "centpkg defined %patchN compatibility shims to parse the spec file. "
Miro Hrončok 12342a
                "%patchN is obsolete, use %patch -P N instead."
Miro Hrončok 12342a
            )
Miro Hrončok 12342a
Brian Stinson ca61eb
    # redefined loaders
Brian Stinson ca61eb
    def load_rpmdefines(self):
85cce4
        """
85cce4
        Populate rpmdefines based on branch data
85cce4
        """
a1a2e2
a1a2e2
        if not self.distgitdir.centosversion:
85cce4
            raise rpkgError(
85cce4
                "Could not get the OS version from the branch:{0}".format(
85cce4
                    self.branch_merge
85cce4
                )
85cce4
            )
a1a2e2
a394b1
        self._distvar = self.distgitdir.centosversion
85cce4
        self._distval = self._distvar.replace(".", "_")
85cce4
441ee7
        self._distunset = 'fedora'
85cce4
        self._disttag = "el%s" % self._distval
85cce4
        self._rpmdefines = [
85cce4
            "--define",
85cce4
            "_sourcedir %s" % self.layout.sourcedir,
85cce4
            "--define",
85cce4
            "_specdir %s" % self.layout.specdir,
85cce4
            "--define",
85cce4
            "_builddir %s" % self.layout.builddir,
85cce4
            "--define",
85cce4
            "_srcrpmdir %s" % self.layout.srcrpmdir,
85cce4
            "--define",
85cce4
            "_rpmdir %s" % self.layout.rpmdir,
85cce4
            "--define",
85cce4
            "dist .%s" % self._disttag,
85cce4
            # int and float this to remove the decimal
85cce4
            "--define",
85cce4
            "%s 1" % self._disttag,
85cce4
            # This is so the rhel macro is set for spec files
85cce4
            "--define",
85cce4
            "rhel %s" % self._distval.split("_")[0],
7f8395
            # This is so the centos macro is set for spec files
7f8395
            "--define",
7f8395
            "centos %s" % self._distval.split("_")[0],
441ee7
            # This is so the fedora macro is unset for spec files
441ee7
            "--eval",
441ee7
            "%%undefine %s" % self._distunset,
85cce4
        ]
Miro Hrončok 12342a
        self._define_patchn_compatiblity_macros()
a394b1
        self.log.debug("RPMDefines: %s" % self._rpmdefines)
Brian Stinson ca61eb
James Antill dda40d
    def construct_build_url(self, *args, **kwargs):
James Antill dda40d
        """Override build URL for CentOS/Fedora Koji build
James Antill dda40d
James Antill dda40d
        In CentOS/Fedora Koji, anonymous URL should have prefix "git+https://"
James Antill dda40d
        """
James Antill dda40d
        url = super(Commands, self).construct_build_url(*args, **kwargs)
85cce4
        return "git+{0}".format(url)
James Antill dda40d
Brian Stinson 6ba768
    def load_target(self):
85cce4
        """This sets the target attribute (used for mock and koji)"""
Brian Stinson 3f76b3
8b3983
        self._target = self.distgitdir.target