Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for isolated builds #1393

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading