Support passing in tie breaking functions to apt resolver.

This commit is contained in:
Jelmer Vernooij 2021-03-23 15:29:31 +00:00
parent 94e0b4f99d
commit 0b6cc8d8cc
No known key found for this signature in database
GPG key ID: 579C160D4C9E23E8
5 changed files with 154 additions and 290 deletions

View file

@ -26,7 +26,7 @@ from buildlog_consultant.apt import (
from .. import DetailedFailure, UnidentifiedError
from ..session import Session, run_with_tee, get_user
from .file_search import FileSearcher, AptCachedContentsFileSearcher, GENERATED_FILE_SEARCHER, get_package_for_paths
from .file_search import FileSearcher, AptCachedContentsFileSearcher, GENERATED_FILE_SEARCHER, get_packages_for_paths
def run_apt(session: Session, args: List[str], prefix: Optional[List[str]] = None) -> None:
@ -81,10 +81,10 @@ class AptManager(object):
self._apt_cache = apt.Cache(rootdir=self.session.location)
return package in self._apt_cache
def get_package_for_paths(self, paths, regex=False):
def get_packages_for_paths(self, paths, regex=False):
logging.debug("Searching for packages containing %r", paths)
# TODO(jelmer): Make sure we use whatever is configured in self.session
return get_package_for_paths(paths, self.searchers(), regex=regex)
return get_packages_for_paths(paths, self.searchers(), regex=regex)
def missing(self, packages):
root = getattr(self.session, "location", "/")

View file

@ -264,40 +264,14 @@ GENERATED_FILE_SEARCHER = GeneratedFileSearcher(
)
def get_package_for_paths(
def get_packages_for_paths(
paths: List[str], searchers: List[FileSearcher], regex: bool = False
) -> Optional[str]:
candidates: Set[str] = set()
) -> List[str]:
candidates: List[str] = list()
for path in paths:
for searcher in searchers:
candidates.update(searcher.search_files(path, regex=regex))
if candidates:
break
if len(candidates) == 0:
logging.debug("No packages found that contain %r", paths)
return None
if len(candidates) > 1:
logging.warning(
"More than 1 packages found that contain %r: %r", path, candidates
)
# TODO(jelmer): Pick package based on what appears most commonly in
# build-depends{-indep,-arch}
try:
from .udd import UDD
except ModuleNotFoundError:
logging.warning('Unable to import UDD, not ranking by popcon')
return sorted(candidates, key=len)[0]
udd = UDD()
udd.connect()
winner = udd.get_most_popular(candidates)
if winner is None:
logging.warning(
'No relevant popcon information found, not ranking by popcon')
return sorted(candidates, key=len)[0]
logging.info('Picked winner using popcon')
return winner
else:
return candidates.pop()
candidates.extend(searcher.search_files(path, regex=regex))
return candidates
def main(argv):
@ -317,7 +291,8 @@ def main(argv):
main_searcher.load_local()
searchers = [main_searcher, GENERATED_FILE_SEARCHER]
package = get_package_for_paths(args.path, searchers=searchers, regex=args.regex)
packages = get_packages_for_paths(args.path, searchers=searchers, regex=args.regex)
for package in packages:
print(package)

View file

@ -20,6 +20,7 @@ __all__ = [
]
from datetime import datetime
from functools import partial
import logging
import os
import shutil
@ -131,11 +132,12 @@ class CircularDependency(Exception):
class DebianPackagingContext(object):
def __init__(self, tree, subpath, committer, update_changelog):
def __init__(self, tree, subpath, committer, update_changelog, commit_reporter=None):
self.tree = tree
self.subpath = subpath
self.committer = committer
self.update_changelog = update_changelog
self.commit_reporter = commit_reporter
def commit(self, summary: str, update_changelog: Optional[bool] = None) -> bool:
if update_changelog is None:
@ -149,7 +151,8 @@ class DebianPackagingContext(object):
debcommit(self.tree, committer=self.committer, subpath=self.subpath)
else:
self.tree.commit(
message=summary, committer=self.committer, specific_files=[self.subpath]
message=summary, committer=self.committer, specific_files=[self.subpath],
reporter=self.commit_reporter
)
except PointlessCommit:
return False
@ -268,134 +271,36 @@ def add_test_dependency(context, testname, requirement):
)
def targeted_python_versions(tree: Tree, subpath: str) -> Set[str]:
def targeted_python_versions(tree: Tree, subpath: str) -> List[str]:
with tree.get_file(os.path.join(subpath, "debian/control")) as f:
control = Deb822(f)
build_depends = PkgRelation.parse_relations(control.get("Build-Depends", ""))
all_build_deps: Set[str] = set()
for or_deps in build_depends:
all_build_deps.update(or_dep["name"] for or_dep in or_deps)
targeted = set()
if any(x.startswith("pypy") for x in all_build_deps):
targeted.add("pypy")
if any(x.startswith("python-") for x in all_build_deps):
targeted.add("cpython2")
targeted = []
if any(x.startswith("python3-") for x in all_build_deps):
targeted.add("cpython3")
targeted.append("python3")
if any(x.startswith("pypy") for x in all_build_deps):
targeted.append("pypy")
if any(x.startswith("python-") for x in all_build_deps):
targeted.append("python")
return targeted
def fix_missing_python_distribution(error, phase, apt, context): # noqa: C901
targeted = targeted_python_versions(context.tree, context.subpath)
default = not targeted
pypy_pkg = apt.get_package_for_paths(
["/usr/lib/pypy/dist-packages/%s-.*.egg-info" % error.distribution], regex=True
)
if pypy_pkg is None:
pypy_pkg = "pypy-%s" % error.distribution
if not apt.package_exists(pypy_pkg):
pypy_pkg = None
py2_pkg = apt.get_package_for_paths(
["/usr/lib/python2\\.[0-9]/dist-packages/%s-.*.egg-info" % error.distribution],
regex=True,
)
if py2_pkg is None:
py2_pkg = "python-%s" % error.distribution
if not apt.package_exists(py2_pkg):
py2_pkg = None
py3_pkg = apt.get_package_for_paths(
["/usr/lib/python3/dist-packages/%s-.*.egg-info" % error.distribution],
regex=True,
)
if py3_pkg is None:
py3_pkg = "python3-%s" % error.distribution
if not apt.package_exists(py3_pkg):
py3_pkg = None
extra_build_deps = []
if error.python_version == 2:
if "pypy" in targeted:
if not pypy_pkg:
logging.warning("no pypy package found for %s", error.module)
else:
extra_build_deps.append(pypy_pkg)
if "cpython2" in targeted or default:
if not py2_pkg:
logging.warning("no python 2 package found for %s", error.module)
return False
extra_build_deps.append(py2_pkg)
elif error.python_version == 3:
if not py3_pkg:
logging.warning("no python 3 package found for %s", error.module)
return False
extra_build_deps.append(py3_pkg)
else:
if py3_pkg and ("cpython3" in targeted or default):
extra_build_deps.append(py3_pkg)
if py2_pkg and ("cpython2" in targeted or default):
extra_build_deps.append(py2_pkg)
if pypy_pkg and "pypy" in targeted:
extra_build_deps.append(pypy_pkg)
if not extra_build_deps:
return False
for dep_pkg in extra_build_deps:
assert dep_pkg is not None
if not add_dependency(context, phase, dep_pkg):
return False
return True
def fix_missing_python_module(error, phase, apt, context):
targeted = targeted_python_versions(context.tree, context.subpath)
default = not targeted
if error.minimum_version:
specs = [(">=", error.minimum_version)]
else:
specs = []
pypy_pkg = get_package_for_python_module(apt, error.module, "pypy", specs)
py2_pkg = get_package_for_python_module(apt, error.module, "cpython2", specs)
py3_pkg = get_package_for_python_module(apt, error.module, "cpython3", specs)
extra_build_deps = []
if error.python_version == 2:
if "pypy" in targeted:
if not pypy_pkg:
logging.warning("no pypy package found for %s", error.module)
else:
extra_build_deps.append(pypy_pkg)
if "cpython2" in targeted or default:
if not py2_pkg:
logging.warning("no python 2 package found for %s", error.module)
return False
extra_build_deps.append(py2_pkg)
elif error.python_version == 3:
if not py3_pkg:
logging.warning("no python 3 package found for %s", error.module)
return False
extra_build_deps.append(py3_pkg)
else:
if py3_pkg and ("cpython3" in targeted or default):
extra_build_deps.append(py3_pkg)
if py2_pkg and ("cpython2" in targeted or default):
extra_build_deps.append(py2_pkg)
if pypy_pkg and "pypy" in targeted:
extra_build_deps.append(pypy_pkg)
if not extra_build_deps:
return False
for dep_pkg in extra_build_deps:
assert dep_pkg is not None
if not add_dependency(context, phase, dep_pkg):
return False
return True
def python_tie_breaker(tree, subpath, reqs):
targeted = targeted_python_versions(tree, subpath)
if not targeted:
return None
for prefix in targeted:
for req in reqs:
if any(name.startswith(prefix + '-') for name in req.package_names()):
logging.info(
'Breaking tie between %r to %r, since package already '
'has %r build-dependencies', [str(req) for req in reqs],
str(req), prefix)
return req
return None
def retry_apt_failure(error, phase, apt, context):
@ -536,12 +441,34 @@ def versioned_package_fixers(session, packaging_context):
]
def udd_tie_breaker(candidates):
# TODO(jelmer): Pick package based on what appears most commonly in
# build-depends{-indep,-arch}
try:
from .udd import UDD
except ModuleNotFoundError:
logging.warning('Unable to import UDD, not ranking by popcon')
return sorted(candidates, key=len)[0]
udd = UDD()
udd.connect()
names = {list(c.package_names())[0]: c for c in candidates}
winner = udd.get_most_popular(list(names.keys()))
if winner is None:
logging.warning(
'No relevant popcon information found, not ranking by popcon')
return None
logging.info('Picked winner using popcon')
return names[winner]
def apt_fixers(apt, packaging_context) -> List[BuildFixer]:
from ..resolver.apt import AptResolver
resolver = AptResolver(apt)
apt_tie_breakers = [
partial(python_tie_breaker, packaging_context.tree, packaging_context.subpath),
udd_tie_breaker,
]
resolver = AptResolver(apt, apt_tie_breakers)
return [
DependencyBuildFixer(packaging_context, apt, MissingPythonModule, fix_missing_python_module),
DependencyBuildFixer(packaging_context, apt, MissingPythonDistribution, fix_missing_python_distribution),
DependencyBuildFixer(packaging_context, apt, AptFetchFailure, retry_apt_failure),
PackageDependencyFixer(packaging_context, resolver),
]

View file

@ -20,6 +20,7 @@ import logging
import os
import posixpath
import re
from typing import Optional, List
from debian.changelog import Version
from debian.deb822 import PkgRelation
@ -84,14 +85,30 @@ class AptRequirement(Requirement):
def __str__(self):
return "apt requirement: %s" % self.pkg_relation_str()
def touches_package(self, package):
def package_names(self):
for rel in self.relations:
for entry in rel:
if entry["name"] == package:
yield entry["name"]
def touches_package(self, package):
for name in self.package_names():
if name == package:
return True
return False
def find_package_names(apt_mgr: AptManager, paths: List[str], regex: bool = False) -> List[str]:
if not isinstance(paths, list):
raise TypeError(paths)
return apt_mgr.get_packages_for_paths(paths, regex)
def find_reqs_simple(apt_mgr: AptManager, paths: List[str], regex: bool = False, minimum_version=None) -> List[str]:
if not isinstance(paths, list):
raise TypeError(paths)
return [AptRequirement.simple(package, minimum_version=minimum_version) for package in find_package_names(apt_mgr, paths, regex)]
def python_spec_to_apt_rels(pkg_name, specs):
# TODO(jelmer): Dealing with epoch, etc?
if not specs:
@ -133,11 +150,8 @@ def get_package_for_python_package(apt_mgr, package, python_version: Optional[st
paths = [cpython3_regex, cpython2_regex, pypy_regex]
else:
raise NotImplementedError('unsupported python version %d' % python_version)
pkg_name = apt_mgr.get_package_for_paths(paths, regex=True)
if pkg_name is None:
return None
rels = python_spec_to_apt_rels(pkg_name, specs)
return AptRequirement(rels)
names = find_package_names(apt_mgr, paths, regex=True)
return [AptRequirement(python_spec_to_apt_rels(name, specs)) for name in names]
def get_package_for_python_module(apt_mgr, module, python_version, specs):
@ -198,11 +212,8 @@ def get_package_for_python_module(apt_mgr, module, python_version, specs):
paths = cpython3_regexes + cpython2_regexes + pypy_regexes
else:
raise AssertionError("unknown python version %r" % python_version)
pkg_name = apt_mgr.get_package_for_paths(paths, regex=True)
if pkg_name is None:
return None
rels = python_spec_to_apt_rels(pkg_name, specs)
return AptRequirement(rels)
names = find_package_names(apt_mgr, paths, regex=True)
return [AptRequirement(python_spec_to_apt_rels(name, specs)) for name in names]
def resolve_binary_req(apt_mgr, req):
@ -212,61 +223,42 @@ def resolve_binary_req(apt_mgr, req):
paths = [
posixpath.join(dirname, req.binary_name) for dirname in ["/usr/bin", "/bin"]
]
pkg_name = apt_mgr.get_package_for_paths(paths)
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, paths)
def resolve_pkg_config_req(apt_mgr, req):
package = apt_mgr.get_package_for_paths(
[posixpath.join("/usr/lib/pkgconfig", req.module + ".pc")],
)
if package is None:
package = apt_mgr.get_package_for_paths(
names = find_package_names(apt_mgr,
[posixpath.join("/usr/lib", ".*", "pkgconfig", re.escape(req.module) + "\\.pc")],
regex=True,
)
if package is not None:
return AptRequirement.simple(package, minimum_version=req.minimum_version)
return None
regex=True)
if not names:
names = find_package_names(
apt_mgr, [posixpath.join("/usr/lib/pkgconfig", req.module + ".pc")])
return [AptRequirement.simple(name, minimum_version=req.minimum_version) for name in names]
def resolve_path_req(apt_mgr, req):
package = apt_mgr.get_package_for_paths([req.path])
if package is not None:
return AptRequirement.simple(package)
return None
return find_reqs_simple(apt_mgr, [req.path])
def resolve_c_header_req(apt_mgr, req):
package = apt_mgr.get_package_for_paths(
[posixpath.join("/usr/include", req.header)], regex=False
)
if package is None:
package = apt_mgr.get_package_for_paths(
reqs = find_reqs_simple(
apt_mgr,
[posixpath.join("/usr/include", req.header)], regex=False)
if not reqs:
reqs = find_package_names(
apt_mgr,
[posixpath.join("/usr/include", ".*", re.escape(req.header))], regex=True
)
if package is None:
return None
return AptRequirement.simple(package)
return reqs
def resolve_js_runtime_req(apt_mgr, req):
package = apt_mgr.get_package_for_paths(
["/usr/bin/node", "/usr/bin/duk"], regex=False
)
if package is not None:
return AptRequirement.simple(package)
return None
return find_reqs_simple(apt_mgr, ["/usr/bin/node", "/usr/bin/duk"])
def resolve_vala_package_req(apt_mgr, req):
path = "/usr/share/vala-[0-9.]+/vapi/%s\\.vapi" % re.escape(req.package)
package = apt_mgr.get_package_for_paths([path], regex=True)
if package is not None:
return AptRequirement.simple(package)
return None
return find_reqs_simple(apt_mgr, [path], regex=True)
def resolve_ruby_gem_req(apt_mgr, req):
@ -276,43 +268,29 @@ def resolve_ruby_gem_req(apt_mgr, req):
"specifications/%s-.*\\.gemspec" % re.escape(req.gem)
)
]
package = apt_mgr.get_package_for_paths(paths, regex=True)
if package is not None:
return AptRequirement.simple(package, minimum_version=req.minimum_version)
return None
return find_reqs_simple(apt_mgr, paths, regex=True, minimum_version=req.minimum_version)
def resolve_go_package_req(apt_mgr, req):
package = apt_mgr.get_package_for_paths(
return find_reqs_simple(
apt_mgr,
[posixpath.join("/usr/share/gocode/src", re.escape(req.package), ".*")], regex=True
)
if package is not None:
return AptRequirement.simple(package)
return None
def resolve_dh_addon_req(apt_mgr, req):
paths = [posixpath.join("/usr/share/perl5", req.path)]
package = apt_mgr.get_package_for_paths(paths)
if package is not None:
return AptRequirement.simple(package)
return None
return find_reqs_simple(apt_mgr, paths)
def resolve_php_class_req(apt_mgr, req):
path = "/usr/share/php/%s.php" % req.php_class.replace("\\", "/")
package = apt_mgr.get_package_for_paths([path])
if package is not None:
return AptRequirement.simple(package)
return None
return find_reqs_simple(apt_mgr, [path])
def resolve_r_package_req(apt_mgr, req):
paths = [posixpath.join("/usr/lib/R/site-library/.*/R/%s$" % re.escape(req.package))]
package = apt_mgr.get_package_for_paths(paths, regex=True)
if package is not None:
return AptRequirement.simple(package)
return None
return find_reqs_simple(apt_mgr, paths, regex=True)
def resolve_node_package_req(apt_mgr, req):
@ -321,10 +299,7 @@ def resolve_node_package_req(apt_mgr, req):
"/usr/lib/nodejs/%s/package\\.json" % re.escape(req.package),
"/usr/share/nodejs/%s/package\\.json" % re.escape(req.package),
]
pkg_name = apt_mgr.get_package_for_paths(paths, regex=True)
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, paths, regex=True)
def resolve_library_req(apt_mgr, req):
@ -334,27 +309,21 @@ def resolve_library_req(apt_mgr, req):
posixpath.join("/usr/lib/lib%s.a$" % re.escape(req.library)),
posixpath.join("/usr/lib/.*/lib%s.a$" % re.escape(req.library)),
]
pkg_name = apt_mgr.get_package_for_paths(paths, regex=True)
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, paths)
def resolve_ruby_file_req(apt_mgr, req):
paths = [posixpath.join("/usr/lib/ruby/vendor_ruby/%s.rb" % req.filename)]
package = apt_mgr.get_package_for_paths(paths)
if package is not None:
return AptRequirement.simple(package)
reqs = find_reqs_simple(apt_mgr, paths, regex=False)
if reqs:
return reqs
paths = [
posixpath.join(
r"/usr/share/rubygems-integration/all/gems/([^/]+)/"
"lib/%s\\.rb" % re.escape(req.filename)
)
]
pkg_name = apt_mgr.get_package_for_paths(paths, regex=True)
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, paths, regex=True)
def resolve_xml_entity_req(apt_mgr, req):
@ -370,10 +339,7 @@ def resolve_xml_entity_req(apt_mgr, req):
else:
return None
pkg_name = apt_mgr.get_package_for_paths([search_path], regex=False)
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, [search_path], regex=False)
def resolve_sprockets_file_req(apt_mgr, req):
@ -382,10 +348,7 @@ def resolve_sprockets_file_req(apt_mgr, req):
else:
logging.warning("unable to handle content type %s", req.content_type)
return None
pkg_name = apt_mgr.get_package_for_paths([path], regex=True)
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, [path], regex=True)
def resolve_java_class_req(apt_mgr, req):
@ -399,19 +362,12 @@ def resolve_java_class_req(apt_mgr, req):
logging.warning("unable to find classpath for %s", req.classname)
return False
logging.info("Classpath for %s: %r", req.classname, classpath)
package = apt_mgr.get_package_for_paths(classpath)
if package is None:
logging.warning("no package for files in %r", classpath)
return None
return AptRequirement.simple(package)
return find_reqs_simple(apt_mgr, [classpath])
def resolve_haskell_package_req(apt_mgr, req):
path = "/var/lib/ghc/package\\.conf\\.d/%s-.*\\.conf" % re.escape(req.deps[0][0])
pkg_name = apt_mgr.get_package_for_paths([path], regex=True)
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, [path], regex=True)
def resolve_maven_artifact_req(apt_mgr, req):
@ -440,10 +396,7 @@ def resolve_maven_artifact_req(apt_mgr, req):
"%s-%s.%s" % (artifact_id, version, kind),
)
]
pkg_name = apt_mgr.get_package_for_paths(paths, regex=regex)
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, paths, regex=regex)
def resolve_gnome_common_req(apt_mgr, req):
@ -452,10 +405,7 @@ def resolve_gnome_common_req(apt_mgr, req):
def resolve_jdk_file_req(apt_mgr, req):
path = re.escape(req.jdk_path) + ".*/" + re.escape(req.filename)
pkg_name = apt_mgr.get_package_for_paths([path], regex=True)
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, [path], regex=True)
def resolve_jdk_req(apt_mgr, req):
@ -478,17 +428,11 @@ def resolve_perl_module_req(apt_mgr, req):
paths = [req.filename]
else:
paths = [posixpath.join(inc, req.filename) for inc in req.inc]
pkg_name = apt_mgr.get_package_for_paths(paths, regex=False)
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, paths, regex=False)
def resolve_perl_file_req(apt_mgr, req):
pkg_name = apt_mgr.get_package_for_paths([req.filename], regex=False)
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, [req.filename], regex=False)
def _find_aclocal_fun(macro):
@ -512,10 +456,7 @@ def resolve_autoconf_macro_req(apt_mgr, req):
except KeyError:
logging.info("No local m4 file found defining %s", req.macro)
return None
pkg_name = apt_mgr.get_package_for_paths([path])
if pkg_name is not None:
return AptRequirement.simple(pkg_name)
return None
return find_reqs_simple(apt_mgr, [path])
def resolve_python_module_req(apt_mgr, req):
@ -547,10 +488,7 @@ def resolve_python_package_req(apt_mgr, req):
def resolve_cargo_crate_req(apt_mgr, req):
paths = [
'/usr/share/cargo/registry/%s-[0-9]+.*/Cargo.toml' % re.escape(req.crate)]
pkg_name = apt_mgr.get_package_for_paths(paths, regex=True)
if pkg_name is None:
return None
return AptRequirement.simple(pkg_name)
return find_reqs_simple(apt_mgr, paths, regex=True)
def resolve_ca_req(apt_mgr, req):
@ -591,16 +529,24 @@ APT_REQUIREMENT_RESOLVERS = [
]
def resolve_requirement_apt(apt_mgr, req: Requirement) -> AptRequirement:
def resolve_requirement_apt(apt_mgr, req: Requirement) -> List[AptRequirement]:
for rr_class, rr_fn in APT_REQUIREMENT_RESOLVERS:
if isinstance(req, rr_class):
return rr_fn(apt_mgr, req)
ret = rr_fn(apt_mgr, req)
if not ret:
return []
if not isinstance(ret, list):
raise TypeError(ret)
return ret
raise NotImplementedError(type(req))
class AptResolver(Resolver):
def __init__(self, apt):
def __init__(self, apt, tie_breakers=None):
self.apt = apt
if tie_breakers is None:
tie_breakers = []
self.tie_breakers = tie_breakers
def __str__(self):
return "apt"
@ -647,4 +593,18 @@ class AptResolver(Resolver):
yield (self.apt.satisfy_command([PkgRelation.str(chain(*[r.relations for o, r in apt_requirements]))]), [o for o, r in apt_requirements])
def resolve(self, req: Requirement):
return resolve_requirement_apt(self.apt, req)
ret = resolve_requirement_apt(self.apt, req)
if not ret:
return None
if len(ret) == 1:
return ret[0]
for tie_breaker in self.tie_breakers:
winner = tie_breaker(ret)
if winner is not None:
if not isinstance(winner, AptRequirement):
raise TypeError(winner)
return winner
logging.info(
'Unable to break tie over %r, picking first: %r',
ret, ret[0])
return ret[0]

View file

@ -37,6 +37,7 @@ from ..debian.fix_build import (
apt_fixers,
DebianPackagingContext,
)
from breezy.commit import NullCommitReporter
from breezy.tests import TestCaseWithTransport
@ -99,7 +100,8 @@ blah (0.1) UNRELEASED; urgency=medium
apt._searchers = [DummyAptSearcher(self._apt_files)]
context = DebianPackagingContext(
self.tree, subpath="", committer="ognibuild <ognibuild@jelmer.uk>",
update_changelog=True)
update_changelog=True,
commit_reporter=NullCommitReporter())
fixers = versioned_package_fixers(session, context) + apt_fixers(apt, context)
return resolve_error(error, ("build", ), fixers)