mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
Harden optional science toolbelt probe
This commit is contained in:
parent
c896e66670
commit
5ff6163087
4 changed files with 134 additions and 17 deletions
|
|
@ -22,9 +22,16 @@ Equivalent npm convenience commands:
|
|||
|
||||
```bash
|
||||
npm run setup-science-light
|
||||
npm run setup-science-all
|
||||
npm run probe-science
|
||||
```
|
||||
|
||||
`setup-science-light` installs the pure Python and wheel-backed baseline.
|
||||
`setup-science-all` also asks for Dedalus and `liboqs-python`; those packages
|
||||
still need native libraries on the host. On Arch/CachyOS, Dedalus needed
|
||||
`fftw-openmpi`, and `liboqs-python` was completed by installing liboqs under
|
||||
`$HOME/_oqs`.
|
||||
|
||||
Native command-line tools are listed in:
|
||||
|
||||
```text
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"setup-cad-env": "cd 5-Applications/text-to-cad && python3.11 -m venv .venv && ./.venv/bin/pip install -r requirements-cad.txt",
|
||||
"verify-cad": "cd 5-Applications/text-to-cad && ./.venv/bin/python -c \"import build123d; import OCP; print('CAD dependencies OK')\"",
|
||||
"setup-science-light": "uv venv .venv-science && . .venv-science/bin/activate && uv pip install -r requirements-optional-science.txt",
|
||||
"probe-science": "python3 scripts/probe_science_toolbelt.py"
|
||||
"setup-science-all": "uv venv --python 3.11 .venv-science && . .venv-science/bin/activate && uv pip install -r requirements-optional-science.txt 'dedalus>=3.0.5' 'liboqs-python>=0.14.1'",
|
||||
"probe-science": "sh -c 'if [ -x .venv-science/bin/python ]; then .venv-science/bin/python scripts/probe_science_toolbelt.py; else python3 scripts/probe_science_toolbelt.py; fi'"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^12.4.1"
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ Exit code:
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib
|
||||
import importlib.metadata
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -115,34 +116,136 @@ COMMAND_TOOLS: tuple[CommandTool, ...] = (
|
|||
)
|
||||
|
||||
|
||||
def _module_version(module: Any) -> str | None:
|
||||
for attr in ("__version__", "VERSION", "version"):
|
||||
value = getattr(module, attr, None)
|
||||
if value is None:
|
||||
continue
|
||||
if callable(value):
|
||||
try:
|
||||
value = value()
|
||||
except Exception:
|
||||
continue
|
||||
return str(value)
|
||||
return None
|
||||
_DISTRIBUTION_NAMES: dict[str, str] = {
|
||||
"pywavelets": "PyWavelets",
|
||||
}
|
||||
|
||||
|
||||
def _distribution_version(tool: PythonTool) -> str | None:
|
||||
name = _DISTRIBUTION_NAMES.get(tool.key, tool.key)
|
||||
try:
|
||||
return importlib.metadata.version(name)
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
return None
|
||||
|
||||
|
||||
def _first_line(text: str) -> str:
|
||||
for line in text.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped:
|
||||
return stripped
|
||||
return text.strip()
|
||||
|
||||
|
||||
def _liboqs_env() -> dict[str, str]:
|
||||
env = dict(os.environ)
|
||||
if "OQS_INSTALL_PATH" in env:
|
||||
return env
|
||||
|
||||
home_install = Path.home() / "_oqs"
|
||||
if (home_install / "lib" / "liboqs.so").exists() or (
|
||||
home_install / "lib64" / "liboqs.so"
|
||||
).exists():
|
||||
env["OQS_INSTALL_PATH"] = str(home_install)
|
||||
return env
|
||||
|
||||
|
||||
def _has_native_liboqs() -> bool:
|
||||
candidates = [
|
||||
Path(os.environ["OQS_INSTALL_PATH"])
|
||||
for key in ("OQS_INSTALL_PATH",)
|
||||
if key in os.environ
|
||||
]
|
||||
candidates.append(Path.home() / "_oqs")
|
||||
for root in candidates:
|
||||
if (root / "lib" / "liboqs.so").exists() or (
|
||||
root / "lib64" / "liboqs.so"
|
||||
).exists():
|
||||
return True
|
||||
for pattern in ("/usr/lib/liboqs.so*", "/usr/local/lib/liboqs.so*"):
|
||||
if list(Path("/").glob(pattern.lstrip("/"))):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _probe_python(tool: PythonTool) -> dict[str, Any]:
|
||||
installed_version = _distribution_version(tool)
|
||||
if installed_version is None:
|
||||
return {
|
||||
**asdict(tool),
|
||||
"available": False,
|
||||
"error": "package is not installed in this Python environment",
|
||||
}
|
||||
|
||||
if tool.key == "liboqs-python" and not _has_native_liboqs():
|
||||
return {
|
||||
**asdict(tool),
|
||||
"available": False,
|
||||
"installed_version": installed_version,
|
||||
"error": "Python package is installed, but native liboqs.so is missing",
|
||||
}
|
||||
|
||||
marker = "__SCIENCE_TOOLBELT_PROBE__"
|
||||
code = f"""
|
||||
import importlib
|
||||
import json
|
||||
|
||||
module = importlib.import_module({tool.module!r})
|
||||
version = None
|
||||
for attr in ("__version__", "VERSION", "version"):
|
||||
value = getattr(module, attr, None)
|
||||
if value is None:
|
||||
continue
|
||||
if callable(value):
|
||||
try:
|
||||
value = value()
|
||||
except Exception:
|
||||
continue
|
||||
version = str(value)
|
||||
break
|
||||
print({marker!r} + json.dumps({{"version": version, "path": getattr(module, "__file__", None)}}))
|
||||
"""
|
||||
env = _liboqs_env() if tool.key == "liboqs-python" else dict(os.environ)
|
||||
try:
|
||||
module = importlib.import_module(tool.module)
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", code],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=20,
|
||||
check=False,
|
||||
env=env,
|
||||
)
|
||||
except Exception as exc:
|
||||
return {
|
||||
**asdict(tool),
|
||||
"available": False,
|
||||
"installed_version": installed_version,
|
||||
"error": f"{type(exc).__name__}: {exc}",
|
||||
}
|
||||
|
||||
payload = None
|
||||
for line in result.stdout.splitlines():
|
||||
if line.startswith(marker):
|
||||
payload = json.loads(line[len(marker) :])
|
||||
if result.returncode != 0 or payload is None:
|
||||
error = _first_line(result.stderr) or _first_line(result.stdout)
|
||||
return {
|
||||
**asdict(tool),
|
||||
"available": False,
|
||||
"installed_version": installed_version,
|
||||
"error": error or f"import subprocess exited {result.returncode}",
|
||||
}
|
||||
|
||||
return {
|
||||
**asdict(tool),
|
||||
"available": True,
|
||||
"version": _module_version(module),
|
||||
"path": getattr(module, "__file__", None),
|
||||
"version": installed_version,
|
||||
**(
|
||||
{"module_version": payload.get("version")}
|
||||
if payload.get("version") and payload.get("version") != installed_version
|
||||
else {}
|
||||
),
|
||||
"path": payload.get("path"),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,5 +22,11 @@ zstd
|
|||
graphviz
|
||||
paraview
|
||||
|
||||
# CFD/PDE native helpers
|
||||
# Arch/CachyOS: fftw-openmpi
|
||||
|
||||
# Chemistry / materials native helpers
|
||||
openbabel
|
||||
|
||||
# Cryptography native helpers
|
||||
# liboqs may need a local source install when no distro package exists.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue