From a9d1049a3c0ad767b2bb29264b5aaf70521eb271 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 6 Dec 2021 20:30:58 +0100 Subject: [PATCH] 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 --- pmb/build/_package.py | 5 +++++ pmb/chroot/apk.py | 4 ++++ pmb/parse/apkindex.py | 2 -- pmb/parse/depends.py | 30 ++++++++++++++++++++---------- test/test_apk.py | 32 ++++++++++++++++++++++++++++++++ test/test_build_package.py | 10 ++++++---- test/test_parse_apkindex.py | 28 ++++++++++++++++++++++++++++ test/test_parse_depends.py | 6 ++++-- test/testdata/apkindex/conflict | 20 ++++++++++++++++++++ 9 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 test/test_apk.py create mode 100644 test/testdata/apkindex/conflict diff --git a/pmb/build/_package.py b/pmb/build/_package.py index 99469946..79504bec 100644 --- a/pmb/build/_package.py +++ b/pmb/build/_package.py @@ -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: " + diff --git a/pmb/chroot/apk.py b/pmb/chroot/apk.py index 34411c1d..88a39738 100644 --- a/pmb/chroot/apk.py +++ b/pmb/chroot/apk.py @@ -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: diff --git a/pmb/parse/apkindex.py b/pmb/parse/apkindex.py index 984c534a..0b27995a 100644 --- a/pmb/parse/apkindex.py +++ b/pmb/parse/apkindex.py @@ -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] diff --git a/pmb/parse/depends.py b/pmb/parse/depends.py index cb6b5565..b6aec723 100644 --- a/pmb/parse/depends.py +++ b/pmb/parse/depends.py @@ -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 diff --git a/test/test_apk.py b/test/test_apk.py new file mode 100644 index 00000000..613e0aae --- /dev/null +++ b/test/test_apk.py @@ -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 diff --git a/test/test_build_package.py b/test/test_build_package.py index 48d15f5f..b4cc395f 100644 --- a/test/test_build_package.py +++ b/test/test_build_package.py @@ -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): diff --git a/test/test_parse_apkindex.py b/test/test_parse_apkindex.py index dbe64417..20746e84 100644 --- a/test/test_parse_apkindex.py +++ b/test/test_parse_apkindex.py @@ -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 diff --git a/test/test_parse_depends.py b/test/test_parse_depends.py index 60634041..ff6db119 100644 --- a/test/test_parse_depends.py +++ b/test/test_parse_depends.py @@ -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 diff --git a/test/testdata/apkindex/conflict b/test/testdata/apkindex/conflict new file mode 100644 index 00000000..ebf3b827 --- /dev/null +++ b/test/testdata/apkindex/conflict @@ -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= +