RPackage: parse description for incomplete dependencies

This commit is contained in:
Wouter Deconinck 2024-08-22 15:43:08 -05:00
parent eaabde6ee9
commit 0cd97d584e

View File

@ -1,12 +1,19 @@
# Copyright Spack Project Developers. See COPYRIGHT file for details. # Copyright Spack Project Developers. See COPYRIGHT file for details.
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import re
from typing import Optional, Tuple from typing import Optional, Tuple
import llnl.util.filesystem as fs
import llnl.util.lang as lang
import llnl.util.tty as tty
import spack.deptypes as dt
from llnl.util.filesystem import mkdirp from llnl.util.filesystem import mkdirp
from llnl.util.lang import ClassProperty, classproperty from llnl.util.lang import ClassProperty, classproperty
from spack.dependency import Dependency
from spack.directives import extends from spack.directives import extends
from spack.spec import Spec
from .generic import GenericBuilder, Package from .generic import GenericBuilder, Package
@ -38,6 +45,59 @@ def install(self, pkg, spec, prefix):
"""Installs an R package.""" """Installs an R package."""
mkdirp(pkg.module.r_lib_dir) mkdirp(pkg.module.r_lib_dir)
try:
# TODO: use a more sustainable dcf parser
import pycran
# Read DESCRIPTION file with dependency information
# https://r-pkgs.org/description.html
r_deps = []
with open(fs.join_path(self.stage.source_path, 'DESCRIPTION')) as file:
for desc in pycran.parse(file.read()):
r_deps.extend([d.strip() for d in desc.get("Imports", None).split(",")])
r_deps.extend([d.strip() for d in desc.get("Depends", None).split(",")])
# Convert to spack dependencies format for comparison
deps = {}
for r_dep in r_deps:
p = re.search(r"^[\w_-]+", r_dep) # first word, incl. underscore or dash
v = re.search("(?<=[(]).*(?=[)])", r_dep) # everything between parentheses
# require valid package
assert(p, f"Unable to find package name in {r_dep}")
r_spec = f"r-{p[0].strip().lower()}" if p[0].lower() != "r" else "r"
# allow minimum or pinned versions
if v:
v = re.sub(r">=\s([\d.-]+)", r"@\1:", v[0]) # >=
v = re.sub(r"==\s([\d.-]+)", r"@\1", v) # ==
deps[r_spec] = Dependency(pkg, Spec(r_spec + v), dt.BUILD | dt.RUN)
else:
deps[r_spec] = Dependency(pkg, Spec(r_spec), dt.BUILD | dt.RUN)
# Retrieve dependencies for current spack package and version
spack_dependencies = []
for when, dep in pkg.dependencies.items():
if spec.satisfies(when):
spack_dependencies.append(dep)
merged_dependencies = {}
for dep in spack_dependencies:
for n, d in dep.items():
if d in merged_dependencies:
merged_dependencies[n].merge(d)
else:
merged_dependencies[n] = d
# For each R dependency, ensure Spack dependency is at least as strong
for dep in sorted(deps.keys()):
if dep in merged_dependencies:
if not merged_dependencies[dep].spec.satisfies(deps[dep].spec):
tty.debug(f' depends_on("{deps[dep].spec}", when="@{pkg.version}:", type=("build", "run"))')
else:
tty.debug(f' depends_on("{deps[dep].spec}", when="@{pkg.version}:", type=("build", "run"))')
except ImportError:
tty.debug("R package dependency verification requires pycran")
pass
config_args = self.configure_args() config_args = self.configure_args()
config_vars = self.configure_vars() config_vars = self.configure_vars()