Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions bzl/bundle_rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ DocsBundleInfo = provider(
doc = "A documentation bundle with its source and placement metadata.",
fields = {
"entries": "Ordered entries, one per source directory, including its final documentation-tree location.",
"own_source_files": "This bundle's direct source files, excluding nested bundles.",
"sourcelinks": "Source-code-link JSON files together with their owning repository.",
"external_runfiles": "Documentation source files from external repositories needed in runfiles.",
# Bundle-owned generated/supporting files. Both bundle data and
Expand Down Expand Up @@ -312,6 +313,7 @@ def _docs_bundle_impl(ctx):
DefaultInfo(files = depset(transitive = [all_source_files, all_data])),
DocsBundleInfo(
entries = entries,
own_source_files = depset(direct = own_source_files),
sourcelinks = sourcelinks,
external_runfiles = external_runfiles,
data = all_data,
Expand Down Expand Up @@ -351,6 +353,27 @@ def create_bundle(name, bundles, srcs = [], sourcelinks = [], strip_prefix = "",
)
return ":" + name

def _bundle_source_files_impl(ctx):
"""Expose only a bundle's direct sources as a Sphinx source tree."""
return [DefaultInfo(files = ctx.attr.bundle[DocsBundleInfo].own_source_files)]

_bundle_source_files = rule(
implementation = _bundle_source_files_impl,
attrs = {
"bundle": attr.label(providers = [DocsBundleInfo]),
},
doc = "Exposes direct bundle sources without nested bundle sources.",
)

def bundle_source_files(name, bundle, visibility = None):
"""Create a target containing only the direct sources of a bundle."""
_bundle_source_files(
name = name,
bundle = bundle,
visibility = visibility,
)
return ":" + name

def _external_docs_runfiles_impl(ctx):
"""Expose external documentation sources needed under ``bazel run``."""
bundle = ctx.attr.bundle[DocsBundleInfo]
Expand Down
11 changes: 10 additions & 1 deletion docs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ load(
load(
"@score_docs_as_code//:bzl/bundle_rules.bzl",
"create_bundle",
"bundle_source_files",
"merge_bundle_sourcelinks",
"external_docs_runfiles",
"generate_code_target_sourcelinks",
Expand Down Expand Up @@ -345,6 +346,11 @@ def docs(
code_targets = code_targets,
visibility = ["//visibility:public"],
)
sphinx_sources = bundle_source_files(
name = "_docs_sphinx_sources",
bundle = ":docs_bundle",
visibility = ["//visibility:private"],
)
merge_bundle_sourcelinks(
name = "sourcelinks_json",
bundle = ":docs_bundle",
Expand Down Expand Up @@ -454,7 +460,10 @@ def docs(

sphinx_docs(
name = "needs_json",
srcs = [":docs_bundle"],
# Nested bundle sources are mounted by score_mounts. Passing the
# complete bundle as srcs would also expose those files as raw Sphinx
# sources and make every nested need appear twice.
srcs = [sphinx_sources],
deps = data_library_label_for_sphinx_docs,
config = sphinx_config,
extra_opts = [
Expand Down
1 change: 1 addition & 0 deletions src/tests/docs_bzl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ docs_bzl/
│ ├── external_needs/
│ ├── metamodel_violation/
│ ├── nested_bundles/
│ ├── subdirectory_bundle/
│ ├── external_bundle/
│ ├── local_version_mismatch/
│ └── invalid_bundle_placements/
Expand Down
28 changes: 28 additions & 0 deletions src/tests/docs_bzl/scenarios/subdirectory_bundle/consumer/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# *******************************************************************************
# 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
# *******************************************************************************

load("//:docs.bzl", "docs")

# Consume the producer's complete public docs bundle. This exercises the
# transitive bundle assembled by producer/docs(), including its subdirectory
# bundle, without adding the producer's needs_json separately.
docs(
source_dir = "docs",
project = "Subdirectory Bundle Consumer",
project_url = "https://example.invalid/subdirectory-bundle-consumer",
test_sources = ["src/tests/docs_bzl/scenarios/subdirectory_bundle/consumer"],
bundles = [{
"bundle": "//src/tests/docs_bzl/scenarios/subdirectory_bundle/producer:docs_bundle",
"mount_at": "producer",
}],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
..
*******************************************************************************
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
*******************************************************************************

Consumer documentation
======================

.. gd_req:: Consumer requirement
:id: gd_req__consumer
:version: 1

The consumer also has a local requirement, so its needs output can be
checked for exactly one copy of each mounted requirement.
27 changes: 27 additions & 0 deletions src/tests/docs_bzl/scenarios/subdirectory_bundle/producer/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# *******************************************************************************
# 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
# *******************************************************************************

load("//:docs.bzl", "docs")

# The embedded bundle lives in a subdirectory package alongside the docs source.
# The parent docs() must mount it explicitly because it is a separate package.
docs(
source_dir = "docs",
project = "Subdirectory Bundle Producer",
project_url = "https://example.invalid/subdirectory-bundle-producer",
test_sources = ["src/tests/docs_bzl/scenarios/subdirectory_bundle/producer"],
bundles = [{
"bundle": "//src/tests/docs_bzl/scenarios/subdirectory_bundle/producer/embedded:docs_bundle",
"mount_at": "embedded",
}],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
..
*******************************************************************************
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
*******************************************************************************

Producer documentation
======================

.. gd_req:: Producer root requirement
:id: gd_req__producer_root
:version: 1

The producer's regular documentation is part of its public docs bundle.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# *******************************************************************************
# 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
# *******************************************************************************

load("//:docs.bzl", "docs_bundle")

docs_bundle(
name = "docs_bundle",
source_dir = "content",
visibility = ["//visibility:public"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
..
*******************************************************************************
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
*******************************************************************************

Embedded documentation
======================

.. gd_req:: Embedded requirement
:id: gd_req__embedded
:version: 1

The embedded documentation is supplied by a docs_bundle in a subdirectory.
86 changes: 86 additions & 0 deletions src/tests/docs_bzl/test_subdirectory_bundle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# *******************************************************************************
# 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
# *******************************************************************************
"""Coverage for docs() with a docs_bundle in a nested Bazel package."""

from src.tests.docs_bzl.helpers import load_needs, run_bazel, run_package

PRODUCER = "//src/tests/docs_bzl/scenarios/subdirectory_bundle/producer"
CONSUMER = "//src/tests/docs_bzl/scenarios/subdirectory_bundle/consumer"


def test_docs_targets_build_with_a_bundle_in_a_subdirectory():
targets = [
"docs",
"docs_check",
"docs_link_check",
"live_preview",
"ide_support",
"docs_bundle",
"sourcelinks_json",
"needs_json",
"metrics_json",
"needs_json_file",
"traceability_gate",
]
run_bazel(
[
"build",
*[
f"{package}:{target}"
for package in (PRODUCER, CONSUMER)
for target in targets
],
]
)


def test_producer_needs_include_subdirectory_bundle_once():
result = run_package(
"build", "scenarios/subdirectory_bundle/producer", ":needs_json"
)
assert result.artifacts is not None

needs = load_needs(result.artifacts["needs.json"])
assert set(needs) == {
"gd_req__producer_root",
"gd_req__embedded",
}


def test_consumer_can_render_and_check_transitive_bundle_without_duplicate_needs():
result = run_package(
"build", "scenarios/subdirectory_bundle/consumer", ":needs_json"
)
assert result.artifacts is not None
needs = load_needs(result.artifacts["needs.json"])
assert set(needs) == {
"gd_req__producer_root",
"gd_req__embedded",
"gd_req__consumer",
}

producer_result = run_package(
"run", "scenarios/subdirectory_bundle/producer", ":docs"
)
assert (producer_result.build_dir / "embedded" / "index.html").is_file()

consumer_result = run_package(
"run", "scenarios/subdirectory_bundle/consumer", ":docs"
)
assert (consumer_result.build_dir / "producer" / "index.html").is_file()
assert (
consumer_result.build_dir / "producer" / "embedded" / "index.html"
).is_file()

run_package("run", "scenarios/subdirectory_bundle/consumer", ":docs_check")
run_package("run", "scenarios/subdirectory_bundle/consumer", ":docs_link_check")
Loading