-
Notifications
You must be signed in to change notification settings - Fork 236
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
ba2a10b
commit 6986b10
Showing
9 changed files
with
329 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# -*- coding: utf-8 -*- | ||
# 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]> | ||
"""A mock plugin to pre-process spec files using rpmautospec.""" | ||
|
||
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): | ||
"""Register the rpmautospec plugin with mock.""" | ||
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( | ||
self, | ||
host_chroot_spec: Union[Path, str], | ||
host_chroot_sources: Optional[Union[Path, str]], | ||
) -> None: | ||
"""Attempt to process a spec file with rpmautospec.""" | ||
# 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), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,5 @@ distro | |
jinja2 | ||
pyroute2 | ||
requests | ||
rpmautospec-core | ||
templated-dictionary |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
"""Compatibility code for old Python versions:""" | ||
|
||
try: | ||
from contextlib import nullcontext # noqa: F401 | ||
except ImportError: | ||
# Python < 3.7 | ||
from contextlib import contextmanager | ||
|
||
@contextmanager | ||
def nullcontext(enter_result=None): | ||
"""Trimmed down version of contextlib.nullcontext()""" | ||
yield enter_result |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
"""Test the rpmautospec plugin.""" | ||
|
||
from copy import deepcopy | ||
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): # noqa: C0103 | ||
"""Test the function which registers the plugin.""" | ||
plugins = object() | ||
conf = object() | ||
buildroot = object() | ||
|
||
rpmautospec.init(plugins, conf, buildroot) | ||
|
||
RpmautospecPlugin.assert_called_once_with(plugins, conf, buildroot) | ||
|
||
|
||
class TestRpmautospecPlugin: | ||
"""Test the RpmautospecPlugin class.""" | ||
|
||
DEFAULT_OPTS = { | ||
"requires": ["rpmautospec"], | ||
"cmd_base": ["rpmautospec", "process-distgit"], | ||
} | ||
|
||
def create_plugin(self, plugins=UNSET, conf=UNSET, buildroot=UNSET): | ||
"""Create a plugin object and prepare it for testing.""" | ||
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): | ||
"""Test the constructor.""" | ||
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): # noqa: R0912, R0915 | ||
"""Test the attempt_process_distgit() method.""" | ||
# 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" | ||
spec_dir.mkdir() | ||
sources_dir = tmp_path / "SOURCES" | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters