Skip to content

Commit 290bb1e

Browse files
authored
feat: allow to configure a different metamodel (#449)
1 parent 3ff5415 commit 290bb1e

7 files changed

Lines changed: 70 additions & 13 deletions

File tree

docs.bzl

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def _missing_requirements(deps):
125125
fail(msg)
126126
fail("This case should be unreachable?!")
127127

128-
def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = None):
128+
def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = None, metamodel = None):
129129
"""Creates all targets related to documentation.
130130
131131
By using this function, you'll get any and all updates for documentation targets in one place.
@@ -135,13 +135,24 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
135135
data: Additional data files to include in the documentation build.
136136
deps: Additional dependencies for the documentation build.
137137
scan_code: List of code targets to scan for source code links.
138+
known_good: Optional label to a "known good" JSON file for source links.
139+
metamodel: Optional label to a metamodel.yaml file. When set, the extension loads this
140+
file instead of the default metamodel shipped with score_metamodel.
138141
"""
139142

140143
call_path = native.package_name()
141144

142145
if call_path != "":
143146
fail("docs() must be called from the root package. Current package: " + call_path)
144147

148+
metamodel_data = []
149+
metamodel_env = {}
150+
metamodel_opts = []
151+
if metamodel != None:
152+
metamodel_data = [metamodel]
153+
metamodel_env = {"SCORE_METAMODEL_YAML": "$(location " + str(metamodel) + ")"}
154+
metamodel_opts = ["--define=score_metamodel_yaml=$(location " + str(metamodel) + ")"]
155+
145156
module_deps = deps
146157
deps = deps + _missing_requirements(deps)
147158
deps = deps + [
@@ -152,7 +163,7 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
152163
sphinx_build_binary(
153164
name = "sphinx_build",
154165
visibility = ["//visibility:private"],
155-
data = data,
166+
data = data + metamodel_data,
156167
deps = deps,
157168
)
158169

@@ -187,19 +198,19 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
187198
data_with_docs_sources = _rewrite_needs_json_to_docs_sources(data)
188199
additional_combo_sourcelinks = _rewrite_needs_json_to_sourcelinks(data)
189200
_merge_sourcelinks(name = "merged_sourcelinks", sourcelinks = [":sourcelinks_json"] + additional_combo_sourcelinks, known_good = known_good)
190-
docs_data = data + [":sourcelinks_json"]
191-
combo_data = data_with_docs_sources + [":merged_sourcelinks"]
201+
docs_data = data + metamodel_data + [":sourcelinks_json"]
202+
combo_data = data_with_docs_sources + metamodel_data + [":merged_sourcelinks"]
192203

193204
docs_env = {
194205
"SOURCE_DIRECTORY": source_dir,
195206
"DATA": str(data),
196207
"SCORE_SOURCELINKS": "$(location :sourcelinks_json)",
197-
}
208+
} | metamodel_env
198209
docs_sources_env = {
199210
"SOURCE_DIRECTORY": source_dir,
200211
"DATA": str(data_with_docs_sources),
201212
"SCORE_SOURCELINKS": "$(location :merged_sourcelinks)",
202-
}
213+
} | metamodel_env
203214
if known_good:
204215
docs_env["KNOWN_GOOD_JSON"] = "$(location "+ known_good + ")"
205216
docs_sources_env["KNOWN_GOOD_JSON"] = "$(location "+ known_good + ")"
@@ -293,10 +304,10 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
293304
"--jobs",
294305
"auto",
295306
"--define=external_needs_source=" + str(data),
296-
],
307+
] + metamodel_opts,
297308
formats = ["needs"],
298309
sphinx = ":sphinx_build",
299-
tools = data,
310+
tools = data + metamodel_data,
300311
visibility = ["//visibility:public"],
301312
# Persistent workers cause stale symlinks after dependency version
302313
# changes, corrupting the Bazel cache.

docs/how-to/setup.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@ The `docs()` macro accepts the following arguments:
6868
| Parameter | Description | Required |
6969
|-----------|-------------|----------|
7070
| `source_dir` | Directory of documentation source files (RST, MD) | Yes |
71-
| `data` | List of `needs_json` targets that should be included in the documentation| No |
72-
71+
| `data` | List of `needs_json` targets that should be included in the documentation | No |
72+
| `deps` | Additional Bazel Python dependencies | No |
73+
| `scan_code` | Source code targets to scan for traceability tags | No |
74+
| `metamodel` | Label to a custom `metamodel.yaml` that replaces the default metamodel | No |
7375

7476
### 4. Copy conf.py
7577

docs/reference/bazel_macros.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,29 @@ Minimal example (root ``BUILD``)
6060
If you don't provide the necessary Sphinx packages,
6161
this function adds its own (but checks for conflicts).
6262

63+
- ``scan_code`` (list of bazel labels)
64+
Source code targets to scan for traceability tags (``req-Id:`` annotations).
65+
Used to generate the source-code-link JSON that maps tags back to source files.
66+
67+
- ``metamodel`` (bazel label, optional)
68+
Path to a custom ``metamodel.yaml`` file.
69+
When set, the ``score_metamodel`` extension loads **this file instead of** the default metamodel.
70+
The label is automatically added to the ``data`` and ``tools`` of every generated target
71+
so the file is available in the Bazel sandbox at build time.
72+
73+
Example:
74+
75+
.. code-block:: python
76+
77+
docs(
78+
source_dir = "docs",
79+
metamodel = "//:my_metamodel.yaml",
80+
)
81+
82+
The custom ``metamodel.yaml`` must follow the same schema as the default one
83+
(see :doc:`score_metamodel </internals/extensions/metamodel>`).
84+
When ``metamodel`` is omitted the default metamodel is used unchanged.
85+
6386
Edge cases
6487
----------
6588

src/extensions/score_metamodel/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,14 @@ def postprocess_need_links(needs_types_list: list[ScoreNeedType]):
228228

229229
def setup(app: Sphinx) -> dict[str, str | bool]:
230230
app.add_config_value("external_needs_source", "", rebuild="env")
231+
app.add_config_value("score_metamodel_yaml", "", rebuild="env")
231232
config_setdefault(app.config, "needs_id_required", True)
232233
config_setdefault(app.config, "needs_id_regex", "^[A-Za-z0-9_-]{6,}")
233234

234235
# load metamodel.yaml via ruamel.yaml
235-
metamodel = load_metamodel_data()
236+
raw_metamodel_path = app.config.score_metamodel_yaml
237+
override_path = Path(raw_metamodel_path) if raw_metamodel_path else None
238+
metamodel = load_metamodel_data(override_path)
236239

237240
# Extend sphinx-needs config rather than overwriting
238241
app.config.needs_types += metamodel.needs_types

src/extensions/score_metamodel/tests/test_metamodel_load.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ def load_model_data(model_file: str) -> str:
2525
return f.read()
2626

2727

28+
def test_load_metamodel_data_explicit_path():
29+
"""When an explicit path is given, load_metamodel_data reads that file."""
30+
explicit_path = MODEL_DIR / "simple_model.yaml"
31+
result = load_metamodel_data(yaml_path=explicit_path)
32+
33+
assert len(result.needs_types) == 1
34+
assert result.needs_types[0]["directive"] == "type1"
35+
36+
2837
def test_load_metamodel_data():
2938
model_data: str = load_model_data("simple_model.yaml")
3039

src/extensions/score_metamodel/yaml_parser.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,16 @@ def _collect_all_custom_options(
182182
}
183183

184184

185-
def load_metamodel_data() -> MetaModelData:
185+
def load_metamodel_data(yaml_path: Path | None = None) -> MetaModelData:
186186
"""
187187
Load metamodel.yaml and prepare data fields as needed for sphinx-needs.
188+
189+
Args:
190+
yaml_path: Path to the metamodel YAML file. When None, the default
191+
metamodel shipped with this extension is used.
188192
"""
189-
yaml_path = Path(__file__).resolve().parent / "metamodel.yaml"
193+
if yaml_path is None:
194+
yaml_path = Path(__file__).resolve().parent / "metamodel.yaml"
190195

191196
with open(yaml_path, encoding="utf-8") as f:
192197
data = cast(dict[str, Any], YAML().load(f))

src/incremental.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ def get_env(name: str) -> str:
8484
f"--define=external_needs_source={get_env('DATA')}",
8585
]
8686

87+
metamodel_yaml = os.environ.get("SCORE_METAMODEL_YAML", "")
88+
if metamodel_yaml:
89+
base_arguments.append(f"--define=score_metamodel_yaml={metamodel_yaml}")
90+
8791
# configure sphinx build with GitHub user and repo from CLI
8892
if args.github_user and args.github_repo:
8993
base_arguments.append(f"-A=github_user={args.github_user}")

0 commit comments

Comments
 (0)