diff --git a/.github/workflows/internal_tests.yml b/.github/workflows/internal_tests.yml index c1d0f518313..ec62f248aa9 100644 --- a/.github/workflows/internal_tests.yml +++ b/.github/workflows/internal_tests.yml @@ -21,4 +21,4 @@ jobs: internal_tests: uses: eclipse-score/cicd-workflows/.github/workflows/tests.yml@main with: - bazel-target: "test //scripts/tooling:tooling_tests" + bazel-target: "test //scripts/tooling:tooling_tests //scripts/known_good:known_good_tests" diff --git a/MODULE.bazel b/MODULE.bazel index df2e3a8995e..0b56e7058d4 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -41,6 +41,11 @@ include("//bazel_common:score_modules_target_sw.MODULE.bazel") # Score test images include("//bazel_common:score_images.MODULE.bazel") +# Single-version locks for the deps Stage 2 collects test artifacts from (GTest, the Rust +# test rules, the ferrocene coverage tooling). Read after the includes above so it pins the +# versions they bring in transitively. +include("//bazel_common:score_test_artifact_versions.MODULE.bazel") + bazel_dep(name = "rules_boost", repo_name = "com_github_nelhage_rules_boost") archive_override( module_name = "rules_boost", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index e801dc5fcf1..8b3ee03d415 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -5,7 +5,6 @@ "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", "https://bcr.bazel.build/modules/abseil-cpp/20220623.1/MODULE.bazel": "73ae41b6818d423a11fd79d95aedef1258f304448193d4db4ff90e5e7a0f076c", - "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", @@ -263,14 +262,8 @@ "https://bcr.bazel.build/modules/googleapis/0.0.0-20240326-1c8d509c5/MODULE.bazel": "a4b7e46393c1cdcc5a00e6f85524467c48c565256b22b5fae20f84ab4a999a68", "https://bcr.bazel.build/modules/googleapis/0.0.0-20240819-fe8ba054a/MODULE.bazel": "117b7c7be7327ed5d6c482274533f2dbd78631313f607094d4625c28203cacdf", "https://bcr.bazel.build/modules/googleapis/0.0.0-20240819-fe8ba054a/source.json": "b31fc7eb283a83f71d2e5bfc3d1c562d2994198fa1278409fbe8caec3afc1d3e", - "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", - "https://bcr.bazel.build/modules/googletest/1.13.0/MODULE.bazel": "369533f4a302dc7d9ad1cd9a09a9e820a1d9a4011fad2dfa636b5bb225b9a6c7", - "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", - "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", - "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/MODULE.bazel": "827f54f492a3ce549c940106d73de332c2b30cebd0c20c0bc5d786aba7f116cb", "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/source.json": "3664514073a819992320ffbce5825e4238459df344d8b01748af2208f8d2e1eb", - "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", "https://bcr.bazel.build/modules/grpc-java/1.62.2/MODULE.bazel": "99b8771e8c7cacb130170fed2a10c9e8fed26334a93e73b42d2953250885a158", "https://bcr.bazel.build/modules/grpc-java/1.66.0/MODULE.bazel": "86ff26209fac846adb89db11f3714b3dc0090fb2fb81575673cc74880cda4e7e", "https://bcr.bazel.build/modules/grpc-java/1.69.0/MODULE.bazel": "53887af6a00b3b406d70175d3d07e84ea9362016ff55ea90b9185f0227bfaf98", @@ -530,9 +523,6 @@ "https://bcr.bazel.build/modules/rules_python_gazelle_plugin/1.5.1/source.json": "c52e4d2229fbd92b658bf60a7638e79b96525e8f7ed6c59036b4827cade9e430", "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/MODULE.bazel": "d44fec647d0aeb67b9f3b980cf68ba634976f3ae7ccd6c07d790b59b87a4f251", "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/source.json": "37c10335f2361c337c5c1f34ed36d2da70534c23088062b33a8bdaab68aa9dea", - "https://bcr.bazel.build/modules/rules_rust/0.56.0/MODULE.bazel": "3295b00757db397122092322fe1e920be7f5c9fbfb8619138977e820f2cbbbae", - "https://bcr.bazel.build/modules/rules_rust/0.61.0/MODULE.bazel": "0318a95777b9114c8740f34b60d6d68f9cfef61e2f4b52424ca626213d33787b", - "https://bcr.bazel.build/modules/rules_rust/0.67.0/MODULE.bazel": "87c3816c4321352dcfd9e9e26b58e84efc5b21351ae3ef8fb5d0d57bde7237f5", "https://bcr.bazel.build/modules/rules_shell/0.1.2/MODULE.bazel": "66e4ca3ce084b04af0b9ff05ff14cab4e5df7503973818bb91cbc6cda08d32fc", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", @@ -592,7 +582,6 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/abseil-cpp/20210324.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/abseil-cpp/20211102.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/abseil-cpp/20220623.1/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/abseil-cpp/20230125.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/abseil-cpp/20230802.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/abseil-cpp/20230802.1/MODULE.bazel": "not found", @@ -773,13 +762,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/google_benchmark/1.9.5/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googleapis/0.0.0-20240326-1c8d509c5/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googleapis/0.0.0-20240819-fe8ba054a/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.11.0/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.13.0/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.14.0/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.15.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.17.0.bcr.2/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.17.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/grpc-java/1.62.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/grpc-java/1.66.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/grpc-java/1.69.0/MODULE.bazel": "not found", @@ -997,10 +980,6 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/1.8.5/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python_gazelle_plugin/1.5.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_robolectric/4.14.1.2/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.56.0/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.61.0/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.67.0/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.68.1-score/MODULE.bazel": "dc1c87d74ef6d32190e65c3c8aabfa7e7764e457bf9888312e0313c3c11fdb69", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.68.2-score/MODULE.bazel": "37be8dee6df19d666c1d4266e1266d82012aa83bd82de38b3100fd7f641d064b", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.68.2-score/source.json": "f88ad98dd08f296a546677e86ad42b20f61851e41a9fd3e0449971162fcaf784", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.1.2/MODULE.bazel": "not found", diff --git a/bazel_common/score_images.MODULE.bazel b/bazel_common/score_images.MODULE.bazel index a68ab7227af..38cd721e690 100644 --- a/bazel_common/score_images.MODULE.bazel +++ b/bazel_common/score_images.MODULE.bazel @@ -10,11 +10,13 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +# Spelled as a commit, not `tag = "v2.3.1"`: the Stage-1 manifest carries immutable commits only, so +# a tag-pinned git_override was dropped from it silently and every module resolved its own rules_oci. bazel_dep(name = "rules_oci", version = "2.3.1") git_override( module_name = "rules_oci", + commit = "f214185dcf149090cb3212e878f692eb2c8c0d3d", # v2.3.1 remote = "https://github.com/bazel-contrib/rules_oci.git", - tag = "v2.3.1", ) oci = use_extension("@rules_oci//oci:extensions.bzl", "oci") diff --git a/bazel_common/score_test_artifact_versions.MODULE.bazel b/bazel_common/score_test_artifact_versions.MODULE.bazel new file mode 100644 index 00000000000..e860cb7c186 --- /dev/null +++ b/bazel_common/score_test_artifact_versions.MODULE.bazel @@ -0,0 +1,41 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +# +# Deliberate ceilings for the deps Stage 2 collects test artifacts from. +# +# ref_int's resolved set is imposed on every module under test whether a dependency is named here or +# not; this file does not decide *which* deps get pinned. It decides which of those pins are a +# decision rather than an inheritance. `bazel_dep(version = ...)` is only a floor that MVS raises +# silently, while `single_version_override` is also a ceiling, so the versions below are ones Stage 1 +# reports as `asserted` rather than `incidental`. +# +# Every version here equals what MVS resolves today, so this changes no build now -- only what +# happens the day something in the graph asks for more. + +# C++ test binaries and the coverage .dat files genhtml reads. +single_version_override( + module_name = "googletest", + version = "1.17.0.bcr.2", +) + +# Rust test rules that build the .profraw-emitting binaries. +single_version_override( + module_name = "rules_rust", + version = "0.68.2-score", +) + +# Ferrocene coverage tooling behind those .profraw files. +single_version_override( + module_name = "score_toolchains_rust", + version = "0.8.0", +) diff --git a/scripts/known_good/BUILD b/scripts/known_good/BUILD new file mode 100644 index 00000000000..cfdff652c00 --- /dev/null +++ b/scripts/known_good/BUILD @@ -0,0 +1,63 @@ +# ******************************************************************************* +# 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("@rules_python//python:defs.bzl", "py_binary", "py_library") +load("@score_tooling//python_basics:defs.bzl", "score_py_pytest") + +# Library target: the known_good package (models + generators). +# Depended on by the test and binary targets below. Note //scripts/tooling has its own separate +# lib/known_good package and does not use this one. +py_library( + name = "known_good", + srcs = glob( + ["**/*.py"], + exclude = ["tests/**"], + ), + visibility = ["//visibility:public"], +) + +# Tests for the known_good package (currently: ResolvedDependencies). +# Not part of //scripts/tooling:tooling_tests, whose glob is scoped to scripts/tooling/tests/. +score_py_pytest( + name = "known_good_tests", + srcs = glob(["tests/**/*.py"]), + data = ["//:known_good.json"], + pytest_config = "//:pyproject.toml", + deps = [":known_good"], +) + +# Runnable binary for the resolve + inject workflow. +# +# Stage 1 (export) — 'bazel mod graph' is a prerequisite; run it first and pass the result: +# bazel mod graph --verbose --output=json > graph.json +# bazel run //scripts/known_good:resolve_deps -- \ +# --mod-graph graph.json --export _resolved_deps/resolved_versions.json +# Writes the manifest, graph.json and resolved_pins_report.json side by side; all three are +# published as the stage1-resolved-deps artifact. Paths are resolved against +# BUILD_WORKSPACE_DIRECTORY, so graph.json does not need to be listed in data = [...]. +# +# '--verbose' adds 'originalVersion' to each edge, the only record of a consumer asking for a +# version other than the one ref_int imposes. It is a strict superset, so Stage 2 reads the same +# graph.json either way; without it the export still succeeds, with every verdict 'unknown'. +# +# Stage 2 (inject) — consumes that same directory: +# bazel run //scripts/known_good:resolve_deps -- \ +# _module/MODULE.bazel --resolved-deps _resolved_deps/ +# The manifest supplies each module's resolved version; graph.json identifies the +# module-under-test's transitive closure so all of it is pinned, not only direct deps. +py_binary( + name = "resolve_deps", + srcs = ["resolved_dependencies.py"], + main = "resolved_dependencies.py", + visibility = ["//visibility:public"], + deps = [":known_good"], +) diff --git a/scripts/known_good/module_patches.py b/scripts/known_good/module_patches.py new file mode 100644 index 00000000000..84291beb9da --- /dev/null +++ b/scripts/known_good/module_patches.py @@ -0,0 +1,109 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +"""Apply the module-under-test's own ``bazel_patches`` to its Stage-2 checkout. + +Distinct from the ``bazel_patches`` strip in ``ResolvedDependencies.overwrite``: that drops +``//patches/...`` labels from *injected dependency overrides* (they do not resolve inside +another module's root); this applies the module's own patches by filesystem path, emitting +no label. Both are correct at once. +""" + +from __future__ import annotations + +import re +import subprocess +from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .models.module import Module + +# ``//patches/:``; known_good.json also carries bare paths, so both are accepted. +_LABEL_RE = re.compile(r"^//(?P[^:]+):(?P.+)$") + +# Matches the patch_strip emitted with these same patches in Stage-1 override directives. +PATCH_STRIP = 1 + + +class ModulePatchError(RuntimeError): + """A declared patch could not be resolved or applied. + + Always a ref_int defect: ref_int's recorded patch no longer matches the commit it is + declared against. + """ + + +def resolve_patch_path(patch: str, ref_int_root: Path) -> Path: + """Map one ``bazel_patches`` entry (label or bare path) to a file in ref_int's tree.""" + match = _LABEL_RE.match(patch) + relative = Path(match.group("pkg")) / match.group("name") if match else Path(patch) + if relative.is_absolute(): + raise ModulePatchError(f"patch entry must be workspace-relative, got absolute path: {patch!r}") + resolved = (ref_int_root / relative).resolve() + if not resolved.is_file(): + raise ModulePatchError(f"declared patch {patch!r} does not exist at {resolved}") + return resolved + + +def _git_apply(workspace: Path, patch_file: Path, *extra: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + ["git", "-C", str(workspace), "apply", f"-p{PATCH_STRIP}", *extra, str(patch_file)], + capture_output=True, + text=True, + check=False, + ) + + +def apply_module_patches( + module: Module, + workspace: Path, + ref_int_root: Path, + log=print, +) -> list[str]: + """Apply ``module``'s declared patches to ``workspace`` in declaration order. + + Returns the newly applied entries; already-applied ones are reported and skipped. Raises + :class:`ModulePatchError` on the first entry that neither applies nor is already applied — + silently testing an unpatched checkout is the bug this step exists to fix. + """ + if not module.bazel_patches: + log(f"QR: {module.name} declares no bazel_patches; checkout is used as-is") + return [] + + applied: list[str] = [] + for patch in module.bazel_patches: + patch_file = resolve_patch_path(patch, ref_int_root) + + # Idempotency for local re-runs (ci_local.sh reuses _module/); fresh CI never hits it. + if _git_apply(workspace, patch_file, "--reverse", "--check").returncode == 0: + log(f"QR: {patch} already applied to {workspace}; skipping") + continue + + dry_run = _git_apply(workspace, patch_file, "--check") + if dry_run.returncode != 0: + raise ModulePatchError( + f"ref_int harness defect: patch {patch} does not apply to {module.name} " + f"at commit {module.hash}, and is not already applied.\n" + f"git apply --check said:\n{dry_run.stderr.strip()}" + ) + + result = _git_apply(workspace, patch_file) + if result.returncode != 0: + raise ModulePatchError( + f"ref_int harness defect: patch {patch} passed --check but failed to apply to " + f"{module.name}.\ngit apply said:\n{result.stderr.strip()}" + ) + log(f"QR: applied {patch} to {workspace}") + applied.append(patch) + + return applied diff --git a/scripts/known_good/rc_label_consistency.py b/scripts/known_good/rc_label_consistency.py new file mode 100644 index 00000000000..7e2906e991f --- /dev/null +++ b/scripts/known_good/rc_label_consistency.py @@ -0,0 +1,127 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +"""Offline guard: ref_int must name one build setting by exactly one label. + +ref_int sets the same build settings from three places (root ``.bazelrc``, +``ci/stage2/module.bazelrc``, ``metadata.extra_test_config``). When a setting relocates +upstream, a stale label fails at *option loading* — before any layered config can override +it — surfacing as an unrelated-looking Stage-2 failure. + +Rule: within one repo, a setting's basename must resolve to a single package path. Two repos +may own a same-named setting (both score_baselibs and score_logging define +``KRemote_Logging``), so the repo is part of the key. +""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from collections import defaultdict +from dataclasses import dataclass +from pathlib import Path + +REF_INT_ROOT = Path(__file__).resolve().parent.parent.parent + +# Only sources ref_int owns; a module's own .bazelrc is fixed by a patch instead. +RC_FILES = (Path(".bazelrc"), Path("ci/stage2/module.bazelrc")) +KNOWN_GOOD = Path("known_good.json") + +# ``--@repo//pkg/path:setting=value`` or the repo-relative ``--//pkg:setting=value``. +_SETTING_RE = re.compile(r"--(?P