class PackageList(object): """Represents a list of packages """ class Remote(object): """A remote package with an optional target """ def __init__(self, name, target): """ :param str name: The name of the package :param str target: The name of the target release """ self.name = name self.target = target def __str__(self): """Converts the package into somehting that apt-get install can parse :rtype: str """ if self.target is None: return self.name else: return self.name + '/' + self.target class Local(object): """A local package """ def __init__(self, path): """ :param str path: The path to the local package """ self.path = path def __str__(self): """ :return: The path to the local package :rtype: string """ return self.path def __init__(self, manifest_vars, source_lists): """ :param dict manifest_vars: The manifest variables :param SourceLists source_lists: The sourcelists for apt """ self.manifest_vars = manifest_vars self.source_lists = source_lists # The default_target is the release we are bootstrapping self.default_target = '{system.release}'.format(**self.manifest_vars) # The list of packages that should be installed, this is not a set. # We want to preserve the order in which the packages were added so that local # packages may be installed in the correct order. self.install = [] # A function that filters the install list and only returns remote packages self.remote = lambda: filter(lambda x: isinstance(x, self.Remote), self.install) def add(self, name, target=None): """Adds a package to the install list :param str name: The name of the package to install, may contain manifest vars references :param str target: The name of the target release for the package, may contain manifest vars references :raises PackageError: When a package of the same name but with a different target has already been added. :raises PackageError: When the specified target release could not be found. """ from exceptions import PackageError name = name.format(**self.manifest_vars) if target is not None: target = target.format(**self.manifest_vars) # Check if the package has already been added. # If so, make sure it's the same target and raise a PackageError otherwise package = next((pkg for pkg in self.remote() if pkg.name == name), None) if package is not None: # It's the same target if the target names match or one of the targets is None # and the other is the default target. same_target = package.target == target same_target = same_target or package.target is None and target == self.default_target same_target = same_target or package.target == self.default_target and target is None if not same_target: msg = ('The package {name} was already added to the package list, ' 'but with target release `{target}\' instead of `{add_target}\'' .format(name=name, target=package.target, add_target=target)) raise PackageError(msg) # The package has already been added, skip the checks below return # Check if the target exists (unless it's the default target) in the sources list # raise a PackageError if does not if target not in (None, self.default_target) and not self.source_lists.target_exists(target): msg = ('The target release {target} was not found in the sources list').format(target=target) raise PackageError(msg) # Note that we maintain the target value even if it is none. # This allows us to preserve the semantics of the default target when calling apt-get install # Why? Try installing nfs-client/wheezy, you can't. It's a virtual package for which you cannot define # a target release. Only `apt-get install nfs-client` works. self.install.append(self.Remote(name, target)) def add_local(self, package_path): """Adds a local package to the installation list :param str package_path: Path to the local package, may contain manifest vars references """ package_path = package_path.format(**self.manifest_vars) self.install.append(self.Local(package_path))