package: Use package expressions in Packages component

- managed_packages() finds all possible packages that could be
  installed. This is used for the check in the action script.

- resolve() finds actual packages to be installed. This is used in
  setup, diagnose, and has_unavailable_packages.

Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
James Valleroy 2022-03-05 11:24:58 -05:00
parent 31a457029c
commit 45820fbdfa
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
3 changed files with 65 additions and 16 deletions

View File

@ -131,7 +131,7 @@ def _assert_managed_packages(module, packages):
app = cls() app = cls()
components = app.get_components_of_type(Packages) components = app.get_components_of_type(Packages)
for component in components: for component in components:
managed_packages += component.packages managed_packages += component.managed_packages()
for package in packages: for package in packages:
assert package in managed_packages assert package in managed_packages

View File

@ -101,7 +101,8 @@ class Packages(app.FollowerComponent):
IGNORE = 'ignore' # Proceed as if there are no conflicts IGNORE = 'ignore' # Proceed as if there are no conflicts
REMOVE = 'remove' # Remove the packages before installing the app REMOVE = 'remove' # Remove the packages before installing the app
def __init__(self, component_id: str, packages: list[str], def __init__(self, component_id: str,
packages: list[Union[str, PackageExpression]],
skip_recommends: bool = False, skip_recommends: bool = False,
conflicts: Optional[list[str]] = None, conflicts: Optional[list[str]] = None,
conflicts_action: Optional[ConflictsAction] = None): conflicts_action: Optional[ConflictsAction] = None):
@ -126,29 +127,50 @@ class Packages(app.FollowerComponent):
super().__init__(component_id) super().__init__(component_id)
self.component_id = component_id self.component_id = component_id
self._packages = packages self._packages = []
for package in packages:
if isinstance(package, str):
self._packages.append(Package(package))
else:
self._packages.append(package)
self.skip_recommends = skip_recommends self.skip_recommends = skip_recommends
self.conflicts = conflicts self.conflicts = conflicts
self.conflicts_action = conflicts_action self.conflicts_action = conflicts_action
@property @property
def packages(self) -> list[str]: def packages(self) -> list[Union[str, PackageExpression]]:
"""Return the list of packages managed by this component.""" """Return the list of packages and package expressions managed by this
component."""
return self._packages return self._packages
def managed_packages(self) -> list[str]:
"""Return the list of possible packages before resolving."""
managed_packages: list[str] = []
for package in self.packages:
managed_package = package.possible()
managed_packages.extend(managed_package)
return managed_packages
def resolve(self) -> list[str]:
"""Return the resolved list of packages to install."""
return [package.actual() for package in self.packages]
def setup(self, old_version): def setup(self, old_version):
"""Install the packages.""" """Install the packages."""
# TODO: Drop the need for setup helper. # TODO: Drop the need for setup helper.
module_name = self.app.__module__ module_name = self.app.__module__
module = sys.modules[module_name] module = sys.modules[module_name]
helper = module.setup_helper helper = module.setup_helper
helper.install(self.packages, skip_recommends=self.skip_recommends) helper.install(self.resolve(), skip_recommends=self.skip_recommends)
def diagnose(self): def diagnose(self):
"""Run diagnostics and return results.""" """Run diagnostics and return results."""
results = super().diagnose() results = super().diagnose()
cache = apt.Cache() cache = apt.Cache()
for package_name in self.packages: # XXX: Needs to be able to handle missing packages.
for package_name in self.resolve():
result = 'warning' result = 'warning'
latest_version = '?' latest_version = '?'
if package_name in cache: if package_name in cache:
@ -187,9 +209,12 @@ class Packages(app.FollowerComponent):
return None return None
# List of all packages from all Package components # List of all packages from all Package components
cache = apt.Cache() try:
return any(package for package in self.packages self.resolve()
if package not in cache) except MissingPackageError:
return True
return False
class PackageException(Exception): class PackageException(Exception):

View File

@ -53,8 +53,8 @@ class TestPackageExpressions(unittest.TestCase):
def test_packages_init(): def test_packages_init():
"""Test initialization of packages component.""" """Test initialization of packages component."""
component = Packages('test-component', ['foo', 'bar']) component = Packages('test-component', ['foo', 'bar'])
assert component.managed_packages() == ['foo', 'bar']
assert component.component_id == 'test-component' assert component.component_id == 'test-component'
assert component.packages == ['foo', 'bar']
assert not component.skip_recommends assert not component.skip_recommends
assert component.conflicts is None assert component.conflicts is None
assert component.conflicts_action is None assert component.conflicts_action is None
@ -65,12 +65,27 @@ def test_packages_init():
component = Packages('test-component', [], skip_recommends=True, component = Packages('test-component', [], skip_recommends=True,
conflicts=['conflict1', 'conflict2'], conflicts=['conflict1', 'conflict2'],
conflicts_action=Packages.ConflictsAction.IGNORE) conflicts_action=Packages.ConflictsAction.IGNORE)
assert component.packages == [] assert component.managed_packages() == []
assert component.skip_recommends assert component.skip_recommends
assert component.conflicts == ['conflict1', 'conflict2'] assert component.conflicts == ['conflict1', 'conflict2']
assert component.conflicts_action == Packages.ConflictsAction.IGNORE assert component.conflicts_action == Packages.ConflictsAction.IGNORE
def test_packages_resolve():
"""Test resolving of package expressions."""
component = Packages('test-component', ['python3'])
assert component.resolve() == ['python3']
component = Packages('test-component',
[Package('unknown-package') | Package('python3')])
assert component.resolve() == ['python3']
component = Packages('test-component', [], skip_recommends=True,
conflicts=['conflict1', 'conflict2'],
conflicts_action=Packages.ConflictsAction.IGNORE)
assert component.resolve() == []
def test_packages_setup(): def test_packages_setup():
"""Test setting up packages component.""" """Test setting up packages component."""
@ -78,22 +93,31 @@ def test_packages_setup():
"""Test app""" """Test app"""
app_id = 'test-app' app_id = 'test-app'
component = Packages('test-component', ['foo1', 'bar1']) component = Packages('test-component', ['python3', 'bash'])
app = TestApp() app = TestApp()
app.add(component) app.add(component)
setup_helper.reset_mock() setup_helper.reset_mock()
app.setup(old_version=3) app.setup(old_version=3)
setup_helper.install.assert_has_calls( setup_helper.install.assert_has_calls(
[call(['foo1', 'bar1'], skip_recommends=False)]) [call(['python3', 'bash'], skip_recommends=False)])
component = Packages('test-component', ['foo2', 'bar2'], component = Packages('test-component', ['bash', 'perl'],
skip_recommends=True) skip_recommends=True)
app = TestApp() app = TestApp()
app.add(component) app.add(component)
setup_helper.reset_mock() setup_helper.reset_mock()
app.setup(old_version=3) app.setup(old_version=3)
setup_helper.install.assert_has_calls( setup_helper.install.assert_has_calls(
[call(['foo2', 'bar2'], skip_recommends=True)]) [call(['bash', 'perl'], skip_recommends=True)])
component = Packages('test-component',
[Package('python3') | Package('unknown-package')])
app = TestApp()
app.add(component)
setup_helper.reset_mock()
app.setup(old_version=3)
setup_helper.install.assert_has_calls(
[call(['python3'], skip_recommends=False)])
@patch('apt.Cache') @patch('apt.Cache')