From f3e37faf7e131ab52c93c65d87ed7547040cc286 Mon Sep 17 00:00:00 2001 From: Niels Thykier Date: Sat, 7 May 2016 08:56:41 +0000 Subject: [PATCH] Add present-and-installable constraints support Solves first half of GH#5. Signed-off-by: Niels Thykier --- britney.conf | 4 +++ britney.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/britney.conf b/britney.conf index 8137fb0..9b9dd19 100644 --- a/britney.conf +++ b/britney.conf @@ -13,6 +13,10 @@ EXCUSES_YAML_OUTPUT = /srv/release.debian.org/britney/var/data-b2/output/excuses UPGRADE_OUTPUT = /srv/release.debian.org/britney/var/data-b2/output/output.txt HEIDI_OUTPUT = /srv/release.debian.org/britney/var/data-b2/output/HeidiResult +# External policy/constraints/faux-packages information that +# (presumably) rarely changes. Examples include "constraints". +STATIC_INPUT_DIR = /srv/release.debian.org/britney/input + # Directory for input files that Britney will update herself # (e.g. aging information) or will need regular updates # (e.g. urgency information). diff --git a/britney.py b/britney.py index 9cb4ab8..f3c1616 100755 --- a/britney.py +++ b/britney.py @@ -317,6 +317,21 @@ class Britney(object): # unstable self.binaries['testing'][arch] = self.read_binaries(self.options.testing, "testing", arch) + try: + constraints_file = os.path.join(self.options.static_input_dir, 'constraints') + except AttributeError: + self.log("The static_input_dir option is not set", type='I') + constraints_file = None + if constraints_file is not None and os.path.exists(constraints_file): + self.log("Loading constraints from %s" % constraints_file, type='I') + self.constraints = self._load_constraints(constraints_file) + else: + if constraints_file is not None: + self.log("No constraints as %s does not exist" % constraints_file, type='I') + self.constraints = { + 'keep-installable': [], + } + self.log("Compiling Installability tester", type="I") self._build_installability_tester(self.options.architectures) @@ -476,6 +491,88 @@ class Britney(object): if self.options.verbose or type in ("E", "W"): print("%s: [%s] - %s" % (type, time.asctime(), msg)) + def _load_constraints(self, constraints_file): + """Loads configurable constraints + + The constraints file can contain extra rules that Britney should attempt + to satisfy. Examples can be "keep package X in testing and ensure it is + installable". + + :param constraints_file: Path to the file containing the constraints + """ + tag_file = apt_pkg.TagFile(constraints_file) + get_field = tag_file.section.get + step = tag_file.step + no = 0 + faux_version = sys.intern('1') + faux_section = sys.intern('faux') + + while step(): + no += 1 + pkg_name = get_field('Fake-Package-Name', None) + if pkg_name is None: + raise ValueError("Missing Fake-Package-Name field in paragraph %d (file %s)" % (no, constraints_file)) + pkg_name = sys.intern(pkg_name) + + def mandatory_field(x): + v = get_field(x, None) + if v is None: + raise ValueError("Missing %s field for %s (file %s)" % (x, pkg_name, constraints_file)) + return v + + constraint = mandatory_field('Constraint') + if constraint not in {'present-and-installable'}: + raise ValueError("Unsupported constraint %s for %s (file %s)" % (constraint, pkg_name, constraints_file)) + + self.log(" - constraint %s" % pkg_name, type='I') + + pkg_list = [x.strip() for x in mandatory_field('Package-List').split("\n") if x.strip() != ''] + src_data = [faux_version, + faux_section, + [], + None, + True, + ] + self.sources['testing'][pkg_name] = src_data + self.sources['unstable'][pkg_name] = src_data + for arch in self.options.architectures: + deps = [] + for pkg_spec in pkg_list: + s = pkg_spec.split(None, 2) + if len(s) == 1: + deps.append(s[0]) + else: + pkg, arch_res = s + if not (arch_res.startswith('[') and arch_res.endswith(']')): + raise ValueError("Invalid arch-restriction on %s - should be [arch1 arch2] (for %s file %s)" + % (pkg, pkg_name, constraints_file)) + arch_res = arch_res[1:-1].split() + if not arch_res: + msg = "Empty arch-restriction for %s: Uses comma or negation (for %s file %s)" + raise ValueError(msg % (pkg, pkg_name, constraints_file)) + for a in arch_res: + if a == arch: + deps.append(pkg) + elif ',' in a or '!' in a: + msg = "Invalid arch-restriction for %s: Uses comma or negation (for %s file %s)" + raise ValueError(msg % (pkg, pkg_name, constraints_file)) + pkg_id = (pkg_name, faux_version, arch) + bin_data = BinaryPackage(faux_version, + faux_section, + pkg_name, + faux_version, + arch, + 'no', + ', '.join(deps), + None, + [], + False, + ) + src_data[BINARIES].append(pkg_id) + self.binaries['testing'][arch][0][pkg_name] = bin_data + self.binaries['unstable'][arch][0][pkg_name] = bin_data + self.all_binaries[pkg_id] = bin_data + def _build_installability_tester(self, archs): """Create the installability tester"""