mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-13 20:08:05 +08:00
133 lines
5.7 KiB
Python
133 lines
5.7 KiB
Python
"""Evaluate package metadata without downloading an OpenWrt SDK.
|
|
|
|
The SDK captures the build dependency graph inside BuildPackage, then expands
|
|
Package/<name>/DEPENDS when writing the package. Keep that boundary in this
|
|
fixture so APK conflicts cannot accidentally become Kconfig dependencies.
|
|
Actual SDK builds additionally validate the generated APK/IPK metadata.
|
|
"""
|
|
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
PACKAGES = ("smart-srun", "luci-app-smart-srun", "luci-app-smart-srun-bundle")
|
|
BUILD_DEPENDS = {
|
|
"smart-srun": {"+ca-bundle", "+uci", "+ubus", "+procd", "+rpcd", "+rpcd-mod-iwinfo"},
|
|
"luci-app-smart-srun": {"+smart-srun", "+luci-base", "+luci-compat"},
|
|
"luci-app-smart-srun-bundle": {"+ca-bundle", "+uci", "+ubus", "+procd", "+rpcd", "+rpcd-mod-iwinfo", "+luci-base", "+luci-compat"},
|
|
}
|
|
CONFLICTS = {
|
|
"smart-srun": {"luci-app-smart-srun-bundle"},
|
|
"luci-app-smart-srun": {"luci-app-smart-srun-bundle"},
|
|
"luci-app-smart-srun-bundle": {"smart-srun", "luci-app-smart-srun"},
|
|
}
|
|
|
|
|
|
@unittest.skipUnless(shutil.which("make"), "GNU make is required")
|
|
class PackageConflictTests(unittest.TestCase):
|
|
def evaluate_metadata(self, use_apk, dump=False):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
path = Path(temp_dir)
|
|
(path / "rules.mk").write_text("INCLUDE_DIR := .\n", encoding="utf-8")
|
|
go_framework = path / "feeds/packages/lang/golang/golang-package.mk"
|
|
go_framework.parent.mkdir(parents=True)
|
|
go_framework.write_text("# Dependency graph fixture; real builds use the pinned SDK framework.\n")
|
|
(path / "package.mk").write_text(
|
|
"define BuildPackage\n"
|
|
"$(eval DEPENDS :=)\n"
|
|
"$(eval CONFLICTS :=)\n"
|
|
"$(eval EXTRA_DEPENDS :=)\n"
|
|
"$(eval $(call Package/$(1)))\n"
|
|
"$(eval BuildDepends/$(1) := $(DEPENDS))\n"
|
|
"$(eval Conflicts/$(1) := $(CONFLICTS))\n"
|
|
"$(eval ExactDepends/$(1) := $(EXTRA_DEPENDS))\n"
|
|
"$(eval Package/$(1)/DEPENDS := $(subst +,,$(DEPENDS)))\n"
|
|
"endef\n",
|
|
encoding="utf-8",
|
|
)
|
|
makefile = (REPO_ROOT / "Makefile").read_text(encoding="utf-8")
|
|
for package in PACKAGES:
|
|
for field, variable in (
|
|
("build", "BuildDepends/" + package),
|
|
("conflicts", "Conflicts/" + package),
|
|
("runtime", "Package/" + package + "/DEPENDS"),
|
|
("exact", "ExactDepends/" + package),
|
|
):
|
|
makefile += "\n$(info contract:%s:%s:$(%s))" % (
|
|
package,
|
|
field,
|
|
variable,
|
|
)
|
|
makefile += "\n.PHONY: contract\ncontract:\n"
|
|
(path / "Makefile").write_text(makefile, encoding="utf-8")
|
|
completed = subprocess.run(
|
|
[
|
|
shutil.which("make"),
|
|
"--no-print-directory",
|
|
"-s",
|
|
"TOPDIR=.",
|
|
"CONFIG_USE_APK=" + ("y" if use_apk else ""),
|
|
"DUMP=" + ("1" if dump else ""),
|
|
"VERSION=2.0.0~rc1-r1",
|
|
"contract",
|
|
],
|
|
cwd=path,
|
|
stdin=subprocess.DEVNULL,
|
|
text=True,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
metadata = {}
|
|
for line in completed.stdout.splitlines():
|
|
if line.startswith("contract:"):
|
|
_, package, field, value = line.split(":", 3)
|
|
metadata.setdefault(package, {})[field] = set(
|
|
filter(None, re.split(r"[,\s]+", value.strip()))
|
|
)
|
|
self.assertEqual(set(metadata), set(PACKAGES))
|
|
return metadata
|
|
|
|
def test_luci_uses_sdk_version_including_its_actual_release_suffix(self):
|
|
metadata = self.evaluate_metadata(False)
|
|
self.assertEqual(metadata["luci-app-smart-srun"]["exact"],
|
|
{"smart-srun", "(=2.0.0~rc1-r1)"})
|
|
|
|
def test_dump_omits_runtime_conflicts_from_kconfig(self):
|
|
metadata = self.evaluate_metadata(False, dump=True)
|
|
for package in PACKAGES:
|
|
with self.subTest(package=package):
|
|
self.assertEqual(metadata[package]["build"], BUILD_DEPENDS[package])
|
|
self.assertEqual(metadata[package]["conflicts"], set())
|
|
|
|
def test_ipk_keeps_conflicts_separate_from_runtime_dependencies(self):
|
|
metadata = self.evaluate_metadata(False)
|
|
for package in PACKAGES:
|
|
with self.subTest(package=package):
|
|
self.assertEqual(metadata[package]["build"], BUILD_DEPENDS[package])
|
|
self.assertEqual(metadata[package]["conflicts"], CONFLICTS[package])
|
|
self.assertEqual(
|
|
metadata[package]["runtime"],
|
|
{dep.lstrip("+") for dep in BUILD_DEPENDS[package]},
|
|
)
|
|
|
|
def test_apk_excludes_bundle_without_changing_the_split_build_graph(self):
|
|
metadata = self.evaluate_metadata(True)
|
|
for package in PACKAGES:
|
|
with self.subTest(package=package):
|
|
self.assertEqual(metadata[package]["build"], BUILD_DEPENDS[package])
|
|
self.assertEqual(metadata[package]["conflicts"], CONFLICTS[package])
|
|
self.assertEqual(
|
|
metadata[package]["runtime"],
|
|
{dep.lstrip("+") for dep in BUILD_DEPENDS[package]}
|
|
| {"!" + conflict for conflict in CONFLICTS[package]},
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|