Skip to content

Commit

Permalink
Add rpmautospec plugin
Browse files Browse the repository at this point in the history
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
sgallagher and nphilipp committed Dec 7, 2023
1 parent 9d6fe43 commit ed377be
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 2 deletions.
8 changes: 8 additions & 0 deletions mock/docs/site-defaults.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,14 @@
# config_opts['plugin_conf']['rpkg_preprocessor_opts']['requires'] = ['preproc-rpmspec']
# config_opts['plugin_conf']['rpkg_preprocessor_opts']['cmd'] = '/usr/bin/preproc-rpmspec %(source_spec)s --output %(target_spec)s'

# The rpmautospec plugin is disabled by default and distributed in the separate
# subpackage mock-rpmautospec.
# config_opts['plugin_conf']['rpmautospec_enable'] = True
# config_opts['plugin_conf']['rpmautospec_opts'] = {
# 'requires': ['rpmautospec'],
# 'cmd_base': ['/usr/bin/rpmautospec', 'process-distgit'],
# }

#############################################################################
#
# environment for chroot
Expand Down
14 changes: 14 additions & 0 deletions mock/mock.spec
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ BuildRequires: python%{python3_pkgversion}-devel
%if %{with lint}
BuildRequires: python%{python3_pkgversion}-pylint
%endif
BuildRequires: python%{python3_pkgversion}-rpmautospec-core

%if 0%{?fedora} >= 38
# DNF5 stack
Expand Down Expand Up @@ -132,6 +133,15 @@ Requires: lvm2
Mock plugin that enables using LVM as a backend and support creating snapshots
of the buildroot.

%package rpmautospec
Summary: Rpmautospec plugin for mock
Requires: %{name} = %{version}-%{release}
# This lets mock determine if a spec file needs to be processed with rpmautospec.
Requires: python%{python3_pkgversion}-rpmautospec-core

%description rpmautospec
Mock plugin that preprocesses spec files using rpmautospec.

%package filesystem
Summary: Mock filesystem layout
Requires(pre): shadow-utils
Expand Down Expand Up @@ -266,6 +276,10 @@ pylint-3 py/mockbuild/ py/*.py py/mockbuild/plugins/* || :
%{python_sitelib}/mockbuild/plugins/lvm_root.*
%{python3_sitelib}/mockbuild/plugins/__pycache__/lvm_root.*.py*

%files rpmautospec
%{python_sitelib}/mockbuild/plugins/rpmautospec.*
%{python3_sitelib}/mockbuild/plugins/__pycache__/rpmautospec.*.py*

%files filesystem
%license COPYING
%dir %{_sysconfdir}/mock
Expand Down
11 changes: 10 additions & 1 deletion mock/py/mockbuild/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
PLUGIN_LIST = ['tmpfs', 'root_cache', 'yum_cache', 'mount', 'bind_mount',
'ccache', 'selinux', 'package_state', 'chroot_scan',
'lvm_root', 'compress_logs', 'sign', 'pm_request',
'hw_info', 'procenv', 'showrc', 'rpkg_preprocessor']
'hw_info', 'procenv', 'showrc', 'rpkg_preprocessor',
'rpmautospec']

def nspawn_supported():
"""Detect some situations where the systemd-nspawn chroot code won't work"""
Expand Down Expand Up @@ -202,6 +203,14 @@ def setup_default_config_opts():
'requires': ['preproc-rpmspec'],
'cmd': '/usr/bin/preproc-rpmspec %(source_spec)s --output %(target_spec)s',
},
'rpmautospec_enable': False,
'rpmautospec_opts': {
'requires': ['rpmautospec'],
'cmd_base': [
'/usr/bin/rpmautospec',
'process-distgit',
]
},
}

config_opts['environment'] = {
Expand Down
Empty file.
128 changes: 128 additions & 0 deletions mock/py/mockbuild/plugins/rpmautospec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# -*- 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, PkgError
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):
try:
self.buildroot.pkg_manager.install_as_root(*self.opts["requires"], check=True)
except Exception as exc:
raise PkgError(
"Can’t install rpmautospec dependencies into chroot: "
+ ", ".join(self.opts["requires"])
) from exc

# 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>

# Run the rpmautospec tool in the chroot sandbox. This minimizes
# external dependencies in the host, e.g. the Koji build system. As a
# bonus, spec files will be processed in the environment they will be
# built for, reducing the impact of the host system on the outcome,
# leading to more deterministic results and better repeatable builds.
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),
)
1 change: 1 addition & 0 deletions mock/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ distro
jinja2
pyroute2
requests
rpmautospec-core
templated-dictionary
Empty file added mock/tests/__init__.py
Empty file.
14 changes: 14 additions & 0 deletions mock/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Common pytest fixtures."""

import pytest


pytest_version_tuple = tuple(int(piece) for piece in pytest.__version__.split("."))
if pytest_version_tuple < (3, 9):
# Old versions of pytest don’t have the tmp_path fixture, fill it in here.
from pathlib import Path

@pytest.fixture
def tmp_path(tmpdir):
"""Return temporary directory path object."""
return Path(tmpdir)
Empty file added mock/tests/plugins/__init__.py
Empty file.
Loading

0 comments on commit ed377be

Please sign in to comment.