RPackage:move verification to separate method, only when testing

This commit is contained in:
Wouter Deconinck 2024-09-01 19:22:07 -05:00
parent 7314fce7e3
commit 99bfe06ae9

View File

@ -7,12 +7,14 @@
import llnl.util.filesystem as fs
import llnl.util.lang as lang
import llnl.util.tty as tty
import spack.builder
import spack.deptypes as dt
from llnl.util.filesystem import mkdirp
from llnl.util.lang import ClassProperty, classproperty
from spack.dependency import Dependency
from spack.directives import extends
from spack.error import SpackError
from spack.spec import Spec
from .generic import GenericBuilder, Package
@ -103,64 +105,108 @@ def append(field_value: Union[bytes, str]):
# the last parsed package.
if package:
yield package
def install(self, pkg, spec, prefix):
"""Installs an R package."""
mkdirp(pkg.module.r_lib_dir)
try:
# TODO: use a more sustainable dcf parser
import pycran
def verify_package(self):
if not self.pkg.run_tests:
return
# 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()):
if "Imports" in desc:
r_deps.extend([d.strip() for d in desc["Imports"].split(",") if d != ""])
if "Depends" in desc:
r_deps.extend([d.strip() for d in desc["Depends"].split(",") if d != ""])
with open(fs.join_path(self.stage.source_path, "DESCRIPTION")) as file:
for desc in RBuilder.parse_description(file.read()):
for field in [f for f in ["Depends", "Imports", "LinkingTo"] if f in desc]:
r_deps.extend([d.strip() for d in desc[field].split(",") if d != ""])
tty.debug(f"DESCRIPTION: {r_deps}")
# Convert to spack dependencies format for comparison
deps = {}
r_core = [
"r-compiler",
"r-graphics",
"r-grdevices",
"r-grid",
"r-methods",
"r-parallel",
"r-splines",
"r-stats",
"r-stats4",
"r-tcltk",
"r-tools",
"r-utils",
]
for r_dep in r_deps:
p = re.search(r"^[\w_-]+", r_dep) # first word, incl. underscore or dash
p = re.search(r"^[\w_.-]+", r_dep) # first word, incl. underscore, dot, 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"
r_spec = re.sub(r"\.", "-", r_spec) # dot to dash
# filter R core packages
if r_spec in r_core:
r_spec = "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.1:", v) # >
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)
v = ""
# merge dependencies as they are added
if r_spec in deps:
deps[r_spec].merge(Dependency(self.pkg, Spec(r_spec + v), dt.BUILD | dt.RUN))
else:
deps[r_spec] = Dependency(self.pkg, Spec(r_spec + v), dt.BUILD | dt.RUN)
tty.debug(f"Converted: {deps}")
# Retrieve dependencies for current spack package and version
spack_dependencies = []
for when, dep in pkg.dependencies.items():
if spec.satisfies(when):
for when, dep in self.pkg.dependencies.items():
if self.spec.satisfies(when):
spack_dependencies.append(dep)
tty.debug(f"Spack as read: {spack_dependencies}")
merged_dependencies = {}
for dep in spack_dependencies:
for n, d in dep.items():
if d in merged_dependencies:
if n in merged_dependencies:
merged_dependencies[n].merge(d)
else:
merged_dependencies[n] = d
tty.debug(f"Spack merged: {merged_dependencies}")
# For each R dependency, ensure Spack dependency is at least as strong
missing_deps = []
for dep in sorted(deps.keys()):
if dep in merged_dependencies:
if dep in list(merged_dependencies.keys()):
# Spack dependency must satisfy R dependency
if not merged_dependencies[dep].spec.satisfies(deps[dep].spec):
tty.debug(f' depends_on("{deps[dep].spec}", when="@{pkg.version}:", type=("build", "run"))')
missing_deps.append(
f' depends_on("{deps[dep].spec}", type=("build", "run"), when="@{self.pkg.version}:")'
)
# Remove from dict
del merged_dependencies[dep]
else:
tty.debug(f' depends_on("{deps[dep].spec}", when="@{pkg.version}:", type=("build", "run"))')
missing_deps.append(
f' depends_on("{deps[dep].spec}", type=("build", "run"), when="@{self.pkg.version}:")'
)
for dep in merged_dependencies:
if re.match("^r-.*", dep):
missing_deps.append(
f' #depends_on("{merged_dependencies[dep].spec}") not needed anymore'
)
except ImportError:
tty.debug("R package dependency verification requires pycran")
pass
# Raise exception
if len(missing_deps) > 0:
raise SpackError(
"This package requires stricter dependencies than specified:\n\n"
+ "\n".join(missing_deps)
)
spack.builder.run_before("install")(verify_package)
def install(self, pkg, spec, prefix):
"""Installs an R package."""
mkdirp(pkg.module.r_lib_dir)
config_args = self.configure_args()
config_vars = self.configure_vars()