Refactor pkgmgr installers, introduce capability-based execution, and replace manifest layer
References: - Current ChatGPT conversation: https://chatgpt.com/share/6935d6d7-0ae4-800f-988a-44a50c17ba48 - Extended discussion: https://chatgpt.com/share/6935d734-fd84-800f-9755-290902b8cee8 Summary: This commit performs a major cleanup and modernization of the installation pipeline: 1. Introduced a new capability-detection subsystem: - Capabilities (python-runtime, make-install, nix-flake) are detected per installer/layer. - Installers run only when they add new capabilities. - Prevents duplicated work such as Python installers running when Nix already provides the runtime. 2. Removed deprecated pkgmgr.yml manifest installer: - Dependency resolution is now delegated entirely to real package managers (Nix, pip, make, distro build tools). - Simplifies layering and avoids unnecessary recursion. 3. Reworked OS-specific installers: - Arch PKGBUILD now uses 'makepkg --syncdeps --cleanbuild --install --noconfirm'. - Debian installer now builds proper .deb packages via dpkg-buildpackage + installs them. - RPM installer now builds packages using rpmbuild and installs them via rpm. 4. Switched from remote GitHub flakes to local-flake execution: - Wrapper now executes: nix run /usr/lib/package-manager#pkgmgr - Avoids lock-file write attempts and improves reliability in CI. 5. Added bash -i based integration test: - Correctly sources ~/.bashrc and evaluates alias + venv activation. - ‘pkgmgr --help’ is now printed for debugging without failing tests. 6. Updated unit tests across all installers: - Removed references to manifest installer. - Adjusted expectations for new behaviors (makepkg, dpkg-buildpackage, rpmbuild). - Added capability subsystem tests. 7. Improved flake.nix packaging logic: - The entire project source tree is copied into the runtime closure. - pkgmgr wrapper now executes runpy inside the packaged directory. Together, these changes create a predictable, layered, capability-driven installer pipeline with consistent behavior across Arch, Debian, RPM, Nix, and Python layers.
This commit is contained in:
@@ -26,39 +26,69 @@ class TestArchPkgbuildInstaller(unittest.TestCase):
|
||||
)
|
||||
self.installer = ArchPkgbuildInstaller()
|
||||
|
||||
@patch("pkgmgr.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000)
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch("shutil.which", return_value="/usr/bin/pacman")
|
||||
def test_supports_true_when_pacman_and_pkgbuild_exist(self, mock_which, mock_exists):
|
||||
@patch("shutil.which")
|
||||
def test_supports_true_when_tools_and_pkgbuild_exist(
|
||||
self, mock_which, mock_exists, mock_geteuid
|
||||
):
|
||||
def which_side_effect(name):
|
||||
if name in ("pacman", "makepkg"):
|
||||
return f"/usr/bin/{name}"
|
||||
return None
|
||||
|
||||
mock_which.side_effect = which_side_effect
|
||||
|
||||
self.assertTrue(self.installer.supports(self.ctx))
|
||||
mock_which.assert_called_with("pacman")
|
||||
|
||||
calls = [c.args[0] for c in mock_which.call_args_list]
|
||||
self.assertIn("pacman", calls)
|
||||
self.assertIn("makepkg", calls)
|
||||
mock_exists.assert_called_with(os.path.join(self.ctx.repo_dir, "PKGBUILD"))
|
||||
|
||||
@patch("pkgmgr.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=0)
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch("shutil.which")
|
||||
def test_supports_false_when_running_as_root(
|
||||
self, mock_which, mock_exists, mock_geteuid
|
||||
):
|
||||
mock_which.return_value = "/usr/bin/pacman"
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("pkgmgr.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000)
|
||||
@patch("os.path.exists", return_value=False)
|
||||
@patch("shutil.which", return_value="/usr/bin/pacman")
|
||||
def test_supports_false_when_pkgbuild_missing(self, mock_which, mock_exists):
|
||||
@patch("shutil.which")
|
||||
def test_supports_false_when_pkgbuild_missing(
|
||||
self, mock_which, mock_exists, mock_geteuid
|
||||
):
|
||||
mock_which.return_value = "/usr/bin/pacman"
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("pkgmgr.installers.os_packages.arch_pkgbuild.run_command")
|
||||
@patch("subprocess.check_output", return_value="python\ngit\n")
|
||||
@patch("pkgmgr.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000)
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch("shutil.which", return_value="/usr/bin/pacman")
|
||||
def test_run_installs_all_packages_and_uses_clean_bash(
|
||||
self, mock_which, mock_exists, mock_check_output, mock_run_command
|
||||
@patch("shutil.which")
|
||||
def test_run_builds_and_installs_with_makepkg(
|
||||
self, mock_which, mock_exists, mock_geteuid, mock_run_command
|
||||
):
|
||||
def which_side_effect(name):
|
||||
if name in ("pacman", "makepkg"):
|
||||
return f"/usr/bin/{name}"
|
||||
return None
|
||||
|
||||
mock_which.side_effect = which_side_effect
|
||||
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
# subprocess.check_output call
|
||||
args, kwargs = mock_check_output.call_args
|
||||
cmd_list = args[0]
|
||||
self.assertEqual(cmd_list[0], "bash")
|
||||
self.assertIn("--noprofile", cmd_list)
|
||||
self.assertIn("--norc", cmd_list)
|
||||
|
||||
# pacman install command
|
||||
cmd = mock_run_command.call_args[0][0]
|
||||
self.assertTrue(cmd.startswith("sudo pacman -S --noconfirm "))
|
||||
self.assertIn("python", cmd)
|
||||
self.assertIn("git", cmd)
|
||||
self.assertEqual(
|
||||
cmd,
|
||||
"makepkg --syncdeps --cleanbuild --install --noconfirm",
|
||||
)
|
||||
self.assertEqual(
|
||||
mock_run_command.call_args[1].get("cwd"),
|
||||
self.ctx.repo_dir,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# tests/unit/pkgmgr/installers/os_packages/test_debian_control.py
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch, mock_open
|
||||
from unittest.mock import patch
|
||||
|
||||
from pkgmgr.context import RepoContext
|
||||
from pkgmgr.installers.os_packages.debian_control import DebianControlInstaller
|
||||
@@ -26,40 +27,53 @@ class TestDebianControlInstaller(unittest.TestCase):
|
||||
self.installer = DebianControlInstaller()
|
||||
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch("shutil.which", return_value="/usr/bin/apt-get")
|
||||
@patch("shutil.which", return_value="/usr/bin/dpkg-buildpackage")
|
||||
def test_supports_true(self, mock_which, mock_exists):
|
||||
self.assertTrue(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch("shutil.which", return_value=None)
|
||||
def test_supports_false_without_apt(self, mock_which, mock_exists):
|
||||
def test_supports_false_without_dpkg_buildpackage(self, mock_which, mock_exists):
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("pkgmgr.installers.os_packages.debian_control.run_command")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="""
|
||||
Build-Depends: python3, git (>= 2.0)
|
||||
Depends: curl | wget
|
||||
""")
|
||||
@patch("glob.glob", return_value=["/tmp/package-manager_0.1.1_all.deb"])
|
||||
@patch("os.path.exists", return_value=True)
|
||||
@patch("shutil.which", return_value="/usr/bin/apt-get")
|
||||
def test_run_installs_parsed_packages(
|
||||
@patch("shutil.which")
|
||||
def test_run_builds_and_installs_debs(
|
||||
self,
|
||||
mock_which,
|
||||
mock_exists,
|
||||
mock_file,
|
||||
mock_run_command
|
||||
mock_glob,
|
||||
mock_run_command,
|
||||
):
|
||||
# dpkg-buildpackage + apt-get vorhanden
|
||||
def which_side_effect(name):
|
||||
if name == "dpkg-buildpackage":
|
||||
return "/usr/bin/dpkg-buildpackage"
|
||||
if name == "apt-get":
|
||||
return "/usr/bin/apt-get"
|
||||
return None
|
||||
|
||||
mock_which.side_effect = which_side_effect
|
||||
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
# First call: apt-get update
|
||||
self.assertIn("apt-get update", mock_run_command.call_args_list[0][0][0])
|
||||
cmds = [c[0][0] for c in mock_run_command.call_args_list]
|
||||
|
||||
# Second call: install packages
|
||||
install_cmd = mock_run_command.call_args_list[1][0][0]
|
||||
self.assertIn("apt-get install -y", install_cmd)
|
||||
self.assertIn("python3", install_cmd)
|
||||
self.assertIn("git", install_cmd)
|
||||
self.assertIn("curl", install_cmd)
|
||||
# 1) apt-get update
|
||||
self.assertTrue(any("apt-get update" in cmd for cmd in cmds))
|
||||
|
||||
# 2) apt-get build-dep ./
|
||||
self.assertTrue(any("apt-get build-dep -y ./ " in cmd or
|
||||
"apt-get build-dep -y ./"
|
||||
in cmd for cmd in cmds))
|
||||
|
||||
# 3) dpkg-buildpackage -b -us -uc
|
||||
self.assertTrue(any("dpkg-buildpackage -b -us -uc" in cmd for cmd in cmds))
|
||||
|
||||
# 4) dpkg -i ../*.deb
|
||||
self.assertTrue(any(cmd.startswith("sudo dpkg -i ") for cmd in cmds))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# tests/unit/pkgmgr/installers/os_packages/test_rpm_spec.py
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch, mock_open
|
||||
from unittest.mock import patch
|
||||
|
||||
from pkgmgr.context import RepoContext
|
||||
from pkgmgr.installers.os_packages.rpm_spec import RpmSpecInstaller
|
||||
@@ -26,34 +26,67 @@ class TestRpmSpecInstaller(unittest.TestCase):
|
||||
self.installer = RpmSpecInstaller()
|
||||
|
||||
@patch("glob.glob", return_value=["/tmp/repo/test.spec"])
|
||||
@patch("shutil.which", return_value="/usr/bin/dnf")
|
||||
@patch("shutil.which")
|
||||
def test_supports_true(self, mock_which, mock_glob):
|
||||
def which_side_effect(name):
|
||||
if name == "rpmbuild":
|
||||
return "/usr/bin/rpmbuild"
|
||||
if name == "dnf":
|
||||
return "/usr/bin/dnf"
|
||||
return None
|
||||
|
||||
mock_which.side_effect = which_side_effect
|
||||
|
||||
self.assertTrue(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("glob.glob", return_value=[])
|
||||
@patch("shutil.which", return_value="/usr/bin/dnf")
|
||||
@patch("shutil.which")
|
||||
def test_supports_false_missing_spec(self, mock_which, mock_glob):
|
||||
mock_which.return_value = "/usr/bin/rpmbuild"
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("pkgmgr.installers.os_packages.rpm_spec.run_command")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="""
|
||||
BuildRequires: python3-devel, git >= 2.0
|
||||
Requires: curl
|
||||
""")
|
||||
@patch("glob.glob", return_value=["/tmp/repo/test.spec"])
|
||||
@patch("shutil.which", return_value="/usr/bin/dnf")
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_run_installs_parsed_dependencies(
|
||||
self, mock_exists, mock_which, mock_glob, mock_file, mock_run_command
|
||||
@patch("glob.glob")
|
||||
@patch("shutil.which")
|
||||
def test_run_builds_and_installs_rpms(
|
||||
self,
|
||||
mock_which,
|
||||
mock_glob,
|
||||
mock_run_command,
|
||||
):
|
||||
# glob.glob wird zweimal benutzt: einmal für *.spec, einmal für gebaute RPMs
|
||||
def glob_side_effect(pattern, recursive=False):
|
||||
if pattern.endswith("*.spec"):
|
||||
return ["/tmp/repo/package-manager.spec"]
|
||||
if "rpmbuild/RPMS" in pattern:
|
||||
return ["/home/user/rpmbuild/RPMS/x86_64/package-manager-0.1.1.rpm"]
|
||||
return []
|
||||
|
||||
mock_glob.side_effect = glob_side_effect
|
||||
|
||||
def which_side_effect(name):
|
||||
if name == "rpmbuild":
|
||||
return "/usr/bin/rpmbuild"
|
||||
if name == "dnf":
|
||||
return "/usr/bin/dnf"
|
||||
if name == "rpm":
|
||||
return "/usr/bin/rpm"
|
||||
return None
|
||||
|
||||
mock_which.side_effect = which_side_effect
|
||||
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
install_cmd = mock_run_command.call_args_list[0][0][0]
|
||||
cmds = [c[0][0] for c in mock_run_command.call_args_list]
|
||||
|
||||
self.assertIn("dnf install -y", install_cmd)
|
||||
self.assertIn("python3-devel", install_cmd)
|
||||
self.assertIn("git", install_cmd)
|
||||
self.assertIn("curl", install_cmd)
|
||||
# 1) builddep
|
||||
self.assertTrue(any("builddep -y" in cmd for cmd in cmds))
|
||||
|
||||
# 2) rpmbuild -ba
|
||||
self.assertTrue(any(cmd.startswith("rpmbuild -ba ") for cmd in cmds))
|
||||
|
||||
# 3) rpm -i …
|
||||
self.assertTrue(any(cmd.startswith("sudo rpm -i ") for cmd in cmds))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
# tests/unit/pkgmgr/installers/test_pkgmgr_manifest.py
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch, mock_open
|
||||
|
||||
from pkgmgr.context import RepoContext
|
||||
from pkgmgr.installers.pkgmgr_manifest import PkgmgrManifestInstaller
|
||||
|
||||
|
||||
class TestPkgmgrManifestInstaller(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.repo = {"name": "test-repo"}
|
||||
self.ctx = RepoContext(
|
||||
repo=self.repo,
|
||||
identifier="test-id",
|
||||
repo_dir="/tmp/repo",
|
||||
repositories_base_dir="/tmp",
|
||||
bin_dir="/bin",
|
||||
all_repos=[self.repo],
|
||||
no_verification=False,
|
||||
preview=False,
|
||||
quiet=False,
|
||||
clone_mode="ssh",
|
||||
update_dependencies=True,
|
||||
)
|
||||
self.installer = PkgmgrManifestInstaller()
|
||||
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_supports_true_when_manifest_exists(self, mock_exists):
|
||||
self.assertTrue(self.installer.supports(self.ctx))
|
||||
manifest_path = os.path.join(self.ctx.repo_dir, "pkgmgr.yml")
|
||||
mock_exists.assert_called_with(manifest_path)
|
||||
|
||||
@patch("os.path.exists", return_value=False)
|
||||
def test_supports_false_when_manifest_missing(self, mock_exists):
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("pkgmgr.installers.pkgmgr_manifest.run_command")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="""
|
||||
version: 1
|
||||
author: "Kevin"
|
||||
url: "https://example.com"
|
||||
description: "Test repo"
|
||||
dependencies:
|
||||
- repository: github:user/repo1
|
||||
version: main
|
||||
reason: "Core dependency"
|
||||
- repository: github:user/repo2
|
||||
""")
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_run_installs_dependencies_and_pulls_when_update_enabled(
|
||||
self, mock_exists, mock_file, mock_run_command
|
||||
):
|
||||
self.installer.run(self.ctx)
|
||||
|
||||
# First call: pkgmgr pull github:user/repo1 github:user/repo2
|
||||
# Then calls to pkgmgr install ...
|
||||
cmds = [call_args[0][0] for call_args in mock_run_command.call_args_list]
|
||||
|
||||
self.assertIn(
|
||||
"pkgmgr pull github:user/repo1 github:user/repo2",
|
||||
cmds,
|
||||
)
|
||||
self.assertIn(
|
||||
"pkgmgr install github:user/repo1 --version main --dependencies --clone-mode ssh",
|
||||
cmds,
|
||||
)
|
||||
# For repo2: no version but dependencies + clone_mode
|
||||
self.assertIn(
|
||||
"pkgmgr install github:user/repo2 --dependencies --clone-mode ssh",
|
||||
cmds,
|
||||
)
|
||||
|
||||
@patch("pkgmgr.installers.pkgmgr_manifest.run_command")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="{}")
|
||||
@patch("os.path.exists", return_value=True)
|
||||
def test_run_no_dependencies_no_command_called(
|
||||
self, mock_exists, mock_file, mock_run_command
|
||||
):
|
||||
self.ctx.update_dependencies = True
|
||||
self.installer.run(self.ctx)
|
||||
mock_run_command.assert_not_called()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -30,19 +30,12 @@ class TestPythonInstaller(unittest.TestCase):
|
||||
def test_supports_true_when_pyproject_exists(self, mock_exists):
|
||||
self.assertTrue(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("os.path.exists", side_effect=lambda path: path.endswith("requirements.txt"))
|
||||
def test_supports_true_when_requirements_exists(self, mock_exists):
|
||||
self.assertTrue(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("os.path.exists", return_value=False)
|
||||
def test_supports_false_when_no_python_files(self, mock_exists):
|
||||
def test_supports_false_when_no_pyproject(self, mock_exists):
|
||||
self.assertFalse(self.installer.supports(self.ctx))
|
||||
|
||||
@patch("pkgmgr.installers.python.run_command")
|
||||
@patch(
|
||||
"os.path.exists",
|
||||
side_effect=lambda path: path.endswith("pyproject.toml")
|
||||
)
|
||||
@patch("os.path.exists", side_effect=lambda path: path.endswith("pyproject.toml"))
|
||||
def test_run_installs_project_from_pyproject(self, mock_exists, mock_run_command):
|
||||
self.installer.run(self.ctx)
|
||||
cmd = mock_run_command.call_args[0][0]
|
||||
@@ -52,20 +45,6 @@ class TestPythonInstaller(unittest.TestCase):
|
||||
self.ctx.repo_dir,
|
||||
)
|
||||
|
||||
@patch("pkgmgr.installers.python.run_command")
|
||||
@patch(
|
||||
"os.path.exists",
|
||||
side_effect=lambda path: path.endswith("requirements.txt")
|
||||
)
|
||||
def test_run_installs_dependencies_from_requirements(self, mock_exists, mock_run_command):
|
||||
self.installer.run(self.ctx)
|
||||
cmd = mock_run_command.call_args[0][0]
|
||||
self.assertIn("pip install -r requirements.txt", cmd)
|
||||
self.assertEqual(
|
||||
mock_run_command.call_args[1].get("cwd"),
|
||||
self.ctx.repo_dir,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
76
tests/unit/pkgmgr/test_capabilities.py
Normal file
76
tests/unit/pkgmgr/test_capabilities.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# tests/unit/pkgmgr/test_capabilities.py
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch, mock_open
|
||||
|
||||
from pkgmgr.capabilities import (
|
||||
PythonRuntimeCapability,
|
||||
MakeInstallCapability,
|
||||
NixFlakeCapability,
|
||||
)
|
||||
|
||||
|
||||
class DummyCtx:
|
||||
"""Minimal RepoContext stub with just repo_dir."""
|
||||
def __init__(self, repo_dir: str):
|
||||
self.repo_dir = repo_dir
|
||||
|
||||
|
||||
class TestCapabilities(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.ctx = DummyCtx("/tmp/repo")
|
||||
|
||||
@patch("pkgmgr.capabilities.os.path.exists")
|
||||
def test_python_runtime_python_layer_pyproject(self, mock_exists):
|
||||
cap = PythonRuntimeCapability()
|
||||
|
||||
def exists_side_effect(path):
|
||||
return path.endswith("pyproject.toml")
|
||||
|
||||
mock_exists.side_effect = exists_side_effect
|
||||
|
||||
self.assertTrue(cap.applies_to_layer("python"))
|
||||
self.assertTrue(cap.is_provided(self.ctx, "python"))
|
||||
|
||||
@patch("pkgmgr.capabilities._read_text_if_exists")
|
||||
@patch("pkgmgr.capabilities.os.path.exists")
|
||||
def test_python_runtime_nix_layer_flake(self, mock_exists, mock_read):
|
||||
cap = PythonRuntimeCapability()
|
||||
|
||||
def exists_side_effect(path):
|
||||
return path.endswith("flake.nix")
|
||||
|
||||
mock_exists.side_effect = exists_side_effect
|
||||
mock_read.return_value = "buildPythonApplication something"
|
||||
|
||||
self.assertTrue(cap.applies_to_layer("nix"))
|
||||
self.assertTrue(cap.is_provided(self.ctx, "nix"))
|
||||
|
||||
@patch("pkgmgr.capabilities.os.path.exists", return_value=True)
|
||||
@patch(
|
||||
"builtins.open",
|
||||
new_callable=mock_open,
|
||||
read_data="install:\n\t echo 'installing'\n",
|
||||
)
|
||||
def test_make_install_makefile_layer(self, mock_file, mock_exists):
|
||||
cap = MakeInstallCapability()
|
||||
|
||||
self.assertTrue(cap.applies_to_layer("makefile"))
|
||||
self.assertTrue(cap.is_provided(self.ctx, "makefile"))
|
||||
|
||||
@patch("pkgmgr.capabilities.os.path.exists")
|
||||
def test_nix_flake_capability_on_nix_layer(self, mock_exists):
|
||||
cap = NixFlakeCapability()
|
||||
|
||||
def exists_side_effect(path):
|
||||
return path.endswith("flake.nix")
|
||||
|
||||
mock_exists.side_effect = exists_side_effect
|
||||
|
||||
self.assertTrue(cap.applies_to_layer("nix"))
|
||||
self.assertTrue(cap.is_provided(self.ctx, "nix"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,13 +1,18 @@
|
||||
from pkgmgr.run_command import run_command
|
||||
# tests/unit/pkgmgr/test_install_repos.py
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from pkgmgr.context import RepoContext
|
||||
import pkgmgr.install_repos as install_module
|
||||
from pkgmgr.installers.base import BaseInstaller
|
||||
|
||||
|
||||
class DummyInstaller:
|
||||
class DummyInstaller(BaseInstaller):
|
||||
"""Simple installer for testing orchestration."""
|
||||
|
||||
layer = None # keine speziellen Capabilities
|
||||
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user