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()
components = app.get_components_of_type(Packages)
for component in components:
managed_packages += component.packages
managed_packages += component.managed_packages()
for package in 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
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,
conflicts: Optional[list[str]] = None,
conflicts_action: Optional[ConflictsAction] = None):
@ -126,29 +127,50 @@ class Packages(app.FollowerComponent):
super().__init__(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.conflicts = conflicts
self.conflicts_action = conflicts_action
@property
def packages(self) -> list[str]:
"""Return the list of packages managed by this component."""
def packages(self) -> list[Union[str, PackageExpression]]:
"""Return the list of packages and package expressions managed by this
component."""
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):
"""Install the packages."""
# TODO: Drop the need for setup helper.
module_name = self.app.__module__
module = sys.modules[module_name]
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):
"""Run diagnostics and return results."""
results = super().diagnose()
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'
latest_version = '?'
if package_name in cache:
@ -187,9 +209,12 @@ class Packages(app.FollowerComponent):
return None
# List of all packages from all Package components
cache = apt.Cache()
return any(package for package in self.packages
if package not in cache)
try:
self.resolve()
except MissingPackageError:
return True
return False
class PackageException(Exception):

View File

@ -53,8 +53,8 @@ class TestPackageExpressions(unittest.TestCase):
def test_packages_init():
"""Test initialization of packages component."""
component = Packages('test-component', ['foo', 'bar'])
assert component.managed_packages() == ['foo', 'bar']
assert component.component_id == 'test-component'
assert component.packages == ['foo', 'bar']
assert not component.skip_recommends
assert component.conflicts is None
assert component.conflicts_action is None
@ -65,12 +65,27 @@ def test_packages_init():
component = Packages('test-component', [], skip_recommends=True,
conflicts=['conflict1', 'conflict2'],
conflicts_action=Packages.ConflictsAction.IGNORE)
assert component.packages == []
assert component.managed_packages() == []
assert component.skip_recommends
assert component.conflicts == ['conflict1', 'conflict2']
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():
"""Test setting up packages component."""
@ -78,22 +93,31 @@ def test_packages_setup():
"""Test app"""
app_id = 'test-app'
component = Packages('test-component', ['foo1', 'bar1'])
component = Packages('test-component', ['python3', 'bash'])
app = TestApp()
app.add(component)
setup_helper.reset_mock()
app.setup(old_version=3)
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)
app = TestApp()
app.add(component)
setup_helper.reset_mock()
app.setup(old_version=3)
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')