From b0c166f4aab8b75a1a812bde633e0cbca3c4f526 Mon Sep 17 00:00:00 2001 From: Joshua Watt Date: Wed, 25 Feb 2026 10:21:54 -0700 Subject: [PATCH] generate-bindings: Rework for offline builds Reworks the way that generate-bindings works to make it more suitable for offline builds. Specifically: 1) Re-write as a python script to make it easier to maintain 2) Download model files manually instead of having shacl2code do it 3) Add options to control when downloading occurs. Options are "always", "never", and "if-missing". 4) Allow SPDX_PYTHON_MODEL_DOWNLOAD environment variable to control when downloading occurs (so it can be set when building the python package) 5) Move the script out of the "gen" directory to "scripts" 6) Ensure the script always produces output in the "gen" directory, even if it is run from elsewhere. Signed-off-by: Joshua Watt --- .gitignore | 4 +- gen/.keep | 0 gen/generate-bindings | 25 --------- pyproject.toml | 4 +- scripts/generate-bindings | 108 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 27 deletions(-) create mode 100644 gen/.keep delete mode 100755 gen/generate-bindings create mode 100755 scripts/generate-bindings diff --git a/.gitignore b/.gitignore index f5e7a23..31b3d02 100644 --- a/.gitignore +++ b/.gitignore @@ -161,4 +161,6 @@ cython_debug/ #.idea/ src/spdx_python_model/bindings/ -gen/*.py +gen/**/* +!gen/.keep + diff --git a/gen/.keep b/gen/.keep new file mode 100644 index 0000000..e69de29 diff --git a/gen/generate-bindings b/gen/generate-bindings deleted file mode 100755 index b963c55..0000000 --- a/gen/generate-bindings +++ /dev/null @@ -1,25 +0,0 @@ -#! /bin/sh -# -# SPDX-License-Identifier: Apache-2.0 - -set -e - -# SPDX versions to generate -SPDX_VERSIONS="3.0.1" - -mkdir -p "gen" - -echo "# Import all versions" > __init__.py - -for v in $SPDX_VERSIONS; do - MODNAME="v$(echo "$v" | sed 's/[^a-zA-Z0-9_]/_/g')" - - shacl2code generate --input https://spdx.org/rdf/$v/spdx-model.ttl \ - --input https://spdx.org/rdf/$v/spdx-json-serialize-annotations.ttl \ - --context https://spdx.org/rdf/$v/spdx-context.jsonld \ - --license Apache-2.0 \ - python \ - -o "$MODNAME.py" - - echo "from . import $MODNAME" >> __init__.py -done diff --git a/pyproject.toml b/pyproject.toml index 60df960..b3811e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ license = "Apache-2.0" [project.optional-dependencies] dev = [ "pytest >= 7.4", + "requests == 2.32.5", ] [project.urls] @@ -35,6 +36,7 @@ requires = [ "hatchling", "hatch-build-scripts", "shacl2code == 0.0.25", + "requests == 2.32.5", ] build-backend = "hatchling.build" @@ -51,7 +53,7 @@ out_dir = "src/spdx_python_model/bindings" work_dir = "gen" clean_out_dir = true commands = [ - "./generate-bindings" + "../scripts/generate-bindings" ] artifacts = [ "*.py" diff --git a/scripts/generate-bindings b/scripts/generate-bindings new file mode 100755 index 0000000..99685fb --- /dev/null +++ b/scripts/generate-bindings @@ -0,0 +1,108 @@ +#! /usr/bin/env python3 +# +# SPDX-License-Identifier: Apache-2.0 + + +import argparse +import sys +import requests +import subprocess +import re +import os +from pathlib import Path + +SPDX_VERSIONS = ("3.0.1",) + +THIS_DIR = Path(__file__).parent +OUT_DIR = THIS_DIR.parent / "gen" + +INIT_FILE_PATH = OUT_DIR / "__init__.py" + + +def download_url(when, session, url, output): + if output.exists(): + if when in ("if-missing", "never"): + print(f"File {output} already exists, skipping download.") + return + elif when == "never": + raise FileNotFoundError( + f"File {output} does not exist and download is disabled." + ) + + print(f"Downloading {url} to {output}...") + response = session.get(url) + response.raise_for_status() + with output.open("wb") as f: + f.write(response.content) + + +def main(): + parser = argparse.ArgumentParser( + description="Generate SPDX Python bindings from RDF SHACL model" + ) + parser.add_argument( + "--download", + choices=("always", "never", "if-missing"), + default=os.environ.get("SPDX_PYTHON_MODEL_DOWNLOAD", "if-missing"), + help="Controls how model files are downloaded. 'always' will force downloading files, 'never' will never download files, 'if-missing' will download the files if they do not already exits. Default is '%(default)s'", + ) + args = parser.parse_args() + + OUT_DIR.mkdir(parents=True, exist_ok=True) + + with INIT_FILE_PATH.open("w") as init_file, requests.Session() as session: + init_file.write("# Import all versions\n") + + for version in SPDX_VERSIONS: + modname = "v" + re.sub(r"[^a-zA-Z0-9_]", "_", version) + dl_dir = OUT_DIR / "download" / version + dl_dir.mkdir(parents=True, exist_ok=True) + + context_url = f"https://spdx.org/rdf/{version}/spdx-context.jsonld" + + download_url( + args.download, + session, + context_url, + dl_dir / "context.jsonld", + ) + download_url( + args.download, + session, + f"https://spdx.org/rdf/{version}/spdx-model.ttl", + dl_dir / "model.ttl", + ) + download_url( + args.download, + session, + f"https://spdx.org/rdf/{version}/spdx-json-serialize-annotations.ttl", + dl_dir / "annotations.ttl", + ) + p = subprocess.run( + [ + "shacl2code", + "generate", + "--input", + dl_dir / "model.ttl", + "--input", + dl_dir / "annotations.ttl", + "--context-url", + dl_dir / "context.jsonld", + context_url, + "--license", + "Apache-2.0", + "python", + "-o", + OUT_DIR / f"{modname}.py", + ] + ) + if p.returncode != 0: + return p.returncode + + init_file.write(f"from . import {modname}\n") + + return 0 + + +if __name__ == "__main__": + sys.exit(main())