Skip to content

Commit 54eb42e

Browse files
committed
[rules score] Add Rules_Score Aspect for simpler result CI-upload
Exports every dependable_element's doc/report artifacts as an opt-in output group, so CI can collect them across a whole workspace without a dedicated build step.
1 parent e716d7c commit 54eb42e

3 files changed

Lines changed: 165 additions & 0 deletions

File tree

‎bazel/aspects/rules_score/BUILD‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exports_files(["dependable_element_export.bzl"])
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# `dependable_element_export` aspect
2+
3+
Collects the doc/report artifacts of every `dependable_element()` (rules_score SEooC) reached by
4+
a build and republishes them under a uniform `_dependable_element_export/` path segment, as a
5+
dedicated, explicitly requestable output group.
6+
7+
It is designed to be added to an **existing** build invocation without changing what that build
8+
produces for any other target: the aspect no-ops for every target that does not carry the
9+
`DependableElementInfo` provider, and its outputs are only built when you ask for the
10+
`dependable_element_export` output group.
11+
12+
## Usage
13+
14+
```sh
15+
bazel build //<your_target(s)> \
16+
--aspects=@score_tooling//bazel/aspects/rules_score:dependable_element_export.bzl%dependable_element_export_aspect \
17+
--output_groups=+dependable_element_export \
18+
--remote_download_regex=".*/_dependable_element_export/.*"
19+
```
20+
21+
The three flags are independent and all required:
22+
23+
| Flag | Why |
24+
| --- | --- |
25+
| `--aspects=` | Attaches the aspect. Note the value is `<label of the .bzl file>%<aspect symbol>` — **not** a target label. |
26+
| `--output_groups=+dependable_element_export` | The leading `+` *adds* to the default output groups instead of replacing them, so the original build still produces everything it normally would. |
27+
| `--remote_download_regex=` | Only needed with remote execution (`--config=rbe`). Without it the exported artifacts stay on the remote worker and never materialize locally. |
28+
29+
Safe to apply to a wide pattern such as `//...` — packages without a `dependable_element()` cost
30+
nothing.
31+
32+
## Where the output lands
33+
34+
```text
35+
bazel-bin/<package-path>/_dependable_element_export/
36+
```
37+
38+
To find every exported element across a whole workspace after the build:
39+
40+
```sh
41+
find -L bazel-bin -type d -name _dependable_element_export
42+
```
43+
44+
## Why the artifacts are copied rather than just re-exposed
45+
46+
A naive aspect would simply re-advertise each `dependable_element`'s existing default outputs.
47+
That is not enough for the CI use case, because those files live at unpredictable,
48+
rule-internal paths that differ per element, which makes them hard to glob, hard to filter with
49+
`--remote_download_regex`, and hard to upload under a stable directory structure.
50+
51+
So the aspect declares new outputs under `_EXPORT_DIR` and populates them with explicit copy
52+
actions. Two details matter:
53+
54+
- **Copies, not symlinks.** `ctx.actions.symlink()` was tried first and broke under remote
55+
execution — the symlinks were not materialized usefully on the local side.
56+
- **Tree artifacts are handled separately.** Some `dependable_element` outputs are directories
57+
(e.g. generated Sphinx HTML), so the implementation branches on `File.is_directory` and uses
58+
`declare_directory()` + a recursive copy for those.
59+
60+
The package prefix is stripped from each file's `short_path` before relocation, so paths are not
61+
redundantly nested inside themselves.
62+
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# *******************************************************************************
2+
# Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
#
4+
# See the NOTICE file(s) distributed with this work for additional
5+
# information regarding copyright ownership.
6+
#
7+
# This program and the accompanying materials are made available under the
8+
# terms of the Apache License Version 2.0 which is available at
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# SPDX-License-Identifier: Apache-2.0
12+
# *******************************************************************************
13+
14+
"""CLI-level aspect exporting dependable_element (rules_score SEooC) artifacts.
15+
16+
The aspect exposes every dependable_element (rules_score SEooC)'s own doc/report
17+
artifacts as a dedicated output group. It no-ops for any target that isn't a
18+
dependable_element, so it is safe to apply unconditionally on //...
19+
Every `dependable_element`'s own doc/report artifacts become an explicitly requestable output group of that *same* build invocation - without changing what is built for any other target.
20+
21+
Rather than merely re-exposing each dependable_element's existing output files as-is, this
22+
aspect *relocates/copies* them under a synthetic `_dependable_element_export/` path segment to simplify downloading it from remote-execution or uploading it in CI.
23+
24+
Usage:
25+
26+
bazel build //<your_target(s)> \
27+
--aspects=@score_tooling//bazel/aspects/rules_score:dependable_element_export.bzl%dependable_element_export_aspect \
28+
--output_groups=+dependable_element_export \
29+
--remote_download_regex=".*/_dependable_element_export/.*"
30+
31+
See README.md next to this file for details.
32+
"""
33+
34+
load("@score_tooling//bazel/rules/rules_score:providers.bzl", "DependableElementInfo")
35+
36+
_EXPORT_DIR = "_dependable_element_export"
37+
38+
def _dependable_element_export_aspect_impl(target, ctx):
39+
"""Re-export a dependable_element's own default outputs under a uniform path segment.
40+
41+
No-ops for any target that isn't a dependable_element (identified via the
42+
`DependableElementInfo` provider that `dependable_element()` forwards for exactly this kind
43+
of cross-target lookup - see rules_score's own use of it for integrity-level checks).
44+
"""
45+
if DependableElementInfo not in target:
46+
return []
47+
48+
# Own package prefix of the file's short_path, if present, is stripped so the relocated
49+
# path doesn't redundantly nest the package path inside itself a second time.
50+
own_package_prefix = ctx.label.package + "/"
51+
52+
exported_files = []
53+
for f in target[DefaultInfo].files.to_list():
54+
short_path = f.short_path
55+
if short_path.startswith("../"):
56+
# Drop external repo prefix.
57+
relative_path = short_path.split("/", 2)[2]
58+
elif short_path.startswith(own_package_prefix):
59+
relative_path = short_path[len(own_package_prefix):]
60+
else:
61+
relative_path = short_path
62+
63+
# Some dependable_element outputs are tree artifacts (e.g. a Sphinx HTML directory),
64+
# not plain files - handled with a directory copy instead of a single-file one.
65+
if f.is_directory:
66+
exported = ctx.actions.declare_directory(_EXPORT_DIR + "/" + relative_path)
67+
ctx.actions.run_shell(
68+
inputs = [f],
69+
outputs = [exported],
70+
command = "mkdir -p \"$2\" && cp -r \"$1\"/. \"$2\"/",
71+
arguments = [f.path, exported.path],
72+
mnemonic = "DependableElementExportCopyDir",
73+
progress_message = "Exporting dependable_element directory " + f.short_path,
74+
)
75+
else:
76+
exported = ctx.actions.declare_file(_EXPORT_DIR + "/" + relative_path)
77+
ctx.actions.run_shell(
78+
inputs = [f],
79+
outputs = [exported],
80+
command = "mkdir -p \"$(dirname \"$2\")\" && cp \"$1\" \"$2\"",
81+
arguments = [f.path, exported.path],
82+
mnemonic = "DependableElementExportCopyFile",
83+
progress_message = "Exporting dependable_element file " + f.short_path,
84+
)
85+
exported_files.append(exported)
86+
87+
return [
88+
OutputGroupInfo(
89+
dependable_element_export = depset(exported_files),
90+
),
91+
]
92+
93+
dependable_element_export_aspect = aspect(
94+
implementation = _dependable_element_export_aspect_impl,
95+
doc = (
96+
"Exports dependable_element (rules_score SEooC) default outputs (doc/report " +
97+
"artifacts), relocated (via copy actions) under a uniform " +
98+
"'_dependable_element_export/' path segment, as a dedicated " +
99+
"'dependable_element_export' output group - for use as an additive `--aspects=` flag " +
100+
"on an otherwise unrelated build."
101+
),
102+
)

0 commit comments

Comments
 (0)