generated from coulomb/repo-seed
maturity scorecard generation
This commit is contained in:
30
scripts/cmis_scorecard.py
Normal file
30
scripts/cmis_scorecard.py
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a CMIS capability maturity scorecard from a guide-board run."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from open_cmis_tck.scorecard import build_scorecard, write_scorecard
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--run-dir", type=Path, required=True)
|
||||
parser.add_argument("--output-dir", type=Path)
|
||||
parser.add_argument("--print", action="store_true", dest="print_json")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.print_json:
|
||||
print(json.dumps(build_scorecard(args.run_dir), indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
result = write_scorecard(args.run_dir, args.output_dir)
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
267
scripts/install_local_toolchain.py
Normal file
267
scripts/install_local_toolchain.py
Normal file
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install a local Java/Maven toolchain under .local without sudo."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
import urllib.request
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_JDK_URL = (
|
||||
"https://api.adoptium.net/v3/binary/latest/17/ga/linux/x64/"
|
||||
"jdk/hotspot/normal/eclipse?project=jdk"
|
||||
)
|
||||
DEFAULT_MAVEN_VERSION = "3.9.11"
|
||||
DEFAULT_MAVEN_URL = (
|
||||
"https://archive.apache.org/dist/maven/maven-3/{version}/binaries/"
|
||||
"apache-maven-{version}-bin.tar.gz"
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--install-dir", type=Path, default=Path(".local") / "toolchains")
|
||||
parser.add_argument("--jdk-url", default=DEFAULT_JDK_URL)
|
||||
parser.add_argument("--jdk-sha256", default="")
|
||||
parser.add_argument("--maven-version", default=DEFAULT_MAVEN_VERSION)
|
||||
parser.add_argument("--maven-url", default="")
|
||||
parser.add_argument("--skip-jdk", action="store_true")
|
||||
parser.add_argument("--skip-maven", action="store_true")
|
||||
parser.add_argument("--force", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
extension_root = Path(__file__).resolve().parents[1]
|
||||
install_dir = (extension_root / args.install_dir).resolve()
|
||||
summary = install_toolchain(
|
||||
install_dir=install_dir,
|
||||
jdk_url=args.jdk_url,
|
||||
jdk_sha256=args.jdk_sha256 or None,
|
||||
maven_version=args.maven_version,
|
||||
maven_url=args.maven_url or DEFAULT_MAVEN_URL.format(version=args.maven_version),
|
||||
install_jdk=not args.skip_jdk,
|
||||
install_maven=not args.skip_maven,
|
||||
force=args.force,
|
||||
)
|
||||
print(json.dumps(summary, indent=2, sort_keys=True))
|
||||
return 0 if summary["status"] == "ready" else 2
|
||||
|
||||
|
||||
def install_toolchain(
|
||||
install_dir: Path,
|
||||
jdk_url: str,
|
||||
jdk_sha256: str | None,
|
||||
maven_version: str,
|
||||
maven_url: str,
|
||||
install_jdk: bool = True,
|
||||
install_maven: bool = True,
|
||||
force: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
downloads_dir = install_dir / "downloads"
|
||||
jdks_dir = install_dir / "jdks"
|
||||
mavens_dir = install_dir / "mavens"
|
||||
downloads_dir.mkdir(parents=True, exist_ok=True)
|
||||
jdks_dir.mkdir(parents=True, exist_ok=True)
|
||||
mavens_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
jdk_home = _existing_link(install_dir / "current-jdk")
|
||||
maven_home = _existing_link(install_dir / "current-maven")
|
||||
downloads: list[dict[str, Any]] = []
|
||||
|
||||
if install_jdk and (force or jdk_home is None):
|
||||
archive = downloads_dir / "temurin-jdk-17-linux-x64.tar.gz"
|
||||
downloads.append(_download(jdk_url, archive, expected_sha256=jdk_sha256))
|
||||
if force:
|
||||
_remove_link_or_dir(install_dir / "current-jdk")
|
||||
jdk_home = _extract_tool(archive, jdks_dir, "bin/java")
|
||||
_replace_symlink_or_copy(jdk_home, install_dir / "current-jdk")
|
||||
|
||||
if install_maven and (force or maven_home is None):
|
||||
archive = downloads_dir / f"apache-maven-{maven_version}-bin.tar.gz"
|
||||
downloads.append(_download(maven_url, archive))
|
||||
_verify_maven_sha512(maven_url, archive)
|
||||
if force:
|
||||
_remove_link_or_dir(install_dir / "current-maven")
|
||||
maven_home = _extract_tool(archive, mavens_dir, "bin/mvn")
|
||||
_replace_symlink_or_copy(maven_home, install_dir / "current-maven")
|
||||
|
||||
jdk_home = _existing_link(install_dir / "current-jdk")
|
||||
maven_home = _existing_link(install_dir / "current-maven")
|
||||
env_path = install_dir / "env.sh"
|
||||
summary_path = install_dir / "toolchain-summary.json"
|
||||
probes = _probe_tools(jdk_home, maven_home)
|
||||
status = "ready" if probes["java"]["available"] and probes["maven"]["available"] else "blocked"
|
||||
if jdk_home is not None and maven_home is not None:
|
||||
_write_env(env_path, jdk_home, maven_home)
|
||||
|
||||
summary = {
|
||||
"id": "opencmis-local-toolchain",
|
||||
"status": status,
|
||||
"created_at": _now(),
|
||||
"install_dir": str(install_dir),
|
||||
"jdk_home": str(jdk_home) if jdk_home else None,
|
||||
"maven_home": str(maven_home) if maven_home else None,
|
||||
"env_path": str(env_path) if env_path.exists() else None,
|
||||
"summary_path": str(summary_path),
|
||||
"downloads": downloads,
|
||||
"probes": probes,
|
||||
}
|
||||
summary_path.write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
return summary
|
||||
|
||||
|
||||
def _download(url: str, destination: Path, expected_sha256: str | None = None) -> dict[str, Any]:
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
with _open_url(url, timeout=120) as response:
|
||||
with destination.open("wb") as handle:
|
||||
shutil.copyfileobj(response, handle)
|
||||
digest = _sha256(destination)
|
||||
if expected_sha256 and digest.lower() != expected_sha256.lower():
|
||||
raise ValueError(f"SHA256 mismatch for {destination.name}")
|
||||
return {
|
||||
"url": url,
|
||||
"path": str(destination),
|
||||
"bytes": destination.stat().st_size,
|
||||
"sha256": digest,
|
||||
}
|
||||
|
||||
|
||||
def _verify_maven_sha512(url: str, archive: Path) -> None:
|
||||
with _open_url(url + ".sha512", timeout=60) as response:
|
||||
expected = response.read().decode("utf-8").strip().split()[0]
|
||||
actual = hashlib.sha512(archive.read_bytes()).hexdigest()
|
||||
if actual.lower() != expected.lower():
|
||||
raise ValueError(f"SHA512 mismatch for {archive.name}")
|
||||
|
||||
|
||||
def _open_url(url: str, timeout: int):
|
||||
request = urllib.request.Request(
|
||||
url,
|
||||
headers={"User-Agent": "open-cmis-tck-local-toolchain/0.1"},
|
||||
)
|
||||
return urllib.request.urlopen(request, timeout=timeout)
|
||||
|
||||
|
||||
def _extract_tool(archive: Path, destination: Path, marker: str) -> Path:
|
||||
before = {path.resolve() for path in destination.iterdir()} if destination.exists() else set()
|
||||
with tarfile.open(archive, "r:gz") as handle:
|
||||
_safe_extract(handle, destination)
|
||||
after = {path.resolve() for path in destination.iterdir()}
|
||||
candidates = sorted(after - before)
|
||||
if not candidates:
|
||||
candidates = sorted(after)
|
||||
for candidate in candidates:
|
||||
if (candidate / marker).exists():
|
||||
return candidate
|
||||
for candidate in sorted(after):
|
||||
if (candidate / marker).exists():
|
||||
return candidate
|
||||
raise ValueError(f"Could not find extracted tool marker {marker!r} from {archive.name}")
|
||||
|
||||
|
||||
def _safe_extract(handle: tarfile.TarFile, destination: Path) -> None:
|
||||
destination = destination.resolve()
|
||||
for member in handle.getmembers():
|
||||
target = (destination / member.name).resolve()
|
||||
try:
|
||||
target.relative_to(destination)
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"Archive member escapes destination: {member.name}") from exc
|
||||
handle.extractall(destination)
|
||||
|
||||
|
||||
def _replace_symlink_or_copy(source: Path, link: Path) -> None:
|
||||
_remove_link_or_dir(link)
|
||||
try:
|
||||
link.symlink_to(source, target_is_directory=True)
|
||||
except OSError:
|
||||
shutil.copytree(source, link)
|
||||
|
||||
|
||||
def _remove_link_or_dir(path: Path) -> None:
|
||||
if path.is_symlink() or path.is_file():
|
||||
path.unlink()
|
||||
elif path.exists():
|
||||
shutil.rmtree(path)
|
||||
|
||||
|
||||
def _existing_link(path: Path) -> Path | None:
|
||||
if not path.exists():
|
||||
return None
|
||||
return path.resolve()
|
||||
|
||||
|
||||
def _probe_tools(jdk_home: Path | None, maven_home: Path | None) -> dict[str, Any]:
|
||||
java = _probe([str(jdk_home / "bin" / "java"), "-version"]) if jdk_home else _missing_probe()
|
||||
env = os.environ.copy()
|
||||
if jdk_home:
|
||||
env["JAVA_HOME"] = str(jdk_home)
|
||||
env["PATH"] = f"{jdk_home / 'bin'}{os.pathsep}{env.get('PATH', '')}"
|
||||
maven = _probe([str(maven_home / "bin" / "mvn"), "-version"], env=env) if maven_home else _missing_probe()
|
||||
return {"java": java, "maven": maven}
|
||||
|
||||
|
||||
def _probe(command: list[str], env: dict[str, str] | None = None) -> dict[str, Any]:
|
||||
completed = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
check=False,
|
||||
env=env,
|
||||
)
|
||||
output = "\n".join(part.strip() for part in [completed.stdout, completed.stderr] if part.strip())
|
||||
return {
|
||||
"available": completed.returncode == 0,
|
||||
"command": command,
|
||||
"returncode": completed.returncode,
|
||||
"version_output": output[:4000],
|
||||
}
|
||||
|
||||
|
||||
def _missing_probe() -> dict[str, Any]:
|
||||
return {
|
||||
"available": False,
|
||||
"command": None,
|
||||
"returncode": None,
|
||||
"version_output": None,
|
||||
}
|
||||
|
||||
|
||||
def _write_env(path: Path, jdk_home: Path, maven_home: Path) -> None:
|
||||
path.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"# Source this file to use the local OpenCMIS TCK toolchain.",
|
||||
f"export JAVA_HOME='{jdk_home}'",
|
||||
f"export MAVEN_HOME='{maven_home}'",
|
||||
'export PATH="$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH"',
|
||||
"",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user