-
Notifications
You must be signed in to change notification settings - Fork 236
Commit
This plugin preprocesses spec files using %autorelease and/or %autochangelog in the mock chroot prior to building SRPMs. The `rpmautospec` package will be installed into the chroot if necessary. Co-authored-by: Nils Philippsen <[email protected]> Signed-off-by: Stephen Gallagher <[email protected]> Signed-off-by: Nils Philippsen <[email protected]>
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# -*- coding: utf-8 -*- | ||
Check warning Code scanning / vcs-diff-lint Missing module docstring Warning
Missing module docstring
|
||
# vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=python:textwidth=0: | ||
# License: GPL2 or later see COPYING | ||
# Copyright (C) 2023 Stephen Gallagher <[email protected]> | ||
# Copyright (C) 2023 Nils Philippsen <[email protected]> | ||
from pathlib import Path | ||
from typing import Optional, Union | ||
|
||
from rpmautospec_core import specfile_uses_rpmautospec | ||
|
||
from mockbuild.exception import ConfigError | ||
from mockbuild.trace_decorator import getLog, traceLog | ||
|
||
requires_api_version = "1.1" | ||
|
||
|
||
@traceLog() | ||
def init(plugins, conf, buildroot): | ||
Check warning Code scanning / vcs-diff-lint init: Missing function or method docstring Warning
init: Missing function or method docstring
|
||
RpmautospecPlugin(plugins, conf, buildroot) | ||
|
||
|
||
class RpmautospecPlugin: | ||
"""Fill in release and changelog from git history using rpmautospec""" | ||
|
||
@traceLog() | ||
def __init__(self, plugins, conf, buildroot): | ||
self.buildroot = buildroot | ||
self.config = buildroot.config | ||
self.opts = conf | ||
self.log = getLog() | ||
|
||
if "cmd_base" not in self.opts: | ||
raise ConfigError("The 'rpmautospec_opts.cmd_base' is unset") | ||
|
||
plugins.add_hook("pre_srpm_build", self.attempt_process_distgit) | ||
self.log.info("rpmautospec: initialized") | ||
|
||
@traceLog() | ||
def attempt_process_distgit( | ||
Check warning Code scanning / vcs-diff-lint RpmautospecPlugin.attempt_process_distgit: Missing function or method docstring Warning
RpmautospecPlugin.attempt_process_distgit: Missing function or method docstring
|
||
self, | ||
host_chroot_spec: Union[Path, str], | ||
host_chroot_sources: Optional[Union[Path, str]], | ||
) -> None: | ||
# Set up variables and check prerequisites. | ||
if not host_chroot_sources: | ||
self.log.debug("Sources not specified, skipping rpmautospec preprocessing.") | ||
return | ||
|
||
host_chroot_spec = Path(host_chroot_spec) | ||
host_chroot_sources = Path(host_chroot_sources) | ||
if not host_chroot_sources.is_dir(): | ||
self.log.debug( | ||
"Sources not a directory, skipping rpmautospec preprocessing." | ||
) | ||
return | ||
|
||
distgit_git_dir = host_chroot_sources / ".git" | ||
if not distgit_git_dir.is_dir(): | ||
self.log.debug( | ||
"Sources is not a git repository, skipping rpmautospec preprocessing." | ||
) | ||
return | ||
|
||
host_chroot_sources_spec = host_chroot_sources / host_chroot_spec.name | ||
if not host_chroot_sources_spec.is_file(): | ||
self.log.debug( | ||
"Sources doesn’t contain spec file, skipping rpmautospec preprocessing." | ||
) | ||
return | ||
|
||
with host_chroot_spec.open("rb") as spec, host_chroot_sources_spec.open( | ||
"rb" | ||
) as sources_spec: | ||
if spec.read() != sources_spec.read(): | ||
self.log.warning( | ||
"Spec file inside and outside sources are different, skipping rpmautospec" | ||
" preprocessing." | ||
) | ||
return | ||
|
||
if not specfile_uses_rpmautospec(host_chroot_sources_spec): | ||
self.log.debug( | ||
"Spec file doesn’t use rpmautospec, skipping rpmautospec preprocessing." | ||
) | ||
return | ||
|
||
# Install the `rpmautospec` command line tool into the build root. | ||
if self.opts.get("requires", None): | ||
self.buildroot.pkg_manager.install_as_root(*self.opts["requires"], check=True) | ||
|
||
# Get paths inside the chroot by chopping off the leading paths | ||
chroot_dir = Path(self.buildroot.make_chroot_path()) | ||
chroot_spec = Path("/") / host_chroot_spec.relative_to(chroot_dir) | ||
chroot_sources = Path("/") / host_chroot_sources.relative_to(chroot_dir) | ||
chroot_sources_spec = Path("/") / host_chroot_sources_spec.relative_to(chroot_dir) | ||
|
||
# Call subprocess to perform the specfile rewrite | ||
command = list(self.opts["cmd_base"]) | ||
command += [chroot_sources_spec] # <input-spec> | ||
command += [chroot_spec] # <output-spec> | ||
|
||
self.buildroot.doChroot( | ||
command, | ||
shell=False, | ||
cwd=chroot_sources, | ||
logger=self.buildroot.build_log, | ||
uid=self.buildroot.chrootuid, | ||
gid=self.buildroot.chrootgid, | ||
user=self.buildroot.chrootuser, | ||
unshare_net=not self.config.get("rpmbuild_networking", False), | ||
nspawn_args=self.config.get("nspawn_args", []), | ||
printOutput=self.config.get("print_main_output", True), | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
try: | ||
Check warning Code scanning / vcs-diff-lint Missing module docstring Warning test
Missing module docstring
|
||
from contextlib import nullcontext | ||
Check warning Code scanning / vcs-diff-lint Unused nullcontext imported from contextlib Warning test
Unused nullcontext imported from contextlib
|
||
except ImportError: | ||
# Python < 3.7 | ||
from contextlib import contextmanager | ||
|
||
@contextmanager | ||
def nullcontext(enter_result=None): | ||
"""Trimmed down version of contextlib.nullcontext()""" | ||
yield enter_result |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
from copy import deepcopy | ||
Check warning Code scanning / vcs-diff-lint Missing module docstring Warning test
Missing module docstring
|
||
from pathlib import Path | ||
from unittest import mock | ||
|
||
import pytest | ||
|
||
from mockbuild.exception import ConfigError | ||
from mockbuild.plugins import rpmautospec | ||
|
||
from ..compat import nullcontext | ||
|
||
UNSET = object() | ||
|
||
|
||
@mock.patch("mockbuild.plugins.rpmautospec.RpmautospecPlugin") | ||
def test_init(RpmautospecPlugin): | ||
Check warning Code scanning / vcs-diff-lint test_init: Missing function or method docstring Warning test
test_init: Missing function or method docstring
Check warning Code scanning / vcs-diff-lint test_init: Argument name "RpmautospecPlugin" doesn't conform to '[a-z_][a-zA-Z0-9_]{,30}$' pattern Warning test
test_init: Argument name "RpmautospecPlugin" doesn't conform to '[a-z_][a-zA-Z0-9_]{,30}$' pattern
|
||
plugins = object() | ||
conf = object() | ||
buildroot = object() | ||
|
||
rpmautospec.init(plugins, conf, buildroot) | ||
|
||
RpmautospecPlugin.assert_called_once_with(plugins, conf, buildroot) | ||
|
||
|
||
class TestRpmautospecPlugin: | ||
Check warning Code scanning / vcs-diff-lint TestRpmautospecPlugin: Missing class docstring Warning test
TestRpmautospecPlugin: Missing class docstring
|
||
DEFAULT_OPTS = { | ||
"requires": ["rpmautospec"], | ||
"cmd_base": ["rpmautospec", "process-distgit"], | ||
} | ||
|
||
def create_plugin(self, plugins=UNSET, conf=UNSET, buildroot=UNSET): | ||
Check warning Code scanning / vcs-diff-lint TestRpmautospecPlugin.create_plugin: Missing function or method docstring Warning test
TestRpmautospecPlugin.create_plugin: Missing function or method docstring
|
||
if plugins is UNSET: | ||
plugins = mock.Mock() | ||
if conf is UNSET: | ||
conf = deepcopy(self.DEFAULT_OPTS) | ||
if buildroot is UNSET: | ||
buildroot = mock.Mock() | ||
|
||
return rpmautospec.RpmautospecPlugin(plugins, conf, buildroot) | ||
|
||
@pytest.mark.parametrize( | ||
"with_cmd_base", (True, False), ids=("with-cmd_base", "without-cmd_base") | ||
) | ||
@mock.patch("mockbuild.plugins.rpmautospec.getLog") | ||
def test___init__(self, getLog, with_cmd_base): | ||
Check warning Code scanning / vcs-diff-lint TestRpmautospecPlugin.test___init__: Missing function or method docstring Warning test
TestRpmautospecPlugin.test___init__: Missing function or method docstring
|
||
plugins = mock.Mock() | ||
|
||
conf = deepcopy(self.DEFAULT_OPTS) | ||
if with_cmd_base: | ||
expectation = nullcontext() | ||
else: | ||
expectation = pytest.raises(ConfigError) | ||
del conf["cmd_base"] | ||
|
||
buildroot = mock.Mock() | ||
|
||
with expectation as exc_info: | ||
logger = getLog.return_value | ||
plugin = self.create_plugin(plugins=plugins, conf=conf, buildroot=buildroot) | ||
|
||
if with_cmd_base: | ||
assert plugin.buildroot is buildroot | ||
assert plugin.config is buildroot.config | ||
assert plugin.opts is conf | ||
assert plugin.log is logger | ||
plugins.add_hook.assert_called_once_with("pre_srpm_build", plugin.attempt_process_distgit) | ||
logger.info.assert_called_once_with("rpmautospec: initialized") | ||
else: | ||
assert "rpmautospec_opts.cmd_base" in str(exc_info.value) | ||
|
||
@pytest.mark.parametrize( | ||
"testcase", | ||
( | ||
"happy-path", | ||
"happy-path-no-requires", | ||
"without-sources", | ||
"sources-not-dir", | ||
"sources-not-repo", | ||
"sources-no-specfile", | ||
"spec-files-different", | ||
"specfile-no-rpmautospec", | ||
), | ||
) | ||
def test_attempt_process_distgit(self, testcase, tmp_path): | ||
Check warning Code scanning / vcs-diff-lint TestRpmautospecPlugin.test_attempt_process_distgit: Missing function or method docstring Warning test
TestRpmautospecPlugin.test_attempt_process_distgit: Missing function or method docstring
Check warning Code scanning / vcs-diff-lint TestRpmautospecPlugin.test_attempt_process_distgit: Too many branches (21/12) Warning test
TestRpmautospecPlugin.test_attempt_process_distgit: Too many branches (21/12)
Check warning Code scanning / vcs-diff-lint TestRpmautospecPlugin.test_attempt_process_distgit: Too many statements (62/50) Warning test
TestRpmautospecPlugin.test_attempt_process_distgit: Too many statements (62/50)
|
||
# Set the stage | ||
plugin = self.create_plugin() | ||
plugin.log = log = mock.Mock() | ||
plugin.buildroot.make_chroot_path.return_value = str(tmp_path) | ||
if "no-requires" in testcase: | ||
plugin.opts["requires"] = [] | ||
|
||
SPEC_DIR = tmp_path / "SPECS" | ||
Check warning Code scanning / vcs-diff-lint TestRpmautospecPlugin.test_attempt_process_distgit: Variable name "SPEC_DIR" doesn't conform to '[a-z_][a-zA-Z0-9_]{,30}$' pattern Warning test
TestRpmautospecPlugin.test_attempt_process_distgit: Variable name "SPEC_DIR" doesn't conform to '[a-z_][a-zA-Z0-9_]{,30}$' pattern
|
||
SPEC_DIR.mkdir() | ||
SOURCES_DIR = tmp_path / "SOURCES" | ||
Check warning Code scanning / vcs-diff-lint TestRpmautospecPlugin.test_attempt_process_distgit: Variable name "SOURCES_DIR" doesn't conform to '[a-z_][a-zA-Z0-9_]{,30}$' pattern Warning test
TestRpmautospecPlugin.test_attempt_process_distgit: Variable name "SOURCES_DIR" doesn't conform to '[a-z_][a-zA-Z0-9_]{,30}$' pattern
|
||
SOURCES_DIR.mkdir() | ||
|
||
host_chroot_spec = SPEC_DIR / "pkg.spec" | ||
host_chroot_sources = SOURCES_DIR / "pkg" | ||
host_chroot_sources.mkdir() | ||
host_chroot_sources_git = host_chroot_sources / ".git" | ||
host_chroot_sources_spec = host_chroot_sources / "pkg.spec" | ||
|
||
if "no-rpmautospec" not in testcase: | ||
spec_contents = ( | ||
"Release: %autorelease", | ||
"%changelog", | ||
"%autochangelog", | ||
) | ||
else: | ||
spec_contents = ( | ||
"Release: 1", | ||
"%changelog", | ||
) | ||
|
||
with host_chroot_spec.open("w") as fp: | ||
for line in spec_contents: | ||
print(line, file=fp) | ||
|
||
if "without-sources" in testcase: | ||
host_chroot_sources = None | ||
elif "sources-not-dir" not in testcase: | ||
if "sources-no-specfile" not in testcase: | ||
with host_chroot_sources_spec.open("w") as fp: | ||
if "spec-files-different" in testcase: | ||
print("# BOO", file=fp) | ||
for line in spec_contents: | ||
print(line, file=fp) | ||
if "sources-not-repo" not in testcase: | ||
host_chroot_sources_git.mkdir() | ||
else: | ||
host_chroot_sources = tmp_path / "pkg.tar" | ||
host_chroot_sources.touch() | ||
|
||
plugin.attempt_process_distgit(host_chroot_spec, host_chroot_sources) | ||
|
||
if "happy-path" in testcase: | ||
chroot_spec = Path("/") / host_chroot_spec.relative_to(tmp_path) | ||
chroot_sources = Path("/") / host_chroot_sources.relative_to(tmp_path) | ||
chroot_sources_spec = Path("/") / host_chroot_sources_spec.relative_to(tmp_path) | ||
|
||
expected_command = plugin.opts["cmd_base"] + [chroot_sources_spec, chroot_spec] | ||
|
||
plugin.buildroot.doChroot.assert_called_once_with( | ||
expected_command, | ||
shell=False, | ||
cwd=chroot_sources, | ||
logger=plugin.buildroot.build_log, | ||
uid=plugin.buildroot.chrootuid, | ||
gid=plugin.buildroot.chrootgid, | ||
user=plugin.buildroot.chrootuser, | ||
unshare_net=not plugin.config.get("rpmbuild_networking", False), | ||
nspawn_args=plugin.config.get("nspawn_args", []), | ||
printOutput=plugin.config.get("print_main_output", True), | ||
) | ||
else: | ||
plugin.buildroot.doChroot.assert_not_called() | ||
if "spec-files-different" in testcase: | ||
log_method = log.warning | ||
else: | ||
log_method = log.debug | ||
log_method.assert_called_once() | ||
log_string = log_method.call_args[0][0] | ||
assert "skipping rpmautospec preprocessing" in log_string | ||
|
||
if "without-sources" in testcase: | ||
assert "Sources not specified" in log_string | ||
elif "sources-not-dir" in testcase: | ||
assert "Sources not a directory" in log_string | ||
elif "sources-not-repo" in testcase: | ||
assert "Sources is not a git repository" in log_string | ||
elif "sources-no-specfile" in testcase: | ||
assert "Sources doesn’t contain spec file" in log_string | ||
elif "spec-files-different" in testcase: | ||
assert "Spec file inside and outside sources are different" in log_string | ||
elif "specfile-no-rpmautospec" in testcase: | ||
assert "Spec file doesn’t use rpmautospec" in log_string |