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: rpm-software-management#1358
Merges: rpm-software-management#1393
Relates: rpm-software-management#1380
  • Loading branch information
praiskup committed Sep 3, 2024
1 parent a4979b7 commit 538c135
Show file tree
Hide file tree
Showing 24 changed files with 1,268 additions and 15 deletions.
1 change: 0 additions & 1 deletion behave/features/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ 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/"
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

# We'd like to use centos-stream+epel-9 here right now, but c9s is not
# bootstrap_image_ready=True :-( and rhel-9 packages are CDN (RHSM)
# only. We SHOULD be fine with c10s once container images are available.
# Fedora 40 is DNF4 but not "bootstrap_image_ready" either :-(
#Scenario: Isolated build against a DNF4 distribution
# Given an unique mock namespace
# When deps for mock-test-bump-version-1-0.src.rpm are calculated against centos-stream+epel-10-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
46 changes: 46 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_,
)
from behave import given, when, then # pylint: disable=no-name-in-module
import jsonschema

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,44 @@ 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)
38 changes: 36 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,14 +68,13 @@ 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:
Expand Down Expand Up @@ -103,6 +103,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
76 changes: 76 additions & 0 deletions docs/Plugin-BuildrootLock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
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).

Example structure
-----------------

Contents of such a lockfile may look like:

{
"version": "1.0.0",
"buildroot": {
"packages": [
{
"license": "MIT",
"name": "fedora-repos-rawhide",
"version": "42",
"release": "0.1",
"arch": "noarch",
"epoch": null,
"sigmd5": "4378929dac9e51ed8470de5173ae664e",
"signature": null,
"url": "http://ftp.sh.cvut.cz/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/f/fedora-repos-rawhide-42-0.1.noarch.rpm"
},
...
]
},
"config": {
"target_arch": "x86_64",
"legal_host_arches": [
"x86_64"
],
"dist": "rawhide",
"package_manager": "dnf5",
"bootstrap_image": "registry.fedoraproject.org/fedora:rawhide",
"bootstrap_image_ready": true
}
}

The list of packages is sorted by the `name + arch` pair.
Loading

0 comments on commit 538c135

Please sign in to comment.