diff --git a/ognibuild/__main__.py b/ognibuild/__main__.py index f7b61f7..f253b97 100644 --- a/ognibuild/__main__.py +++ b/ognibuild/__main__.py @@ -38,11 +38,17 @@ def get_necessary_declared_requirements(resolver, requirements, stages): def install_necessary_declared_requirements(resolver, buildsystem, stages): missing = [] - missing.extend( - get_necessary_declared_requirements( - resolver, buildsystem.get_declared_dependencies(), stages + try: + declared_reqs = buildsystem.get_declared_dependencies() + except NotImplementedError: + logging.warning( + 'Unable to determine declared dependencies from %s', buildsystem) + else: + missing.extend( + get_necessary_declared_requirements( + resolver, declared_reqs, stages + ) ) - ) resolver.install(missing) diff --git a/ognibuild/buildlog.py b/ognibuild/buildlog.py index ae358d5..b2a90db 100644 --- a/ognibuild/buildlog.py +++ b/ognibuild/buildlog.py @@ -124,11 +124,10 @@ def problem_to_upstream_requirement(problem): elif isinstance(problem, MissingJavaClass): return JavaClassRequirement(problem.classname) elif isinstance(problem, MissingHaskellDependencies): - # TODO(jelmer): Create multiple HaskellPackageRequirement objects? - return HaskellPackageRequirement(problem.package) + return [HaskellPackageRequirement(dep) for dep in problem.deps] elif isinstance(problem, MissingMavenArtifacts): - # TODO(jelmer): Create multiple MavenArtifactRequirement objects? - return MavenArtifactRequirement(problem.artifacts) + return [MavenArtifactRequirement(artifact) + for artifact in problem.artifacts] elif isinstance(problem, MissingCSharpCompiler): return BinaryRequirement('msc') elif isinstance(problem, GnomeCommonMissing): @@ -179,16 +178,29 @@ class UpstreamRequirementFixer(BuildFixer): def __init__(self, resolver): self.resolver = resolver + def __repr__(self): + return "%s(%r)" % (type(self).__name__, self.resolver) + + def __str__(self): + return "upstream requirement fixer(%s)" % self.resolver + def can_fix(self, error): req = problem_to_upstream_requirement(error) return req is not None def fix(self, error, context): - req = problem_to_upstream_requirement(error) - if req is None: + reqs = problem_to_upstream_requirement(error) + if reqs is None: return False - package = self.resolver.resolve(req) - if package is None: - return False - return context.add_dependency(package) + if not isinstance(reqs, list): + reqs = [reqs] + + changed = False + for req in reqs: + package = self.resolver.resolve(reqs) + if package is None: + return False + if context.add_dependency(package): + changed = True + return changed diff --git a/ognibuild/buildsystem.py b/ognibuild/buildsystem.py index 5304573..60309e6 100644 --- a/ognibuild/buildsystem.py +++ b/ognibuild/buildsystem.py @@ -51,6 +51,9 @@ class BuildSystem(object): name: str + def __str__(self): + return self.name + def dist(self, session, resolver, fixers): raise NotImplementedError(self.dist) @@ -125,13 +128,13 @@ class SetupPy(BuildSystem): except FileNotFoundError: setup_cfg_contents = "" if "setuptools" in setup_py_contents: - logging.info("Reference to setuptools found, installing.") + logging.debug("Reference to setuptools found, installing.") resolver.install([PythonPackageRequirement("setuptools")]) if ( "setuptools_scm" in setup_py_contents or "setuptools_scm" in setup_cfg_contents ): - logging.info("Reference to setuptools-scm found, installing.") + logging.debug("Reference to setuptools-scm found, installing.") resolver.install( [ PythonPackageRequirement("setuptools-scm"), @@ -215,8 +218,9 @@ class PyProject(BuildSystem): def dist(self, session, resolver, fixers): if "poetry" in self.pyproject.get("tool", []): - logging.info( - "Found pyproject.toml with poetry section, " "assuming poetry project." + logging.debug( + "Found pyproject.toml with poetry section, " + "assuming poetry project." ) resolver.install( [ @@ -279,13 +283,17 @@ class Waf(BuildSystem): def __init__(self, path): self.path = path - def setup(self, resolver): + def setup(self, session, resolver, fixers): resolver.install([BinaryRequirement("python3")]) def dist(self, session, resolver, fixers): - self.setup(resolver) + self.setup(session, resolver, fixers) run_with_build_fixers(session, ["./waf", "dist"], fixers) + def test(self, session, resolver, fixers): + self.setup(session, resolver, fixers) + run_with_build_fixers(session, ["./waf", "test"], fixers) + class Gem(BuildSystem): @@ -321,13 +329,14 @@ class DistInkt(BuildSystem): except ValueError: continue if key.strip() == b"class" and value.strip().startswith(b"'Dist::Inkt"): - logging.info( - "Found Dist::Inkt section in dist.ini, " "assuming distinkt." + logging.debug( + "Found Dist::Inkt section in dist.ini, " + "assuming distinkt." ) self.name = "dist-inkt" self.dist_inkt_class = value.decode().strip("'") return - logging.info("Found dist.ini, assuming dist-zilla.") + logging.debug("Found dist.ini, assuming dist-zilla.") def setup(self, resolver): resolver.install( @@ -397,15 +406,19 @@ class Make(BuildSystem): session.check_call(["./configure"]) def build(self, session, resolver, fixers): - self.setup(session, resolver) + self.setup(session, resolver, fixers) run_with_build_fixers(session, ["make", "all"], fixers) + def test(self, session, resolver, fixers): + self.setup(session, resolver, fixers) + run_with_build_fixers(session, ["make", "check"], fixers) + def install(self, session, resolver, fixers, install_target): - self.setup(session, resolver) + self.setup(session, resolver, fixers) run_with_build_fixers(session, ["make", "install"], fixers) def dist(self, session, resolver, fixers): - self.setup(session, resolver) + self.setup(session, resolver, fixers) try: run_with_build_fixers(session, ["make", "dist"], fixers) except UnidentifiedError as e: @@ -491,6 +504,9 @@ class Cargo(BuildSystem): # TODO(jelmer): Look at details['features'], details['version'] yield "build", CargoCrateRequirement(name) + def test(self, session, resolver, fixers): + run_with_build_fixers(session, ["cargo", "test"], fixers) + class Golang(BuildSystem): """Go builds.""" @@ -513,37 +529,59 @@ class Cabal(BuildSystem): def __init__(self, path): self.path = path + def __repr__(self): + return "%s(%r)" % (type(self).__name__, self.path) + + def _run(self, session, args, fixers): + try: + run_with_build_fixers( + session, ["runhaskell", "Setup.hs"] + args, fixers) + except UnidentifiedError as e: + if "Run the 'configure' command first.\n" in e.lines: + run_with_build_fixers( + session, ["runhaskell", "Setup.hs", "configure"], fixers) + run_with_build_fixers( + session, ["runhaskell", "Setup.hs"] + args, fixers) + else: + raise + + def test(self, session, resolver, fixers): + self._run(session, ["test"], fixers) def detect_buildsystems(path, trust_package=False): # noqa: C901 """Detect build systems.""" if os.path.exists(os.path.join(path, "package.xml")): - logging.info("Found package.xml, assuming pear package.") + logging.debug("Found package.xml, assuming pear package.") yield Pear("package.xml") if os.path.exists(os.path.join(path, "setup.py")): - logging.info("Found setup.py, assuming python project.") + logging.debug("Found setup.py, assuming python project.") yield SetupPy("setup.py") elif os.path.exists(os.path.join(path, "pyproject.toml")): - logging.info("Found pyproject.toml, assuming python project.") + logging.debug("Found pyproject.toml, assuming python project.") yield PyProject("pyproject.toml") elif os.path.exists(os.path.join(path, "setup.cfg")): - logging.info("Found setup.cfg, assuming python project.") + logging.debug("Found setup.cfg, assuming python project.") yield SetupCfg("setup.cfg") if os.path.exists(os.path.join(path, "package.json")): - logging.info("Found package.json, assuming node package.") + logging.debug("Found package.json, assuming node package.") yield Npm("package.json") if os.path.exists(os.path.join(path, "waf")): - logging.info("Found waf, assuming waf package.") + logging.debug("Found waf, assuming waf package.") yield Waf("waf") if os.path.exists(os.path.join(path, "Cargo.toml")): - logging.info("Found Cargo.toml, assuming rust cargo package.") + logging.debug("Found Cargo.toml, assuming rust cargo package.") yield Cargo("Cargo.toml") + if os.path.exists(os.path.join(path, 'Setup.hs')): + logging.debug("Found Setup.hs, assuming haskell package.") + yield Cabal('Setup.hs') + if os.path.exists(os.path.join(path, "pom.xml")): - logging.info("Found pom.xml, assuming maven package.") + logging.debug("Found pom.xml, assuming maven package.") yield Maven("pom.xml") if os.path.exists(os.path.join(path, "dist.ini")) and not os.path.exists( @@ -569,17 +607,6 @@ def detect_buildsystems(path, trust_package=False): # noqa: C901 ): yield Make() - cabal_filenames = [ - entry.name for entry in os.scandir(path) if entry.name.endswith(".cabal") - ] - if cabal_filenames: - if len(cabal_filenames) == 1: - yield Cabal(cabal_filenames[0]) - else: - warnings.warn( - "More than one cabal filename, ignoring all: %r" % cabal_filenames - ) - if os.path.exists(os.path.join(path, ".travis.yml")): import ruamel.yaml.reader diff --git a/ognibuild/fix_build.py b/ognibuild/fix_build.py index 5520e31..d46016d 100644 --- a/ognibuild/fix_build.py +++ b/ognibuild/fix_build.py @@ -120,7 +120,7 @@ def resolve_error(error, context, fixers): logging.warning("No fixer found for %r", error) return False for fixer in relevant_fixers: - logging.info("Attempting to use fixer %r to address %r", fixer, error) + logging.info("Attempting to use fixer %s to address %r", fixer, error) made_changes = fixer.fix(error, context) if made_changes: return True diff --git a/ognibuild/resolver/__init__.py b/ognibuild/resolver/__init__.py index 93074ab..6dc48cc 100644 --- a/ognibuild/resolver/__init__.py +++ b/ognibuild/resolver/__init__.py @@ -60,8 +60,30 @@ class CPANResolver(Resolver): def explain(self, requirements): raise NotImplementedError(self.explain) - def met(self, requirement): - raise NotImplementedError(self.met) + +class HackageResolver(Resolver): + + def __init__(self, session): + self.session = session + + def __str__(self): + return "hackage" + + def install(self, requirements): + from ..requirements import HaskellPackageRequirement + missing = [] + for requirement in requirements: + if not isinstance(requirement, HaskellPackageRequirement): + missing.append(requirement) + continue + self.session.check_call( + ["cabal", "install", requirement.package], + user="root") + if missing: + raise UnsatisfiedRequirements(missing) + + def explain(self, requirements): + raise NotImplementedError(self.explain) class CargoResolver(Resolver): @@ -88,9 +110,6 @@ class CargoResolver(Resolver): def explain(self, requirements): raise NotImplementedError(self.explain) - def met(self, requirement): - raise NotImplementedError(self.met) - class PypiResolver(Resolver): @@ -114,9 +133,6 @@ class PypiResolver(Resolver): def explain(self, requirements): raise NotImplementedError(self.explain) - def met(self, requirement): - raise NotImplementedError(self.met) - NPM_COMMAND_PACKAGES = { "del-cli": "del-cli", @@ -150,14 +166,14 @@ class NpmResolver(Resolver): def explain(self, requirements): raise NotImplementedError(self.explain) - def met(self, requirement): - raise NotImplementedError(self.met) - class StackedResolver(Resolver): def __init__(self, subs): self.subs = subs + def __repr__(self): + return "%s(%r)" % (type(self).__name__, self.subs) + def __str__(self): return "[" + ", ".join(map(str, self.subs)) + "]" @@ -176,7 +192,8 @@ def native_resolvers(session): CPANResolver(session), PypiResolver(session), NpmResolver(session), - CargoResolver(session)]) + CargoResolver(session), + HackageResolver(session)]) class ExplainResolver(Resolver): @@ -203,5 +220,6 @@ def auto_resolver(session): CPANResolver(session), PypiResolver(session), NpmResolver(session), - CargoResolver(session)]) + CargoResolver(session), + HackageResolver(session)]) return StackedResolver(resolvers)