Skip to content

Commit

Permalink
Add support for isolated builds
Browse files Browse the repository at this point in the history
There are new options, --calculate-build-dependencies and
--isolated-build.  New buildroot_lock plugin added.

Fixes: #1358
Merges: #1393
Relates: #1380
  • Loading branch information
praiskup committed Sep 4, 2024
1 parent d2ff946 commit b3c6114
Show file tree
Hide file tree
Showing 24 changed files with 1,247 additions and 15 deletions.
2 changes: 1 addition & 1 deletion behave/features/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ def before_all(context):

default_config = os.readlink("/etc/mock/default.cfg")
context.chroot = default_config[:-4] # drop cfg suffix
context.chroot_used = False

context.test_storage = (
"https://github.com/"
"rpm-software-management/mock-test-data/raw/main/")

context.download = lambda url: _download(context, url)
context.download_rpm = lambda rpm: _download_rpm(context, rpm)
context.next_mock_options = []


def _cleanup_workdir(context):
Expand Down
22 changes: 22 additions & 0 deletions behave/features/isolated-build.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Feature: Mock 5.7+ supports isolated builds

@isolated_build
Scenario: Isolated build against a DNF5 distribution
Given an unique mock namespace
When deps for python-copr-999-1.src.rpm are calculated against fedora-rawhide-x86_64
And a local repository is created from lockfile
And an isolated build is retriggered with the lockfile and repository
Then the build succeeds
And the produced lockfile is validated properly

@isolated_build
Scenario: Isolated build against a DNF4 distribution
Given an unique mock namespace
# Temporary image, until we resolve https://issues.redhat.com/browse/CS-2506
And next mock call uses --config-opts=bootstrap_image=quay.io/mock/behave-testing-c9s-bootstrap option
And next mock call uses --config-opts=bootstrap_image_ready=True option
When deps for mock-test-bump-version-1-0.src.rpm are calculated against centos-stream+epel-9-x86_64
And a local repository is created from lockfile
And an isolated build is retriggered with the lockfile and repository
Then the build succeeds
And the produced lockfile is validated properly
51 changes: 51 additions & 0 deletions behave/features/steps/other.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import glob
import importlib
import json
import os
import shutil
import tempfile
Expand All @@ -12,14 +13,18 @@
ends_with,
equal_to,
has_item,
has_entries,
has_length,
not_,
)
import jsonschema
from behave import given, when, then # pylint: disable=no-name-in-module

from testlib import no_output, run

# flake8: noqa
# pylint: disable=missing-function-docstring,function-redefined
# mypy: disable-error-code="no-redef"


def _first_int(string, max_lines=20):
Expand Down Expand Up @@ -187,3 +192,49 @@ def step_impl(context, call, module, args):
def step_impl(context, field, value):
assert_that(context.last_method_call_retval[field],
equal_to(value))


@when('deps for {srpm} are calculated against {chroot}')
def step_impl(context, srpm, chroot):
url = context.test_storage + srpm
context.mock.calculate_deps(url, chroot)


@when('a local repository is created from lockfile')
def step_impl(context):
mock_run = context.mock_runs["calculate-build-deps"][-1]
lockfile = mock_run["lockfile"]

context.local_repo = tempfile.mkdtemp(prefix="mock-tests-local-repo-")
cmd = ["mock-isolated-repo", "--lockfile", lockfile, "--output-repo",
context.local_repo]
assert_that(run(cmd)[0], equal_to(0))


@when('an isolated build is retriggered with the lockfile and repository')
def step_impl(context):
context.mock.isolated_build()


@then('the produced lockfile is validated properly')
def step_impl(context):
mock_run = context.mock_runs["calculate-build-deps"][-1]
lockfile = mock_run["lockfile"]
with open(lockfile, "r", encoding="utf-8") as fd:
lockfile_data = json.load(fd)

assert_that(lockfile_data["buildroot"]["rpms"],
has_item(has_entries({"name": "filesystem"})))

schemafile = os.path.join(os.path.dirname(__file__), '..', '..', '..',
"mock", "docs",
"buildroot-lock-schema-1.0.0.json")
with open(schemafile, "r", encoding="utf-8") as fd:
schema = json.load(fd)

jsonschema.validate(lockfile_data, schema)


@given('next mock call uses {option} option')
def step_impl(context, option):
context.next_mock_options.append(option)
41 changes: 39 additions & 2 deletions behave/testlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from contextlib import contextmanager
import io
import pipes
import os
import subprocess
import sys

Expand Down Expand Up @@ -67,20 +68,22 @@ def __init__(self, context):
context.mock_runs = {
"init": [],
"rebuild": [],
"calculate-build-deps": [],
}

@property
def basecmd(self):
""" return the pre-configured mock base command """
cmd = ["mock"]
if self.context.chroot_used:
cmd += ["--chroot", self.context.chroot]
if self.context.uniqueext_used:
cmd += ["--uniqueext", self.context.uniqueext]
for repo in self.context.add_repos:
cmd += ["-a", repo]
if self.common_opts:
cmd += self.common_opts
if self.context.next_mock_options:
cmd += self.context.next_mock_options
self.context.next_mock_options = []
return cmd

def init(self):
Expand All @@ -103,6 +106,40 @@ def rebuild(self, srpms):
"srpms": srpms,
}]

def calculate_deps(self, srpm, chroot):
"""
Call Mock with --calculate-build-dependencies and produce lockfile
"""
out, err = run_check(
self.basecmd + ["-r", chroot] + ["--calculate-build-dependencies",
srpm])
self.context.chroot = chroot
self.context.mock_runs["calculate-build-deps"].append({
"status": 0,
"out": out,
"err": err,
"srpm": srpm,
"chroot": chroot,
"lockfile": os.path.join(self.resultdir, "buildroot_lock.json")
})

def isolated_build(self):
"""
From the previous calculate_deps() run, perform isolated build
"""
mock_calc = self.context.mock_runs["calculate-build-deps"][-1]
out, err = run_check(self.basecmd + [
"--isolated-build", mock_calc["lockfile"], self.context.local_repo,
mock_calc["srpm"]
])
self.context.mock_runs["rebuild"].append({
"status": 0,
"out": out,
"err": err,
})
# We built into an isolated-build.cfg!
self.context.chroot = "isolated-build"

def clean(self):
""" Clean chroot, but keep dnf/yum caches """
run_check(self.basecmd + [
Expand Down
39 changes: 39 additions & 0 deletions docs/Plugin-BuildrootLock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
layout: default
title: Plugin buildroot_lock
---

buildroot_lock Plugin
=====================

This plugin generates an additional build artifact—the buildroot *lockfile*
(`buildroot_lock.json` file in the result directory).

The *lockfile* describes both the list of buildroot sources (e.g., a list of
installed RPMs, bootstrap image info, etc.) and a set of Mock configuration
options. Using this information, Mock can later reproduce the buildroot
preparation (see the [Isolated Builds feature page](feature-isolated-builds)).

This plugin is **disabled** by default but is automatically enabled with the
`--calculate-build-dependencies` option. You can enable it (for all builds) by
this configuration snippet:

```python
config_opts['plugin_conf']['buildroot_lock_enable'] = True
```

**Note:** This plugin does not work with the `--offline` option.


Format of the *buildroot_lock.json* file
----------------------------------------

The file `buildroot_lock.json` is a JSON file. List of JSON Schema files is
installed together with the Mock RPM package:

rpm -ql mock | grep schema
/usr/share/doc/mock/buildroot-lock-schema-1.0.0.json

Currently, we do not provide a compatibility promise. Only the exact same
version of Mock that produced the file is guaranteed to read and process it.
For more information, see [Isolated Builds](feature-isolated-builds).
Loading

0 comments on commit b3c6114

Please sign in to comment.