build: add support for conflicting dependencies (MR 2146)

This adds support for the depends="!conflict ..." syntax for explicitly
marking another package as conflicting with the current one.

Fixes: #2085
This commit is contained in:
Johannes Marbach 2021-12-06 20:30:58 +01:00
parent 8a14d366ef
commit a9d1049a3c
No known key found for this signature in database
GPG Key ID: 5AE7F5513E0885CB
9 changed files with 119 additions and 18 deletions

View File

@ -130,6 +130,9 @@ def build_depends(args, apkbuild, arch, strict):
if "no_depends" in args and args.no_depends:
pmb.helpers.repo.update(args, arch)
for depend in depends:
# Ignore conflicting dependencies
if depend.startswith("!"):
continue
# Check if binary package is missing
if not pmb.parse.apkindex.package(args, depend, arch, False):
raise RuntimeError("Missing binary package for dependency '" +
@ -147,6 +150,8 @@ def build_depends(args, apkbuild, arch, strict):
else:
# Build the dependencies
for depend in depends:
if depend.startswith("!"):
continue
if package(args, depend, arch, strict=strict):
depends_built += [depend]
logging.verbose(pkgname + ": build dependencies: done, built: " +

View File

@ -97,6 +97,10 @@ def install_is_necessary(args, build, arch, package, packages_installed):
:returns: True if the package needs to be installed/updated,
False otherwise.
"""
# For packages to be removed we can do the test immediately
if package.startswith("!"):
return package[1:] in packages_installed
# User may have disabled buiding packages during "pmbootstrap install"
build_disabled = False
if args.action == "install" and not args.build_pkgs_on_install:

View File

@ -81,8 +81,6 @@ def parse_next_block(path, lines, start):
values = ret[key].split(" ")
ret[key] = []
for value in values:
if value.startswith("!"):
continue
for operator in [">", "=", "<", "~"]:
if operator in value:
value = value.split(operator)[0]

View File

@ -116,7 +116,8 @@ def recurse(args, pkgnames, suffix="native"):
has multiple providers, we look at the installed packages in
the chroot to make a decision (see package_provider()).
:returns: list of pkgnames: consists of the initial pkgnames plus all
depends
depends. Dependencies explicitly marked as conflicting are
prefixed with !.
"""
logging.debug(f"({suffix}) calculate depends of {', '.join(pkgnames)} "
"(pmbootstrap -v for details)")
@ -131,6 +132,10 @@ def recurse(args, pkgnames, suffix="native"):
if pkgname_depend in ret:
continue
# Check if the dependency is explicitly marked as conflicting
is_conflict = pkgname_depend.startswith("!")
pkgname_depend = pkgname_depend.lstrip("!")
# Get depends and pkgname from aports
pkgnames_install = list(ret) + todo
package = package_from_aports(args, pkgname_depend)
@ -147,18 +152,23 @@ def recurse(args, pkgnames, suffix="native"):
f"Required by '{source}'. See: "
"https://postmarketos.org/depends")
# Append to todo/ret (unless it is a duplicate)
# Determine pkgname
pkgname = package["pkgname"]
if is_conflict:
pkgname = f"!{pkgname}"
# Append to todo/ret (unless it is a duplicate)
if pkgname in ret:
logging.verbose(f"{pkgname}: already found")
else:
depends = package["depends"]
logging.verbose(f"{pkgname}: depends on: {','.join(depends)}")
if depends:
todo += depends
for dep in depends:
if dep not in required_by:
required_by[dep] = set()
required_by[dep].add(pkgname_depend)
if not is_conflict:
depends = package["depends"]
logging.verbose(f"{pkgname}: depends on: {','.join(depends)}")
if depends:
todo += depends
for dep in depends:
if dep not in required_by:
required_by[dep] = set()
required_by[dep].add(pkgname_depend)
ret.append(pkgname)
return ret

32
test/test_apk.py Normal file
View File

@ -0,0 +1,32 @@
# Copyright 2021 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import pytest
import sys
import pmb_test # noqa
import pmb.chroot.apk
@pytest.fixture
def args(tmpdir, request):
import pmb.parse
sys.argv = ["pmbootstrap.py", "init"]
args = pmb.parse.arguments()
args.log = args.work + "/log_testsuite.txt"
pmb.helpers.logging.init(args)
request.addfinalizer(pmb.helpers.logging.logfd.close)
return args
def test_install_is_necessary(args):
# osk-sdl not installed, nothing to do
ret = pmb.chroot.apk.install_is_necessary(args, False, "aarch64",
"!osk-sdl",
{"unl0kr": {"unl0kr": {}}})
assert not ret
# osk-sdl installed, (un)install necessary
ret = pmb.chroot.apk.install_is_necessary(args, False, "aarch64",
"!osk-sdl",
{"osk-sdl": {"osk-sdl": {}}})
assert ret

View File

@ -140,16 +140,18 @@ def test_get_depends(monkeypatch):
def test_build_depends(args, monkeypatch):
# Shortcut and fake apkbuild
func = pmb.build._package.build_depends
apkbuild = {"pkgname": "test", "depends": ["a"], "makedepends": ["b"],
"checkdepends": [], "subpackages": {"d": None}, "options": []}
apkbuild = {"pkgname": "test", "depends": ["a", "!c"],
"makedepends": ["b"], "checkdepends": [],
"subpackages": {"d": None}, "options": []}
# No depends built (first makedepends + depends, then only makedepends)
monkeypatch.setattr(pmb.build._package, "package", return_none)
assert func(args, apkbuild, "armhf", True) == (["a", "b"], [])
assert func(args, apkbuild, "armhf", True) == (["!c", "a", "b"], [])
# All depends built (makedepends only)
monkeypatch.setattr(pmb.build._package, "package", return_string)
assert func(args, apkbuild, "armhf", False) == (["a", "b"], ["a", "b"])
assert func(args, apkbuild, "armhf", False) == (["!c", "a", "b"],
["a", "b"])
def test_build_depends_no_binary_error(args, monkeypatch):

View File

@ -115,6 +115,34 @@ def test_parse_next_block_virtual():
assert start == [31]
def test_parse_next_block_conflict():
"""
Test parsing a package that specifies a conflicting dependency from an
APKINDEX.
"""
# Read the file
func = pmb.parse.apkindex.parse_next_block
path = pmb.config.pmb_src + "/test/testdata/apkindex/conflict"
with open(path, "r", encoding="utf-8") as handle:
lines = handle.readlines()
# First block
start = [0]
block = {'arch': 'x86_64',
'depends': ['!conflict', 'so:libc.musl-x86_64.so.1'],
'origin': 'hello-world',
'pkgname': 'hello-world',
'provides': ['cmd:hello-world'],
'timestamp': '1500000000',
'version': '2-r0'}
assert func(path, lines, start) == block
assert start == [20]
# No more blocks
assert func(path, lines, start) is None
assert start == [20]
def test_parse_add_block(args):
func = pmb.parse.apkindex.parse_add_block
multiple_providers = False

View File

@ -146,7 +146,8 @@ def test_recurse(args, monkeypatch):
depends = {
"test": ["libtest", "so:libtest.so.1"],
"libtest": ["libtest_depend"],
"libtest_depend": [],
"libtest_depend": ["!libtest_conflict"],
"libtest_conflict": [],
"so:libtest.so.1": ["libtest_depend"],
}
@ -158,5 +159,6 @@ def test_recurse(args, monkeypatch):
# Run
func = pmb.parse.depends.recurse
pkgnames = ["test", "so:libtest.so.1"]
result = ["test", "so:libtest.so.1", "libtest", "libtest_depend"]
result = ["test", "so:libtest.so.1", "libtest", "libtest_depend",
"!libtest_conflict"]
assert func(args, pkgnames) == result

20
test/testdata/apkindex/conflict vendored Normal file
View File

@ -0,0 +1,20 @@
C:Q1XaZzCVZ9mvH8djPyEb5aUYhG3r4=
P:hello-world
V:2-r0
A:x86_64
S:2897
I:20480
T:hello world program to be built in the testsuite
U:https://en.wikipedia.org/wiki/%22Hello,_World!%22_program
L:MIT
o:hello-world
t:1500000000
c:
D:!conflict so:libc.musl-x86_64.so.1
p:cmd:hello-world
F:usr
F:usr/bin
R:hello-world
a:0:0:755
Z:Q1ZjTpsnMchSsSwEPB1cTjihYuJvo=