diff --git a/ognibuild/buildsystem.py b/ognibuild/buildsystem.py index 8ffd75b..df8320c 100644 --- a/ognibuild/buildsystem.py +++ b/ognibuild/buildsystem.py @@ -36,6 +36,7 @@ from .requirements import ( NodePackageRequirement, CargoCrateRequirement, RPackageRequirement, + OctavePackageRequirement, ) from .fix_build import run_with_build_fixers @@ -453,9 +454,7 @@ class Octave(BuildSystem): @classmethod def exists(cls, path): - if not ( - os.path.exists(os.path.join(path, "DESCRIPTION")) and - os.path.exists(os.path.join(path, "COPYING"))): + if not os.path.exists(os.path.join(path, "DESCRIPTION")): return False # Urgh, isn't there a better way to see if this is an octave package? for entry in os.scandir(path): @@ -471,9 +470,23 @@ class Octave(BuildSystem): @classmethod def probe(cls, path): if cls.exists(path): - logging.debug("Found DESCRIPTION and COPYING, assuming octave package.") + logging.debug("Found DESCRIPTION, assuming octave package.") return cls(path) + def _read_description(self): + path = os.path.join(self.path, 'DESCRIPTION') + from email.parser import BytesParser + with open(path, 'rb') as f: + return BytesParser().parse(f) + + def get_declared_dependencies(self, session, fixers=None): + def parse_list(t): + return [s.strip() for s in t.split(',') if s.strip()] + description = self._read_description() + if 'Depends' in description: + for s in parse_list(description['Depends']): + yield "build", OctavePackageRequirement.from_str(s) + class Gradle(BuildSystem): diff --git a/ognibuild/requirements.py b/ognibuild/requirements.py index 8b6a0b1..51fba07 100644 --- a/ognibuild/requirements.py +++ b/ognibuild/requirements.py @@ -278,6 +278,41 @@ class RPackageRequirement(Requirement): raise ValueError(text) +class OctavePackageRequirement(Requirement): + + package: str + minimum_version: Optional[str] + + def __init__(self, package: str, minimum_version: Optional[str] = None): + super(OctavePackageRequirement, self).__init__("octave-package") + self.package = package + self.minimum_version = minimum_version + + def __repr__(self): + return "%s(%r, minimum_version=%r)" % ( + type(self).__name__, + self.package, + self.minimum_version, + ) + + def __str__(self): + if self.minimum_version: + return "Octave package: %s (>= %s)" % (self.package, self.minimum_version) + else: + return "Octave package: %s" % (self.package,) + + @classmethod + def from_str(cls, text): + # TODO(jelmer): More complex parser + m = re.fullmatch(r'(.*) \(>= (.*)\)', text) + if m: + return cls(m.group(1), m.group(2)) + m = re.fullmatch(r'([^ ]+)', text) + if m: + return cls(m.group(1)) + raise ValueError(text) + + class LibraryRequirement(Requirement): library: str diff --git a/ognibuild/resolver/__init__.py b/ognibuild/resolver/__init__.py index 7e03cf3..790dd79 100644 --- a/ognibuild/resolver/__init__.py +++ b/ognibuild/resolver/__init__.py @@ -132,6 +132,50 @@ class RResolver(Resolver): raise UnsatisfiedRequirements(missing) +class OctaveForgeResolver(Resolver): + def __init__(self, session, user_local=False): + self.session = session + self.user_local = user_local + + def __str__(self): + return "octave-forge" + + def __repr__(self): + return "%s(%r)" % (type(self).__name__, self.session) + + def _cmd(self, req): + # TODO(jelmer: Handle self.user_local + return ["octave-cli", "--eval", "pkg install -forge %s" % req.package] + + def explain(self, requirements): + from ..requirements import OctavePackageRequirement + + rreqs = [] + for requirement in requirements: + if not isinstance(requirement, OctavePackageRequirement): + continue + rreqs.append(requirement) + if rreqs: + yield ([self._cmd(req) for req in rreqs]) + + def install(self, requirements): + from ..requirements import OctavePackageRequirement + + if self.user_local: + user = None + else: + user = "root" + + missing = [] + for requirement in requirements: + if not isinstance(requirement, OctavePackageRequirement): + missing.append(requirement) + continue + self.session.check_call(self._cmd(requirement), user=user) + if missing: + raise UnsatisfiedRequirements(missing) + + class CRANResolver(RResolver): def __init__(self, session, user_local=False): @@ -362,6 +406,7 @@ NATIVE_RESOLVER_CLS = [ HackageResolver, CRANResolver, BioconductorResolver, + OctaveForgeResolver, ]