refactor: expose bundle target metadata - #841
AlexanderLanin merged 3 commits into
Conversation
|
Documentation preview for this pull request is available at: |
|
those 2 are old and already fixed. copilot doesnt notice it :/ |
| * ``entry_doc`` — the canonical entry document declared by the source bundle. | ||
| * ``external`` — whether the directory belongs to another Bazel module. | ||
| * ``external`` — whether the directory belongs to another Bazel module; | ||
| * ``bundle`` — the declaring bundle's Bazel label, name, and direct targets; |
There was a problem hiding this comment.
What is the bundle metadata useful for? I can only imagine misuse, i.e. documentation logic coupled more tightly to Bazel.
I would prefer that this bundle attribute is not provided/necessary to Python code.
There was a problem hiding this comment.
goal is to link e.g. component needs to bazel targets to source code. Visible effects will take some additional PRs.
There was a problem hiding this comment.
Specifically currently there is a heuristic for how to apply code coverage to component needs. This approach will make the path deterministic / safe.
There was a problem hiding this comment.
Automated response: The metadata is needed to connect a document or Need to the Bazel bundle that supplied it and to that bundle's direct implementation targets. The current code uses a path-based heuristic for applying code coverage to component Needs; carrying this association from Bazel into Python makes that lookup explicit and deterministic. Python receives the already-resolved metadata and does not reconstruct Bazel labels or call Bazel APIs. This PR only transports the information; later processing will consume it.
| <!-- ---------------------------------------------------------------------------- | ||
| Copyright (c) 2026 Contributors to the Eclipse Foundation | ||
|
|
||
| See the NOTICE file(s) distributed with this work for additional | ||
| information regarding copyright ownership. | ||
|
|
||
| This program and the accompanying materials are made available under the | ||
| terms of the Apache License Version 2.0 which is available at | ||
| https://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| SPDX-License-Identifier: Apache-2.0 | ||
| ----------------------------------------------------------------------------- --> | ||
|
|
There was a problem hiding this comment.
| <!-- ---------------------------------------------------------------------------- | |
| Copyright (c) 2026 Contributors to the Eclipse Foundation | |
| See the NOTICE file(s) distributed with this work for additional | |
| information regarding copyright ownership. | |
| This program and the accompanying materials are made available under the | |
| terms of the Apache License Version 2.0 which is available at | |
| https://www.apache.org/licenses/LICENSE-2.0 | |
| SPDX-License-Identifier: Apache-2.0 | |
| ----------------------------------------------------------------------------- --> |
We don't prefix Markdown files with this copyright header, do we?
There was a problem hiding this comment.
we do... hopefully this is even enforced
https://github.com/eclipse-score/tools/blob/main/cr_checker/tool/templates.ini#L79
There was a problem hiding this comment.
We do, since changes '& improvements in the cr_checker
There was a problem hiding this comment.
Automated response: The file was removed from this PR, so the Markdown copyright-header question no longer applies.
|
|
||
| # Deprecated behavior to remove | ||
|
|
||
| This file tracks compatibility paths that should be removed after callers have |
There was a problem hiding this comment.
A markdown file is not a good place to track deprecations. Rather, in Starlark we should print deprecation warnings.
There was a problem hiding this comment.
That's true, but its a different goal. What I wanted here is a list for us of stuff that we should remove on next major release. Let me just remove it from this PR and handle it separately.
There was a problem hiding this comment.
I agree.
THough I think we should have some kind of marker though that will show what is gonna be deprecated next time.
At least in comments somewhere.
There was a problem hiding this comment.
Automated response: Agreed for this PR. deprecations.md was removed. The compatibility code keeps a local TODO next to the behavior so the future cleanup remains visible, but this PR does not introduce a separate deprecation-warning mechanism.
MaximilianSoerenPollak
left a comment
There was a problem hiding this comment.
Got some questions.
| # This is the child bundle's own root before it is placed in the parent. | ||
| # It is distinct from ``root_bundle``, which describes ownership by the | ||
| # root bundle of the complete composition. | ||
| is_unplaced_bundle_root = not entry.mount_at | ||
| if is_unplaced_bundle_root: |
There was a problem hiding this comment.
I don't quiet understand this.
In what scenario does this occur? Is If it's not used as a bundle but instead standalone correct?
If that is the case I find the name a bit confusing, but also can't think of anything better atm.
There was a problem hiding this comment.
If the bundle has not been mounted anywhere, it gets is_unplaced_bundle_root. So this is basically a very dynamic attribute which can change later on.
There was a problem hiding this comment.
Automated response: The original variable was misleading and has been removed. The code now checks if not entry.mount_at directly and explains both branches. This path runs when a parent rebases entries from a child bundle; it is not specifically a standalone-versus-bundled mode.
| bundle_label = entry.bundle_label, | ||
| bundle_name = entry.bundle_name, | ||
| code_targets = entry.code_targets, | ||
| root_bundle = False, |
There was a problem hiding this comment.
What is the difference between root_gundle and is_unplaced_bundle_root ?
There was a problem hiding this comment.
So e.g. a child mount has is_unplaced_bundle_root = True, until it is finally mounted somewhere.
There was a problem hiding this comment.
Automated response: root_bundle is the persisted flag on the final manifest entry. It says whether the entry belongs to the root bundle of the current composition. The old is_unplaced_bundle_root expression was only a temporary condition while rebasing and has now been removed. A bundle can therefore be a root in a standalone composition and a child when embedded.
| @@ -427,6 +466,9 @@ _docs_bundle = rule( | |||
| "bundle_mount_ats": attr.string_list(), | |||
| "bundle_attach_tos": attr.string_list(), | |||
| "data": attr.label_list(allow_files = True), | |||
There was a problem hiding this comment.
So this already has the other things? root_bundle label & name etc. ? Just Code targets was missing?
There was a problem hiding this comment.
Automated response: These fields are introduced as one manifest contract. The previous manifest did not carry the bundle label, bundle name, root status, or direct code targets. code_targets is the new payload, while label, name, and root_bundle identify which bundle the targets belong to and how the entry participates in the current composition.
| data = own_data, | ||
| bundle_label = own_bundle_label, | ||
| bundle_name = own_bundle_name, | ||
| root_bundle = True, |
There was a problem hiding this comment.
How do you know that this is a root_bundle at this point?
There was a problem hiding this comment.
All bundles start with the assumption that they are roots, until they are placed somewhere. And _rebase_bundle_entry sets it to false.
There was a problem hiding this comment.
Automated response: At this point _docs_bundle_impl is creating an entry directly from the current bundle's own source or data. Direct entries are root entries of the current composition and are therefore marked true. If a parent embeds the bundle, _rebase_bundle_entry creates the composed entry and sets root_bundle to false. Root status is composition-specific, not a permanent property of the bundle.
| data = own_data, | ||
| bundle_label = own_bundle_label, | ||
| bundle_name = own_bundle_name, | ||
| root_bundle = True, |
There was a problem hiding this comment.
Same question as above
There was a problem hiding this comment.
Automated response: The same explanation applies here: this is the current bundle's direct explicit-source entry, so it starts as a root entry of the current composition. When a parent embeds it, _rebase_bundle_entry marks the resulting child entry as false.
|
|
||
| def _mounts_manifest_impl(ctx): | ||
| """Generate the canonical Sphinx mount manifest.""" | ||
| def _sorted_code_targets(targets): | ||
| """Return target metadata in deterministic label/type order. | ||
|
|
||
| Bundle declarations are ordered for source composition, but metadata | ||
| consumers should not observe incidental declaration ordering. Encoding the | ||
| pair before sorting keeps each label associated with its rule type. | ||
| """ | ||
| encoded = sorted([ | ||
| target.label + "\n" + target.type | ||
| for target in targets | ||
| ]) | ||
| return [ | ||
| { | ||
| "label": value.split("\n")[0], | ||
| "type": value.split("\n", 1)[1], | ||
| } | ||
| for value in encoded | ||
| ] |
There was a problem hiding this comment.
Why do we need to sort it?
There was a problem hiding this comment.
no real reason, just for stable json output which makes it easier to use it in golden samples. will check again whether we really need it.
There was a problem hiding this comment.
Automated response: No, sorting was not required. The sorting helper has been removed. code_targets are now emitted in the producer's declaration order; consumers treat them as a collection and do not assign meaning to that order.
| return [ | ||
| { | ||
| "label": value.split("\n")[0], | ||
| "type": value.split("\n", 1)[1], |
There was a problem hiding this comment.
You are only splitting the first occurence here, is this on purpose?
There was a problem hiding this comment.
Automated response: This is no longer relevant because the sorting helper and its artificial label/type newline encoding were removed. The current code does not split the combined string.
| def _read_bundle_metadata(entry: dict[str, object]) -> BundleMetadata: | ||
| """Map the producer-owned bundle fields to their runtime dataclass.""" | ||
| bundle = cast("dict[str, object]", entry["bundle"]) | ||
| targets = cast("list[dict[str, str]]", bundle["code_targets"]) | ||
| return BundleMetadata( | ||
| label=cast("str", bundle["label"]), | ||
| name=cast("str", bundle["name"]), | ||
| code_targets=tuple( | ||
| BazelTarget(label=target["label"], type=target["type"]) | ||
| for target in targets | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
| def _read_mount_spec(entry: dict[str, object]) -> MountSpec: | ||
| """Map one producer-owned manifest entry to a runtime mount spec.""" | ||
| attach_to = cast("str", entry["attach_to"]) | ||
| return MountSpec( | ||
| src_root=cast("str", entry["src_root"]), | ||
| runtime_path=cast("str", entry["runtime_path"]), | ||
| mount_at=cast("str", entry["mount_at"]), | ||
| attach_to=attach_to or None, | ||
| entry_doc=cast("str", entry["entry_doc"]), | ||
| external=cast("bool", entry["external"]), | ||
| repository=cast("str", entry["repository"]), | ||
| generated=cast("bool", entry["generated"]), | ||
| files=cast("list[str]", entry.get("files", [])), | ||
| data=cast("list[str]", entry["data"]), | ||
| root_bundle=cast("bool", entry["root_bundle"]), | ||
| bundle=_read_bundle_metadata(entry), | ||
| ) |
There was a problem hiding this comment.
Should these not be methods of the class? Seems kinda llogical to me.
There was a problem hiding this comment.
Automated response: Addressed. Parsing now lives in from_manifest_entry class methods on BazelTarget, BundleMetadata, and MountSpec. load_mounts_manifest delegates to MountSpec.from_manifest_entry, which composes the nested metadata objects.
| <!-- ---------------------------------------------------------------------------- | ||
| Copyright (c) 2026 Contributors to the Eclipse Foundation | ||
|
|
||
| See the NOTICE file(s) distributed with this work for additional | ||
| information regarding copyright ownership. | ||
|
|
||
| This program and the accompanying materials are made available under the | ||
| terms of the Apache License Version 2.0 which is available at | ||
| https://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| SPDX-License-Identifier: Apache-2.0 | ||
| ----------------------------------------------------------------------------- --> | ||
|
|
There was a problem hiding this comment.
We do, since changes '& improvements in the cr_checker
|
|
||
| # Deprecated behavior to remove | ||
|
|
||
| This file tracks compatibility paths that should be removed after callers have |
There was a problem hiding this comment.
I agree.
THough I think we should have some kind of marker though that will show what is gonna be deprecated next time.
At least in comments somewhere.
c3369bb to
6fdb805
Compare
There was a problem hiding this comment.
I think, we should talk about the overall concept first. What is a bundle? How does it correspond to the s-core metamodel? Is it a unit, a component or a module? Can we have compositions of bundles? What is the overall goal? Which use-cases we cover with this?
|
will call and in short: a bundle has no defined scope |
MaximilianSoerenPollak
left a comment
There was a problem hiding this comment.
From my side all questions are answered and make sense.
as discussed in the meeting


What this achieves
Bundle metadata and direct code targets are now carried from Bazel through the
composition manifest into Python. Python can therefore receive the information
about which bundle declared a physical entry and which implementation targets
belong directly to it.
This PR only transports and parses that information. It does not yet use the
new metadata for additional runtime behavior.
Why
Bazel already knows the declaring bundle and its direct code targets while it
composes nested and cross-repository bundles. The existing manifest discarded
that information and exposed only physical mount data. Reconstructing the
logical ownership later from paths would be unreliable, especially after
rebasing bundles into a larger documentation tree.
The metadata must therefore cross the Bazel/Python boundary before Python
features can consume it.
Changes
metadata to the composition manifest.
entry out of the Sphinx mount list.