Skip to content

refactor: expose bundle target metadata - #841

Merged
AlexanderLanin merged 3 commits into
eclipse-score:mainfrom
etas-contrib:feature/bundle-target-need-metadata
Sep 22, 2026
Merged

AlexanderLanin merged 3 commits into
eclipse-score:mainfrom
etas-contrib:feature/bundle-target-need-metadata

Conversation

@AlexanderLanin

@AlexanderLanin AlexanderLanin commented Sep 20, 2026

Copy link
Copy Markdown
Member

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

  • Add bundle label, bundle name, root-bundle status, and direct code-target
    metadata to the composition manifest.
  • Parse the new manifest fields into Python data classes.
  • Generate the manifest from the actual root bundle, while keeping the root
    entry out of the Sphinx mount list.
  • Document the remaining data-based compatibility paths slated for removal.

This comment was marked as resolved.

This comment was marked as resolved.

@github-actions

Copy link
Copy Markdown
Contributor

Documentation preview for this pull request is available at:
pr-841: https://eclipse-score.github.io/docs-as-code/pr-841/

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The producer, parser, runtime filtering, documentation, and regression fixtures consistently implement the expanded manifest contract.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)

@AlexanderLanin

Copy link
Copy Markdown
Member Author

those 2 are old and already fixed. copilot doesnt notice it :/

@AlexanderLanin AlexanderLanin changed the title feat: expose bundle target metadata refactor: expose bundle target metadata Sep 20, 2026
a-zw
a-zw previously requested changes Sep 21, 2026
* ``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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

goal is to link e.g. component needs to bazel targets to source code. Visible effects will take some additional PRs.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically currently there is a heuristic for how to apply code coverage to component needs. This approach will make the path deterministic / safe.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread deprecations.md Outdated
Comment on lines +1 to +13
<!-- ----------------------------------------------------------------------------
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
----------------------------------------------------------------------------- -->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<!-- ----------------------------------------------------------------------------
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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do, since changes '& improvements in the cr_checker

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated response: The file was removed from this PR, so the Markdown copyright-header question no longer applies.

Comment thread deprecations.md Outdated

# Deprecated behavior to remove

This file tracks compatibility paths that should be removed after callers have

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A markdown file is not a good place to track deprecations. Rather, in Starlark we should print deprecation warnings.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 MaximilianSoerenPollak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got some questions.

Comment thread bzl/bundle_rules.bzl Outdated
Comment on lines +227 to +231
# 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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread bzl/bundle_rules.bzl
bundle_label = entry.bundle_label,
bundle_name = entry.bundle_name,
code_targets = entry.code_targets,
root_bundle = False,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between root_gundle and is_unplaced_bundle_root ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So e.g. a child mount has is_unplaced_bundle_root = True, until it is finally mounted somewhere.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread bzl/bundle_rules.bzl
@@ -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),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this already has the other things? root_bundle label & name etc. ? Just Code targets was missing?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread bzl/bundle_rules.bzl
data = own_data,
bundle_label = own_bundle_label,
bundle_name = own_bundle_name,
root_bundle = True,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you know that this is a root_bundle at this point?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All bundles start with the assumption that they are roots, until they are placed somewhere. And _rebase_bundle_entry sets it to false.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread bzl/bundle_rules.bzl
data = own_data,
bundle_label = own_bundle_label,
bundle_name = own_bundle_name,
root_bundle = True,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as above

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread bzl/mount_rules.bzl Outdated
Comment on lines +18 to +36

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
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to sort it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread bzl/mount_rules.bzl Outdated
return [
{
"label": value.split("\n")[0],
"type": value.split("\n", 1)[1],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are only splitting the first occurence here, is this on purpose?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +109 to +139
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),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these not be methods of the class? Seems kinda llogical to me.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread deprecations.md Outdated
Comment on lines +1 to +13
<!-- ----------------------------------------------------------------------------
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
----------------------------------------------------------------------------- -->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do, since changes '& improvements in the cr_checker

Comment thread deprecations.md Outdated

# Deprecated behavior to remove

This file tracks compatibility paths that should be removed after callers have

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@antonkri antonkri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@AlexanderLanin

AlexanderLanin commented Sep 22, 2026

Copy link
Copy Markdown
Member Author

will call and in short: a bundle has no defined scope

@MaximilianSoerenPollak MaximilianSoerenPollak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my side all questions are answered and make sense.

@antonkri
antonkri self-requested a review September 22, 2026 09:13
@AlexanderLanin
AlexanderLanin dismissed stale reviews from a-zw and antonkri September 22, 2026 09:17

as discussed in the meeting

@AlexanderLanin
AlexanderLanin merged commit e0e4d3a into eclipse-score:main Sep 22, 2026
22 checks passed
@AlexanderLanin
AlexanderLanin deleted the feature/bundle-target-need-metadata branch September 22, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

5 participants