diff --git a/.bazelignore b/.bazelignore index 66645815..f2c32d53 100644 --- a/.bazelignore +++ b/.bazelignore @@ -9,11 +9,6 @@ bazel-bin bazel-out bazel-testlogs -third_party/rgo/v0_60_0/base -third_party/rgo/v0_61_1/base -third_party/rgo/v0_62_0/base -third_party/rgo/v0_63_0/base -tools/tests/rules_go_variant_regressions modules/go/bazel-bin modules/go/bazel-out modules/go/bazel-testlogs diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 65c3347b..00000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Unified diff context lines contain a required space before source-code tabs. -third_party/rules_go_orchestrion/patches/**/0001-full-delta.patch whitespace=-blank-at-eol,-blank-at-eof,-space-before-tab diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14292d53..4fae57c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,8 +44,6 @@ jobs: runs-on: ubuntu-latest outputs: docs_only: ${{ steps.classify.outputs.docs_only }} - rules_go_integration_matrix: ${{ steps.rules_go_matrix.outputs.integration_matrix }} - rules_go_upstreams: ${{ steps.rules_go_matrix.outputs.upstreams }} run_full_ci: ${{ steps.classify.outputs.run_full_ci }} steps: - name: Checkout @@ -89,14 +87,6 @@ jobs: echo "${line}" >> "${GITHUB_OUTPUT}" done < <(./tools/dev/classify_ci_changes.sh "${base_sha}" "${head_sha}") - - name: Generate rules_go CI matrices - id: rules_go_matrix - shell: bash - run: | - python3 tools/dev/materialize_rules_go_fork.py list-upstreams | - python3 -c 'import json, sys; upstreams = [line.strip() for line in sys.stdin if line.strip()]; assert upstreams, "no rules_go upstreams found"; print("upstreams=" + json.dumps(upstreams, separators=(",", ":"))); print("integration_matrix=" + json.dumps({"upstream": upstreams, "module_system": ["workspace", "bzlmod"]}, separators=(",", ":")))' \ - >> "${GITHUB_OUTPUT}" - bazel-tests: needs: changes if: ${{ needs.changes.outputs.run_full_ci == 'true' }} @@ -146,14 +136,7 @@ jobs: shell: bash run: | if [[ "${{ runner.os }}" == "Windows" ]]; then - # Ubuntu and macOS build the full example matrix, including Go. - # Keep Windows coverage on the non-Go examples until the vendored - # rules_go Orchestrion stdlib action is stable there. - ./bazelw --output_user_root=C:/b build \ - -- \ - //examples/... \ - -//examples/single_service/src/go-project/... \ - -//examples/multi_service/src/go-project/... + ./bazelw --output_user_root=C:/b build //examples/... else ./bazelw build //examples/... fi @@ -168,10 +151,7 @@ jobs: ./bazelw --output_user_root=C:/b test \ --enable_runfiles \ --test_output=errors \ - -- \ - //examples/... \ - -//examples/single_service/src/go-project/... \ - -//examples/multi_service/src/go-project/... + //examples/... else ./bazelw test //examples/... --test_output=errors fi @@ -405,85 +385,22 @@ jobs: exit 1 fi - rules-go-variant-smoke-shard: - name: rules-go-variant-smoke-shard (${{ matrix.upstream }}) - needs: changes - if: ${{ needs.changes.outputs.run_full_ci == 'true' }} - strategy: - fail-fast: false - max-parallel: 4 - matrix: - upstream: ${{ fromJSON(needs.changes.outputs.rules_go_upstreams) }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - # The variant verifier and smoke harness inspect the vendored fork - # contract directly, so keep the checkout complete for maintainer - # diagnostics instead of relying on a shallow history. - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Set up Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version: ${{ env.GO_VERSION }} - - - name: Install Bazelisk - shell: bash - run: | - GO111MODULE=on go install github.com/bazelbuild/bazelisk@v1.28.1 - echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - - - name: Run vendored rules_go smoke coverage - shell: bash - env: - RULES_GO_UPSTREAM: ${{ matrix.upstream }} - RULES_GO_VARIANT: base - run: ./tools/dev/run_rules_go_variant_smoke.sh - - rules-go-variant-smoke: - needs: - - changes - - rules-go-variant-smoke-shard - if: ${{ always() }} - runs-on: ubuntu-latest - steps: - - name: Require successful rules_go variant smoke shards - shell: bash - env: - CHANGES_RESULT: ${{ needs.changes.result }} - RUN_FULL_CI: ${{ needs.changes.outputs.run_full_ci }} - SHARD_RESULT: ${{ needs.rules-go-variant-smoke-shard.result }} - run: | - if [[ "${CHANGES_RESULT}" != "success" ]]; then - echo "Change classification did not succeed: ${CHANGES_RESULT}" >&2 - exit 1 - fi - - if [[ "${RUN_FULL_CI}" != "true" ]]; then - echo "rules_go variant smoke coverage is not required for this change." - exit 0 - fi - - if [[ "${SHARD_RESULT}" != "success" ]]; then - echo "One or more rules_go variant smoke shards did not succeed: ${SHARD_RESULT}" >&2 - exit 1 - fi - + # ddtest instrument builds the SDK and Mini without the host's Go, so the + # host Go stays behind a sentinel that fails the run if anything calls it. workspace-compat-shard: - name: workspace-compat-shard (${{ matrix.upstream }}, ${{ matrix.module_system }}) + name: workspace-compat-shard (rules_go ${{ matrix.rules_go }}, ${{ matrix.module_system }}) needs: changes if: ${{ needs.changes.outputs.run_full_ci == 'true' }} strategy: fail-fast: false max-parallel: 4 - matrix: ${{ fromJSON(needs.changes.outputs.rules_go_integration_matrix) }} + matrix: + rules_go: ["0.60.0", "0.61.1", "0.62.0", "0.63.0", "0.64.2", "0.65.0"] + module_system: [workspace, bzlmod] + include: + # rules_go 0.65.0 requires Go 1.26. + - rules_go: "0.65.0" + go_version: "1.26.3" runs-on: ubuntu-latest steps: - name: Checkout @@ -505,35 +422,13 @@ jobs: GO111MODULE=on go install github.com/bazelbuild/bazelisk@v1.28.1 echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - - name: Validate general Go companion consumer path - shell: bash - env: - MODULE_SYSTEM: ${{ matrix.module_system }} - ORCHESTRION_MODE: general - RULES_GO_UPSTREAM: ${{ matrix.upstream }} - RULES_GO_VARIANT: base - USE_BAZEL_VERSION: "8.8.2" - run: | - case "${MODULE_SYSTEM}" in - workspace) - ./tools/tests/integration/run_workspace_go_integration.sh - ;; - bzlmod) - ./tools/tests/integration/run_bzlmod_go_integration.sh - ;; - *) - echo "Unsupported module system: ${MODULE_SYSTEM}" >&2 - exit 1 - ;; - esac - - - name: Validate Test Optimization Go companion consumer path + - name: Validate Go companion consumer path shell: bash env: + FORBID_HOST_GO: "1" + GO_VERSION: ${{ matrix.go_version || '1.25.0' }} MODULE_SYSTEM: ${{ matrix.module_system }} - ORCHESTRION_MODE: test_optimization - RULES_GO_UPSTREAM: ${{ matrix.upstream }} - RULES_GO_VARIANT: base + RULES_GO_VERSION: ${{ matrix.rules_go }} USE_BAZEL_VERSION: "8.8.2" run: | case "${MODULE_SYSTEM}" in @@ -578,139 +473,9 @@ jobs: exit 1 fi - go-bootstrap-no-host-shard: - name: go-bootstrap-no-host-shard (${{ matrix.upstream }}, ${{ matrix.module_system }}) + windows-go-smoke: needs: changes if: ${{ needs.changes.outputs.run_full_ci == 'true' }} - strategy: - fail-fast: false - max-parallel: 4 - matrix: ${{ fromJSON(needs.changes.outputs.rules_go_integration_matrix) }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - # Do not use setup-go or `go install` in this lane. The consumer path must - # obtain its Go SDK from Bazel, while a sentinel shadows any Go binary - # already present in the hosted-runner image. - - name: Install Bazelisk without Go - shell: bash - run: | - set -euo pipefail - bin_dir="${RUNNER_TEMP}/bazelisk-bin" - bazelisk_path="${bin_dir}/bazelisk" - mkdir -p "${bin_dir}" - curl --fail --location --silent --show-error \ - --retry 3 \ - --output "${bazelisk_path}" \ - "https://github.com/bazelbuild/bazelisk/releases/download/v${BAZELISK_VERSION}/bazelisk-linux-amd64" - printf '%s %s\n' "${BAZELISK_SHA256_LINUX_AMD64}" "${bazelisk_path}" | - sha256sum --check - chmod +x "${bazelisk_path}" - echo "${bin_dir}" >> "${GITHUB_PATH}" - - - name: Validate cold config transition without host Go - shell: bash - env: - CONFIG_TRANSITION_ONLY: "1" - EXPECTED_ORCHESTRION_CACHE_PHASE: extensions.bootstrap_cache_miss - FORBID_HOST_GO: "1" - MODULE_SYSTEM: ${{ matrix.module_system }} - ORCHESTRION_MODE: test_optimization - RULES_GO_UPSTREAM: ${{ matrix.upstream }} - RULES_GO_VARIANT: base - USE_BAZEL_VERSION: "8.8.2" - XDG_CACHE_HOME: ${{ runner.temp }}/rto-no-host-go-cache/${{ matrix.upstream }}/${{ matrix.module_system }} - run: | - set -euo pipefail - if [[ -e "${XDG_CACHE_HOME}" ]]; then - echo "Cold bootstrap cache path already exists: ${XDG_CACHE_HOME}" >&2 - exit 1 - fi - mkdir -p "${XDG_CACHE_HOME}" - case "${MODULE_SYSTEM}" in - workspace) - ./tools/tests/integration/run_workspace_go_integration.sh - ;; - bzlmod) - ./tools/tests/integration/run_bzlmod_go_integration.sh - ;; - *) - echo "Unsupported module system: ${MODULE_SYSTEM}" >&2 - exit 1 - ;; - esac - - - name: Validate warm cache restoration without host Go - shell: bash - env: - CONFIG_TRANSITION_ONLY: "1" - EXPECTED_ORCHESTRION_CACHE_PHASE: extensions.bootstrap_cache_hit - FORBID_HOST_GO: "1" - MODULE_SYSTEM: ${{ matrix.module_system }} - ORCHESTRION_MODE: test_optimization - RULES_GO_UPSTREAM: ${{ matrix.upstream }} - RULES_GO_VARIANT: base - USE_BAZEL_VERSION: "8.8.2" - XDG_CACHE_HOME: ${{ runner.temp }}/rto-no-host-go-cache/${{ matrix.upstream }}/${{ matrix.module_system }} - run: | - set -euo pipefail - case "${MODULE_SYSTEM}" in - workspace) - ./tools/tests/integration/run_workspace_go_integration.sh - ;; - bzlmod) - ./tools/tests/integration/run_bzlmod_go_integration.sh - ;; - *) - echo "Unsupported module system: ${MODULE_SYSTEM}" >&2 - exit 1 - ;; - esac - - go-bootstrap-no-host: - needs: - - changes - - go-bootstrap-no-host-shard - if: ${{ always() }} - runs-on: ubuntu-latest - steps: - - name: Require successful no-host-Go bootstrap shards - shell: bash - env: - CHANGES_RESULT: ${{ needs.changes.result }} - RUN_FULL_CI: ${{ needs.changes.outputs.run_full_ci }} - SHARD_RESULT: ${{ needs.go-bootstrap-no-host-shard.result }} - run: | - if [[ "${CHANGES_RESULT}" != "success" ]]; then - echo "Change classification did not succeed: ${CHANGES_RESULT}" >&2 - exit 1 - fi - - if [[ "${RUN_FULL_CI}" != "true" ]]; then - echo "No-host-Go bootstrap coverage is not required for this change." - exit 0 - fi - - if [[ "${SHARD_RESULT}" != "success" ]]; then - echo "One or more no-host-Go bootstrap shards did not succeed: ${SHARD_RESULT}" >&2 - exit 1 - fi - - windows-go-bootstrap-shard: - name: windows-go-bootstrap-shard (${{ matrix.upstream }}, ${{ matrix.module_system }}) - needs: changes - if: ${{ needs.changes.outputs.run_full_ci == 'true' }} - strategy: - fail-fast: false - max-parallel: 4 - matrix: ${{ fromJSON(needs.changes.outputs.rules_go_integration_matrix) }} runs-on: windows-latest steps: - name: Checkout @@ -732,57 +497,11 @@ jobs: GO111MODULE=on go install github.com/bazelbuild/bazelisk@v1.28.1 echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - - name: Validate disabled-to-enabled bootstrap transition - shell: bash - env: - MODULE_SYSTEM: ${{ matrix.module_system }} - ORCHESTRION_MODE: test_optimization - RULES_GO_UPSTREAM: ${{ matrix.upstream }} - RULES_GO_VARIANT: base - WINDOWS_CONFIG_TRANSITION_ONLY: "1" - run: | - case "${MODULE_SYSTEM}" in - workspace) - ./tools/tests/integration/run_workspace_go_integration.sh - ;; - bzlmod) - ./tools/tests/integration/run_bzlmod_go_integration.sh - ;; - *) - echo "Unsupported module system: ${MODULE_SYSTEM}" >&2 - exit 1 - ;; - esac - - # Keep one consolidated result while the expensive work runs in parallel. - windows-go-bootstrap-smoke: - needs: - - changes - - windows-go-bootstrap-shard - if: ${{ always() }} - runs-on: ubuntu-latest - steps: - - name: Require successful Windows bootstrap shards + - name: Run an instrumented Go test in a Bzlmod consumer shell: bash env: - CHANGES_RESULT: ${{ needs.changes.result }} - RUN_FULL_CI: ${{ needs.changes.outputs.run_full_ci }} - SHARD_RESULT: ${{ needs.windows-go-bootstrap-shard.result }} - run: | - if [[ "${CHANGES_RESULT}" != "success" ]]; then - echo "Change classification did not succeed: ${CHANGES_RESULT}" >&2 - exit 1 - fi - - if [[ "${RUN_FULL_CI}" != "true" ]]; then - echo "Windows Go bootstrap smoke is not required for this change." - exit 0 - fi - - if [[ "${SHARD_RESULT}" != "success" ]]; then - echo "One or more Windows Go bootstrap shards did not succeed: ${SHARD_RESULT}" >&2 - exit 1 - fi + WINDOWS_SMOKE_ONLY: "1" + run: ./tools/tests/integration/run_bzlmod_go_integration.sh coverage-tools: needs: changes @@ -907,7 +626,6 @@ jobs: run_schema_validation: true run_fixture_json_validation: true run_schema_parser_parity: true - run_rules_go_fork_drift: true # Policy: keep the hermetic lane Linux-only for now. # Rationale: sandbox/network-blocking semantics are most reliable on Linux; diff --git a/.github/workflows/docs-links.yml b/.github/workflows/docs-links.yml index 1d59e8d4..be87f683 100644 --- a/.github/workflows/docs-links.yml +++ b/.github/workflows/docs-links.yml @@ -62,7 +62,7 @@ jobs: filtered=() for file in "${files[@]}"; do case "$file" in - third_party/*|docs/superpowers/plans/*) continue ;; + docs/superpowers/plans/*) continue ;; esac filtered+=("$file") done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a71fc13..867aff9a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -264,7 +264,6 @@ jobs: run_schema_validation: true run_fixture_json_validation: false run_schema_parser_parity: true - run_rules_go_fork_drift: true release-docs-links: timeout-minutes: 10 @@ -388,7 +387,6 @@ jobs: args+=(--tag "${GITHUB_REF#refs/tags/}") fi python3 tools/dev/check_module_versions.py - python3 tools/dev/check_release_archive_contents.py python3 tools/dev/build_release.py --ref "$GITHUB_SHA" --output "$RUNNER_TEMP/release" "${args[@]}" (cd "$RUNNER_TEMP/release" && sha256sum --check SHA256SUMS) diff --git a/.github/workflows/rules-go-variant-extended.yml b/.github/workflows/rules-go-variant-extended.yml deleted file mode 100644 index eb2d8905..00000000 --- a/.github/workflows/rules-go-variant-extended.yml +++ /dev/null @@ -1,52 +0,0 @@ -# Unless explicitly stated otherwise all files in this repository are licensed under -# the Apache 2.0 License. -# -# This product includes software developed at Datadog -# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. - -name: rules_go variant extended - -on: - workflow_dispatch: - schedule: - - cron: "0 3 * * *" - -permissions: - contents: read - -concurrency: - group: rules-go-variant-extended-${{ github.ref }} - cancel-in-progress: true - -jobs: - rules-go-variant-extended: - timeout-minutes: 45 - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: "3.11" - - - name: Set up Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version: "1.25.0" - - - name: Install Bazelisk - shell: bash - run: | - GO111MODULE=on go install github.com/bazelbuild/bazelisk@v1.28.1 - echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - - - name: Run extended vendored rules_go coverage - shell: bash - run: | - while IFS= read -r upstream; do - RULES_GO_UPSTREAM="${upstream}" \ - RULES_GO_VARIANT="base" \ - ./tools/dev/run_rules_go_variant_extended.sh - done < <(python3 tools/dev/materialize_rules_go_fork.py list-upstreams) diff --git a/.github/workflows/shared-validation.yml b/.github/workflows/shared-validation.yml index 9a92d3ae..f1eccc41 100644 --- a/.github/workflows/shared-validation.yml +++ b/.github/workflows/shared-validation.yml @@ -48,10 +48,6 @@ on: required: false type: boolean default: false - run_rules_go_fork_drift: - required: false - type: boolean - default: false permissions: contents: read @@ -107,8 +103,7 @@ jobs: run: | mapfile -t starlark_files < <( git ls-files -- \ - '*.bzl' 'BUILD' 'BUILD.bazel' 'WORKSPACE' 'MODULE.bazel' \ - ':(exclude)third_party/rgo/**' + '*.bzl' 'BUILD' 'BUILD.bazel' 'WORKSPACE' 'MODULE.bazel' ) if (( ${#starlark_files[@]} == 0 )); then echo "No Starlark files found." @@ -138,7 +133,7 @@ jobs: shell: bash run: | mapfile -t go_files < <( - git ls-files -- '*.go' ':(exclude)third_party/**' + git ls-files -- '*.go' ) if (( ${#go_files[@]} == 0 )); then exit 0 @@ -199,130 +194,6 @@ jobs: shell: bash run: python3 tools/core/schemas/check_schema_parser_parity.py - rules-go-fork-drift-matrix: - if: ${{ !inputs.docs_only && inputs.run_rules_go_fork_drift }} - runs-on: ubuntu-latest - outputs: - upstreams: ${{ steps.generate.outputs.upstreams }} - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: ${{ inputs.python_version }} - - - name: Generate rules_go profile matrix - id: generate - shell: bash - run: | - python3 tools/dev/materialize_rules_go_fork.py list-upstreams | - python3 -c 'import json, sys; upstreams = [line.strip() for line in sys.stdin if line.strip()]; assert upstreams, "no rules_go upstreams found"; print("upstreams=" + json.dumps(upstreams, separators=(",", ":")))' \ - >> "${GITHUB_OUTPUT}" - - rules-go-fork-drift-global: - if: ${{ !inputs.docs_only && inputs.run_rules_go_fork_drift }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: ${{ inputs.python_version }} - - - name: Set up Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version: "1.25.0" - - - name: Verify Go toolchain for generated rules_go maps - shell: bash - run: | - go version - command -v gofmt - - - name: Verify rules_go fork registry and materialized trees - shell: bash - run: | - python3 tools/dev/generate_rules_go_fork_maps.py --check - RULES_GO_ORCHESTRION_CACHE="${RUNNER_TEMP}/rules_go_orchestrion_cache" \ - python3 tools/dev/materialize_rules_go_fork.py check --all - - - name: Verify rules_go fork release archive contents - shell: bash - run: python3 tools/dev/check_release_archive_contents.py - - rules-go-fork-profile-shard: - name: rules-go-fork-profile-shard (${{ matrix.upstream }}, ${{ matrix.runner }}) - needs: rules-go-fork-drift-matrix - if: ${{ !inputs.docs_only && inputs.run_rules_go_fork_drift }} - strategy: - fail-fast: false - max-parallel: 4 - matrix: - upstream: ${{ fromJSON(needs.rules-go-fork-drift-matrix.outputs.upstreams) }} - runner: - - ubuntu-latest - - ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: ${{ inputs.python_version }} - - - name: Set up Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version: "1.25.0" - - - name: Verify rules_go consumer patch profile - shell: bash - run: | - python3 tools/dev/verify_rules_go_profiles.py \ - --upstream "${{ matrix.upstream }}" \ - --public-denylist tools/dev/private_leak_public_denylist.txt - python3 tools/dev/verify_rules_go_profiles.py \ - --profile bzlmod_runtime \ - --upstream "${{ matrix.upstream }}" \ - --public-denylist tools/dev/private_leak_public_denylist.txt - - rules-go-fork-drift: - needs: - - rules-go-fork-drift-matrix - - rules-go-fork-drift-global - - rules-go-fork-profile-shard - if: ${{ always() && !inputs.docs_only && inputs.run_rules_go_fork_drift }} - runs-on: ubuntu-latest - steps: - - name: Require successful rules_go fork drift shards - shell: bash - env: - GLOBAL_RESULT: ${{ needs.rules-go-fork-drift-global.result }} - MATRIX_RESULT: ${{ needs.rules-go-fork-drift-matrix.result }} - PROFILE_RESULT: ${{ needs.rules-go-fork-profile-shard.result }} - run: | - if [[ "${MATRIX_RESULT}" != "success" ]]; then - echo "rules_go profile matrix generation did not succeed: ${MATRIX_RESULT}" >&2 - exit 1 - fi - - if [[ "${GLOBAL_RESULT}" != "success" ]]; then - echo "Global rules_go fork drift validation did not succeed: ${GLOBAL_RESULT}" >&2 - exit 1 - fi - - if [[ "${PROFILE_RESULT}" != "success" ]]; then - echo "One or more rules_go profile shards did not succeed: ${PROFILE_RESULT}" >&2 - exit 1 - fi - powershell-lint: if: ${{ !inputs.docs_only && inputs.run_powershell_lint }} timeout-minutes: 20 diff --git a/AGENTS.md b/AGENTS.md index fbefc81c..cf9ffa20 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,10 +26,9 @@ The solution separates concerns into four phases: - Agent workflow for Go consumer onboarding: see `tools/agent-skills/go-test-optimization-onboarding/SKILL.md` for the neutral Codex-compatible skill that guides agents through WORKSPACE/Bzlmod Go instrumentation, validation, and troubleshooting. - Agent workflow for Python consumer onboarding: see `tools/agent-skills/python-test-optimization-onboarding/SKILL.md` for the neutral Codex-compatible skill that guides agents through WORKSPACE/Bzlmod Python instrumentation, validation, and troubleshooting. - Agent workflow for Java consumer onboarding: see `tools/agent-skills/java-test-optimization-onboarding/SKILL.md` for the neutral Codex-compatible skill that guides agents through WORKSPACE/Bzlmod Java instrumentation, validation, and troubleshooting. -- Agent workflow for rules_go upstream migrations: see `tools/agent-skills/rules-go-orchestrion-upstream-migration/SKILL.md` for the neutral Codex-compatible skill that guides agents through porting the vendored Orchestrion-enabled `rules_go` fork to a new upstream tag or commit. - Cross-repository integration fixture: see the sibling repository `../rules_test_optimization_tests` and its `README.md` for the consumer-style validation flow that must stay green after changes here. - For local validation of unpublished changes from this repo, switch that fixture repo from its pinned `git_override(...)` entries to the commented `local_path_override(...)` entries in `../rules_test_optimization_tests/MODULE.bazel` so Bazel resolves this checkout instead of GitHub. -- Go fork maintenance details: see `third_party/rgo/v0_60_0/base.METADATA.json`, `third_party/rules_go_orchestrion/registry.json`, `third_party/rules_go_orchestrion/profiles/workspace_runtime.json`, `tools/dev/diff_rules_go_fork.py`, and `tools/dev/verify_rules_go_profiles.py`. +- Go instrumentation: `dd_topt_go_test` builds tests with the Go SDK that `ddtest instrument` instruments. The ddtest release is pinned in `modules/go/topt_go_ddtest_repository.bzl` (`DDTEST_RELEASE`); the ddtest side is documented in https://github.com/DataDog/ddtest/blob/main/ddto/docs/bazel.md. Agents: start with `README.md` for current operational behavior, then `CONTRIBUTING.md` for the maintained validation workflow. When instrumenting a Go, Python, or Java consumer repository, load the matching skill under `tools/agent-skills/` before editing that consumer. Use the overview and RFC when you need architecture details or design rationale/trade-off context. @@ -43,22 +42,22 @@ Agents: start with `README.md` for current operational behavior, then `CONTRIBUT static multi-sync and does not own target discovery or service derivation. - `core/test_optimization_uploader.bzl` — workspace-level uploader rule (normal rule, not test; runs via `bazel run`). - `dev/*_bootstrap.bzl` — dev-only bootstrap extensions wiring the local Go, Python, Java, NodeJS, .NET, and Ruby companion repos from this workspace root. - - `dev/diff_rules_go_fork.py` — maintainer utility that regenerates the delta report for the vendored `rules_go` fork. - `agent-skills/go-test-optimization-onboarding/` — neutral agent skill for instrumenting Go consumer repositories with Test Optimization. - `agent-skills/python-test-optimization-onboarding/` — neutral agent skill for instrumenting Python consumer repositories with Test Optimization. - `agent-skills/java-test-optimization-onboarding/` — neutral agent skill for instrumenting Java consumer repositories with Test Optimization. - - `agent-skills/rules-go-orchestrion-upstream-migration/` — neutral agent skill for porting the vendored Orchestrion-enabled `rules_go` fork to a new upstream version. - `modules/go/` — Go companion module sources: - - `topt_go_test.bzl` — macro wrapping `go_test` with test optimization environment variables. + - `topt_go_test.bzl` — macro wrapping `go_test` with test optimization environment variables, Mini's `testopt` library, and the registration file. + - `topt_go_ddtest.bzl` — public `ddtest_go_test` wrapper, its `sdk_name` transition, the registration rule, and the aspect that rejects a second copy of Mini. + - `topt_go_ddtest_extension.bzl` / `topt_go_ddtest_repository.bzl` — `topt_go_ddtest` Bzlmod extension, WORKSPACE entry point, pinned ddtest release, instrumented SDK, Mini, and toolchain repositories. - `topt_go_infer.bzl` — aspect + rule to infer Go `importpath` via rules_go providers and select per‑module payloads. + - `topt_go_stdlib.bzl` — `dd_topt_go_stdlib_warmup`, which builds the instrumented standard library. + - `tools/` — `dd_topt_go_bootstrap`, onboarding pins, and the registration file generator. - `tests/` — Go-specific Starlark tests and local stub extension for `@test_optimization_data`. - `modules/python/` — Python companion module sources (`topt_py_test.bzl`, `topt_py_infer.bzl`, companion tests). - `modules/java/` — Java companion module sources (`topt_java_test.bzl`, `topt_java_infer.bzl`, companion tests). - `modules/nodejs/` — NodeJS companion module sources (`topt_nodejs_test.bzl`, `topt_nodejs_infer.bzl`, companion tests). - `modules/dotnet/` — .NET companion module sources (`topt_dotnet_test.bzl`, `topt_dotnet_infer.bzl`, companion tests). - `modules/ruby/` — Ruby companion module sources (`topt_ruby_test.bzl`, `topt_ruby_infer.bzl`, companion tests). -- `third_party/rgo/v0_60_0/base/` — vendored `rules_go` fork used by this repository's dev-only root module wiring and the generic Orchestrion integration. -- `third_party/rules_go_orchestrion/profiles/` — public consumer patch profiles generated from the base Orchestrion integration for repositories that need to rebase this repository's changes onto their own `rules_go` patch stack. - Top‑level: `README.md`, `MODULE.bazel`, `WORKSPACE`, `bazelw`. - Consumers depend on `@//:test_optimization_files` or `:module_`; context via `@//:test_optimization_context`. @@ -73,7 +72,8 @@ The sync rule creates `@test_optimization_data//` containing: - **Per-module splitting**: known tests, test management, and flaky tests data are split by module to reduce cache invalidation. - **Sanitization**: module names are converted into Bazel-safe labels using `sanitize_label_fragment()` (lowercase, `[a-z0-9_]` only, deterministic suffixes). - **Go importpath inference**: `topt_go_payloads_selector` mirrors rules_go importpath logic (explicit `importpath` > `embed` provider > fallback `/`). -- **Vendored rules_go forks for root workflows**: the repository root pins `rules_go` as a dev-only dependency and redirects it to `third_party/rgo/v0_60_0/base` with `local_path_override(...)`; consumer-facing core usage remains rules_go-free. +- **Upstream rules_go**: the repository root pins upstream `rules_go` as a dev-only dependency named `io_bazel_rules_go`, the name that the instrumented SDK repositories load; consumer-facing core usage remains rules_go-free. +- **ddtest instrumentation**: an enabled `dd_topt_go_test` builds a hidden raw `go_test` under a transition that sets `@io_bazel_rules_go//go/toolchain:sdk_name` to `ddto`; only the instrumented toolchains match it, so other builds fetch neither the instrumented SDKs nor Mini. - **Cross-platform uploader**: Python 3.10+ provides one implementation on Linux, macOS, and Windows. Small Bash and PowerShell launchers locate Python and Bazel runfiles. The former Bash/curl and PowerShell/.NET uploaders remain @@ -121,10 +121,8 @@ The sync rule creates `@test_optimization_data//` containing: - Typical workflow: edit Starlark, then run the narrowest matching validation command from `CONTRIBUTING.md`. - Python tooling tests: `./bazelw test //tools/tests/python:python_tools_test`. - Release guard: `python3 tools/dev/check_module_versions.py` (core and all companion module version alignment). -- Fork delta refresh: run `python3 tools/dev/diff_rules_go_fork.py --all --write-report` after intentional edits under the base fork. Then run `python3 tools/dev/materialize_rules_go_fork.py check --all` and `python3 tools/dev/verify_rules_go_profiles.py --public-denylist tools/dev/private_leak_public_denylist.txt`. - Cross-repo regression check: after this repository's own validation passes, also validate the change in `../rules_test_optimization_tests` using that repo's documented commands (`./runtests`, `./runtests-hermetic`, or `./bazelw test //src/...`) so consumer-style integration stays intact. - Before running those checks against local changes, update `../rules_test_optimization_tests/MODULE.bazel` to enable the commented `local_path_override(...)` entries for the core module and any companion modules affected by the change. - - If the change touches the vendored `rules_go` fork or Go bootstrap/orchestrion wiring, also add a temporary local override for `rules_go` that points to `../rules_test_optimization/third_party/rgo/v0_60_0/base`. ## Coding Style & Naming Conventions - Starlark: 2‑space indent; `snake_case` for rules/macros/attrs; concise, descriptive docstrings. @@ -147,12 +145,11 @@ The sync rule creates `@test_optimization_data//` containing: - After validating changes in this repo, also run the relevant checks in sibling repo `../rules_test_optimization_tests`. - Minimum expectation: use that repo's documented integration entrypoints (`./runtests`, `./runtests-hermetic`, or `./bazelw test //src/...`) to confirm this repo still works in a consumer-style workspace before calling the change done. - Use local overrides there while validating local work: enable the commented `local_path_override(...)` entries in `../rules_test_optimization_tests/MODULE.bazel` for `datadog-rules-test-optimization` and each affected companion module so the fixture tests the current checkout instead of the pinned GitHub commit. - - When validating changes under `third_party/rgo/v0_60_0/base/`, `third_party/rules_go_orchestrion/profiles/`, or related Go bootstrap/orchestrion wiring, add a temporary `local_path_override(module_name = "rules_go", path = "../rules_test_optimization/third_party/rgo/v0_60_0/base")` in the fixture repo before running its tests. - Local vs CI troubleshooting for `../rules_test_optimization_tests`: - - If local fixture results differ from CI, compare the local environment to CI before assuming the fork is broken. - - Match the host Go version used in CI when reproducing Go or Orchestrion issues. For the current fixture setup, use Go `1.25.0`. - - Reset long-lived local state early when debugging: run `./bazelw shutdown`, then clear the stable Orchestrion cache under `~/Library/Caches/datadog-orchestrion-go-cache`. - - Global Git configuration can change `go mod` and Orchestrion behavior. In particular, HTTPS-to-SSH rewrites or interactive Git prompts can make local runs diverge from CI. + - If local fixture results differ from CI, compare the local environment to CI before assuming the rules are broken. + - Match the Go version used in CI when reproducing Go issues. For the current fixture setup, use Go `1.25.0`. + - Reset long-lived local state early when debugging: run `./bazelw shutdown`. + - Global Git configuration can change `go mod` behavior. In particular, HTTPS-to-SSH rewrites or interactive Git prompts can make local runs diverge from CI. - When results still look suspicious, retry from a fresh checkout or worktree so Bazel analysis state and external repo state are not inherited from earlier experiments. - In consumer workspaces, prefer `./bazelw test //...` when package layout permits. - Tests write payloads to `$TEST_UNDECLARED_OUTPUTS_DIR/payloads/{tests,coverage}` (Bazel's built-in writable directory). @@ -172,6 +169,7 @@ The sync rule creates `@test_optimization_data//` containing: ## Consumer Tips (bzlmod) - In `MODULE.bazel`: add `bazel_dep("datadog-rules-test-optimization", ...)` and `bazel_dep("datadog-rules-test-optimization-go", ...)`, then use `test_optimization_go_extension` from `@datadog-rules-test-optimization-go//:topt_go_extension.bzl`. Instantiate `test_optimization_go(name = "test_optimization_data", service = "", runtime_version = "", module_path = "")`, then `use_repo(..., "test_optimization_data")`. The public Go extension is config-gated by default. +- Configure the Go SDK that `ddtest instrument` instruments: `topt_go_ddtest = use_extension("@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", "topt_go_ddtest")` and `topt_go_ddtest.sdk(go_mod = "//:go.mod")` (or `go_version = ""`). The Go companion registers the toolchains. A workspace whose `go.mod` requires `github.com/DataDog/ddtest/ddto` adds `use_repo(topt_go_ddtest, "topt_go_ddtest_mini")` and `override_repo(go_deps, com_github_datadog_ddtest_ddto = "topt_go_ddtest_mini")`. - In a small root package or a lightweight monorepo package, create the workspace-level doctor and uploader: ```bzl load("@datadog-rules-test-optimization//tools/core:test_optimization_doctor.bzl", "dd_test_optimization_doctor") @@ -193,21 +191,20 @@ The sync rule creates `@test_optimization_data//` containing: - Tests can read `DD_TEST_OPTIMIZATION_MANIFEST_FILE` to resolve the manifest directory (via `filepath.Dir()`) and access synced payloads. - For Python/Java/NodeJS/.NET/Ruby companions, follow the corresponding quickstart sections in `README.md` (`Bzlmod + Python companion`, `Bzlmod + Java companion`, `Bzlmod + NodeJS companion`, `Bzlmod + .NET companion`, `Bzlmod + Ruby companion`). -Note: Core module (`datadog-rules-test-optimization`) is rules-go free. The Go companion module declares the `rules_go` dependency to load provider definitions; consumers still configure Go SDK/toolchains in their own `MODULE.bazel`. +Note: Core module (`datadog-rules-test-optimization`) is rules-go free. The Go companion module declares the `rules_go` dependency (as `io_bazel_rules_go`) and registers the instrumented Go toolchains; consumers still configure their own Go SDK/toolchains for ordinary Go targets in their own `MODULE.bazel`. ## Maintainer Architecture Map - Core ownership (`tools/core/*`): runtime-agnostic sync + uploader + shared helpers; keep it free from non-dev language-rule dependencies. -- Go ownership (`modules/go/*`): Go macro/aspect/selector and Go-specific tests. +- Go ownership (`modules/go/*`): Go macro/aspect/selector, the ddtest instrumentation repositories and toolchains, the bootstrap, and Go-specific tests. - Python ownership (`modules/python/*`): Python macro/inference rule and companion tests. - Java ownership (`modules/java/*`): Java macro/inference rule and companion tests. - NodeJS ownership (`modules/nodejs/*`): NodeJS macro/inference rule and companion tests. - .NET ownership (`modules/dotnet/*`): .NET macro/inference rule and companion tests. - Ruby ownership (`modules/ruby/*`): Ruby macro/inference rule and companion tests. -- Vendored fork ownership (`third_party/rgo/*/base/*`, `third_party/rgo/*/base.METADATA.json`, `third_party/rgo/*/base.CHANGED_FILES.md`, `third_party/rules_go_orchestrion/patches/*/base*`): in-repo `rules_go` base forks used by the repository root and consumer validation flows; when modifying them, keep the metadata, changed-files reports, materialization checks, and profile verifier in sync. - Bootstrap ownership (`tools/dev/*_bootstrap.bzl`): dev-only local companion wiring for root workspace testing; do not convert bootstrap wiring into module dependency edges. - Invariants: - root module must not `bazel_dep` the Go companion module (avoid `core -> go -> core` cycle), - - root module keeps `rules_go` as a dev-only dependency and redirects it to `third_party/rgo/v0_60_0/base` via `local_path_override(...)` (not consumer-facing core behavior), + - root module keeps upstream `rules_go` as a dev-only dependency named `io_bazel_rules_go` (not consumer-facing core behavior), - Go companion must depend on core and `rules_go`, - non-Go companion modules are also wired into the root workspace through dev-only `tools/dev/*_bootstrap.bzl` extensions instead of root `bazel_dep(...)` edges, - public generated labels in synced repos remain stable (`test_optimization_files`, `test_optimization_context`, `module_`). @@ -219,10 +216,6 @@ Note: Core module (`datadog-rules-test-optimization`) is rules-go free. The Go c - Verify the matching `//tools/dev:*_bootstrap.bzl` extension call and `use_repo(...)` entry still exist in root `MODULE.bazel`. - Symptom: companion tests resolve released core instead of local core. - Run companion tests with `--override_module=datadog-rules-test-optimization=../..`. -- Symptom: repo-root Go/example builds stop reflecting the in-tree fork. - - Verify root `MODULE.bazel` still has `bazel_dep(name = "rules_go", ..., dev_dependency = True)` plus `local_path_override(module_name = "rules_go", path = "third_party/rgo/v0_60_0/base")`. -- Symptom: fork metadata report or consumer patch profile no longer matches the base `rules_go` fork. - - Run `python3 tools/dev/diff_rules_go_fork.py --all --write-report`, review the matching changed-files reports, then run `python3 tools/dev/materialize_rules_go_fork.py check --all` and `python3 tools/dev/verify_rules_go_profiles.py --public-denylist tools/dev/private_leak_public_denylist.txt`. ## Multi‑Service Usage - Use `test_optimization_multi_sync_extension` (`@...//tools/core:test_optimization_multi_sync.bzl`) with `services = ["", ""]` to fetch multiple services at once. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac91b51..6ef5b683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,102 @@ versioning. ## [Unreleased] +### Changed + +- Go tests are instrumented by `ddtest instrument` instead of Orchestrion. This + is a breaking change for Go consumers. `dd_topt_go_test` builds every enabled + test with the Go SDK that `ddtest instrument` instruments, adds Mini's + `testopt` library and a registration file to the hidden raw `go_test` (Mini + is ddtest's Test Optimization runtime), and selects the instrumented + toolchains through the public test's transition. Builds without instrumented + tests fetch neither the instrumented SDKs nor Mini, and the instrumentation + needs no host Go. The ddtest release is pinned in + `modules/go/topt_go_ddtest_repository.bzl`. +- Bzlmod root modules configure the SDK to instrument with + `topt_go_ddtest.sdk(...)` (`go_mod`, `go_version`, `base_sdk_roots`, + `exec_platforms`, `experiments`, `panic_hook`); the Go companion registers + the toolchains. The instrumented toolchains honor + `@io_bazel_rules_go//go/toolchain:sdk_version`; more `sdk` tags with + `sdk_version` add SDK variants, such as a FIPS SDK, for Go targets that a + transition below an instrumented test moves to another SDK, and any other + value fails with what to configure. WORKSPACE consumers call + `topt_go_ddtest_workspace_repositories(...)` after `go_rules_dependencies()`. +- The Go companion uses upstream `rules_go` from the Bazel Central Registry, + `0.60.0` or newer, under the repository name `io_bazel_rules_go`. CI covers + `rules_go` `0.60.0`, `0.61.1`, `0.62.0`, `0.63.0`, `0.64.2`, and `0.65.0`. +- `datadog_go_test_optimization_workspace_repositories` declares only the Go + companion and fails when `rules_go` is not declared yet. Declare upstream + `rules_go` as `io_bazel_rules_go` before calling it. +- Go target metadata reports `bazel.go.instrumentation.backend`, + `bazel.go.instrumentation.version`, `bazel.go.instrumentation.release`, + `bazel.go.instrumentation.contract`, `bazel.go.sdk.name`, and + `bazel.go.sdk.version` instead of `bazel.go.orchestrion.enabled` and + `bazel.go.orchestrion.mode`. +- The test-binary linker flags (`-s -w` where `rules_go` does not already + strip) apply to every enabled Go test by default. `no-remote-exec` stays on + the `TestRunner` action only. +- `dd_topt_go_stdlib_warmup` always builds the instrumented standard library, + with or without `--config=test-optimization`. +- `dd_topt_go_bootstrap` writes a managed `topt_go_ddtest.sdk(...)` block + (`go_version` from `--runtime-version`, otherwise `go_mod`) and adds + `bazel_dep(name = "rules_go", version = "0.60.0")` when it is missing. It + writes no `rules_go` override and runs no Go commands. Its WORKSPACE snippet + declares upstream `rules_go` first and calls + `topt_go_ddtest_workspace_repositories`. Published onboarding pins contain + only the six `RTO_*` values. + +### Removed + +- Orchestrion support: the `orchestrion_mode` and `orchestrion_pin_files` + attributes of `dd_topt_go_test`, which now fail with a migration message; + the `orchestrion` extension and `dd_topt_go_orchestrion_tool_repo`; pin + files; `orchestrion.tool.go` and `orchestrion.yml`; and `dd-trace-go` version + pinning in `go.mod`. +- The vendored `rules_go` forks (`third_party/rgo`, + `third_party/rules_go_orchestrion`), their registry, consumer patch profiles, + tooling, and CI jobs, and the fork blocks of release bundles. The WORKSPACE + helper no longer accepts `rules_go_fetch`, `rules_go_upstream`, or + `rules_go_variant`. +- Bootstrap flags `--write-orchestrion-files`, `--orchestrion-version`, + `--dd-trace-go-version`, `--rules-go-remote`, `--rules-go-commit`, + `--rules-go-upstream`, `--rules-go-variant`, `--rules-go-fetch`, + `--check-go-repositories`, `--print-go-repository-updates`, + `--go-repositories-file`, `--go-repositories-refresh-command`, + `--go-binary`, and `--go-mod-sync`, and the `--variant`, + `--rules-go-upstream`, `--dd-trace-go-version`, and `--orchestrion-version` + flags of `print_go_onboarding_pins`. +- The Orchestrion and `rules_go` fork documents and the + `rules-go-orchestrion-upstream-migration` agent skill. + +### Migrating Go consumers + +- Remove `orchestrion_mode` and `orchestrion_pin_files` from `dd_topt_go_test` + calls and wrappers. +- Remove the `rules_go` override that points at the fork + (`third_party/rgo/...`) and the `orchestrion` extension with its + `rules_go_orchestrion_tool` repository, and depend on upstream `rules_go` + `0.60.0` or newer. In WORKSPACE, declare upstream `rules_go` as + `io_bazel_rules_go`, drop the fork arguments of + `datadog_go_test_optimization_workspace_repositories`, and remove the + `dd_topt_go_orchestrion_tool_repo` call. +- Delete `orchestrion.tool.go` and `orchestrion.yml`, and the Orchestrion and + `dd-trace-go` requirements in `go.mod` that only served as pins. +- Add `topt_go_ddtest.sdk(go_mod = "//:go.mod")` to the root `MODULE.bazel`, + or call `topt_go_ddtest_workspace_repositories(go_version = ...)` in + WORKSPACE. +- Or rerun `dd_topt_go_bootstrap`: it replaces the + `# BEGIN Datadog Go Orchestrion bootstrap` block with the new managed block, + and stops if an unmanaged `rules_go` override still points at the fork. If + the old block's fork commit no longer loads, run the bootstrap from another + workspace with `--workspace `, or delete the block by hand. Old + `# BEGIN Datadog Go Pin Files` blocks in BUILD files are harmless and can be + deleted. +- If `go.mod` requires `github.com/DataDog/ddtest/ddto`, replace `go_deps`' + copy with the generated Mini: `use_repo(topt_go_ddtest, "topt_go_ddtest_mini")` + and `override_repo(go_deps, com_github_datadog_ddtest_ddto = "topt_go_ddtest_mini")`. +- Anything that read `bazel.go.orchestrion.*` from target metadata now reads + `bazel.go.instrumentation.*` and `bazel.go.sdk.*`. + ## [1.4.0] - 2026-10-29 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2119e2b2..e9b44067 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,23 +62,12 @@ This product includes software developed at Datadog - Python tooling tests: - `bash tools/tests/python/run_python_tools_test.sh` - `./bazelw test //tools/tests/python:python_tools_test` -- rules_go variant verification: - - `python3 tools/dev/generate_rules_go_fork_maps.py --check` - - `python3 tools/dev/materialize_rules_go_fork.py check --all` - - `python3 tools/dev/verify_rules_go_profiles.py --public-denylist tools/dev/private_leak_public_denylist.txt` - - `python3 tools/dev/verify_rules_go_profiles.py --profile bzlmod_runtime --public-denylist tools/dev/private_leak_public_denylist.txt` - applies the generated patch to clean upstream rules_go and runs the same - instrumented CGO/reproducibility smoke with Bzlmod enabled and WORKSPACE - disabled. CI runs both profiles for every maintained upstream. - - `python3 tools/dev/check_release_archive_contents.py` - - `python3 tools/dev/diff_rules_go_fork.py --all --write-report` - Optional Python tooling dependencies (for local script execution): - `python3 -m pip install --require-hashes -r tools/requirements.txt` - Local lint prerequisites (match CI tooling): - `shellcheck` (shell lint lane) - `buildifier` (Starlark formatting lane) - `jq` (integration harness payload/CODEOWNERS checks) - - `zstd` (rules_go compact execution-log verification) - Optional pre-commit setup: - `python3 -m pip install pre-commit && pre-commit install` - Optional Python syntax smoke check when editing tooling: @@ -103,88 +92,56 @@ This product includes software developed at Datadog `--config=test-optimization` as the only user-facing switch. The shared config entry is `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`. - - Go also sets the existing `rules_go` Orchestrion `enabled=true` - build setting. Python-only consumers must not declare that Go-only label. + - Go needs no other build setting: an instrumented test selects the Go SDK + that `ddtest instrument` instruments through its own transition. - Omitting the config is the documented complete opt-out for Go and Python: - metadata repositories use disabled stubs when `enabled_by_env = True`, Go - aliases select local empty targets, and Python keeps the consumer runner - without Test Optimization wiring. + metadata repositories use disabled stubs when `enabled_by_env = True`, + `dd_topt_go_test` creates a plain `go_test`, and Python keeps the consumer + runner without Test Optimization wiring. - Other companions retain their existing enablement contract. Do not add `enabled_by_env = True` to their sync repositories until their runtime wrapper implements and tests the disabled export contract. - - Do not add a consumer-local Test Optimization bool flag or a second Go - Orchestrion repository chooser. + - Do not add a consumer-local Test Optimization bool flag. - Go consumer integration harnesses: - - Bzlmod default smoke: - `tools/tests/integration/run_bzlmod_go_integration.sh` - - WORKSPACE default smoke: - `tools/tests/integration/run_workspace_go_integration.sh` - - WORKSPACE base, rules_go v0_60_0: - `USE_BAZEL_VERSION=8.8.2 RULES_GO_UPSTREAM=v0_60_0 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh` - - Bzlmod base, rules_go v0_60_0: - `USE_BAZEL_VERSION=8.8.2 RULES_GO_UPSTREAM=v0_60_0 RULES_GO_VARIANT=base tools/tests/integration/run_bzlmod_go_integration.sh` - - WORKSPACE base, rules_go v0_61_1: - `USE_BAZEL_VERSION=8.8.2 RULES_GO_UPSTREAM=v0_61_1 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh` - - Bzlmod base, rules_go v0_61_1: - `USE_BAZEL_VERSION=8.8.2 RULES_GO_UPSTREAM=v0_61_1 RULES_GO_VARIANT=base tools/tests/integration/run_bzlmod_go_integration.sh` - - WORKSPACE base, rules_go v0_62_0: - `USE_BAZEL_VERSION=8.8.2 RULES_GO_UPSTREAM=v0_62_0 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh` - - Bzlmod base, rules_go v0_62_0: - `USE_BAZEL_VERSION=8.8.2 RULES_GO_UPSTREAM=v0_62_0 RULES_GO_VARIANT=base tools/tests/integration/run_bzlmod_go_integration.sh` - - WORKSPACE base, rules_go v0_63_0: - `USE_BAZEL_VERSION=8.8.2 RULES_GO_UPSTREAM=v0_63_0 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh` - - Bzlmod base, rules_go v0_63_0: - `USE_BAZEL_VERSION=8.8.2 RULES_GO_UPSTREAM=v0_63_0 RULES_GO_VARIANT=base tools/tests/integration/run_bzlmod_go_integration.sh` - - Disabled alias gate for a fresh output root: - `WINDOWS_DISABLED_SMOKE_ONLY=1 RULES_GO_UPSTREAM=v0_60_0 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh` - - Enabled alias and payload gate with valid Orchestrion pins: - `WINDOWS_ENABLED_SMOKE_ONLY=1 RULES_GO_UPSTREAM=v0_60_0 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh` - - Same-output-root disabled then enabled transition for the public central - wrapper: - `CONFIG_TRANSITION_ONLY=1 RULES_GO_UPSTREAM=v0_60_0 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh` - - Cold bootstrap without host Go, using the Bazel-managed SDK and an isolated - bootstrap cache: - `cache_root="$(mktemp -d)"; CONFIG_TRANSITION_ONLY=1 FORBID_HOST_GO=1 EXPECTED_ORCHESTRION_CACHE_PHASE=extensions.bootstrap_cache_miss XDG_CACHE_HOME="$cache_root" RULES_GO_UPSTREAM=v0_60_0 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh` - - Repeat that command with the same `XDG_CACHE_HOME` and - `EXPECTED_ORCHESTRION_CACHE_PHASE=extensions.bootstrap_cache_hit` to prove - warm restoration also avoids host Go. Run both commands for WORKSPACE and - Bzlmod; CI covers both supported `rules_go` upstreams. - - Each script now validates: - - the same consumer-owned central wrapper call expands to a raw `go_test` - without the named config and to the existing Orchestrion-backed shape - with it - - disabled then enabled resolution on one fixture workspace and output - root, with no intervening clean or shutdown - - no hidden Test Optimization targets, empty Orchestrion aliases, exact - disabled stubs, zero metadata HTTP requests, and no payload while - disabled - - normal mode - - hermetic mode with the inline CI sandbox/network-blocking flags + - Bzlmod: `tools/tests/integration/run_bzlmod_go_integration.sh` + - WORKSPACE: `tools/tests/integration/run_workspace_go_integration.sh` + - Both scripts build a temporary consumer from this checkout with upstream + `rules_go` and the Go SDK that `ddtest instrument` instruments. Bzlmod + consumes the repository through `archive_override(...)`; WORKSPACE runs a + local and an archive fixture. + - `RULES_GO_VERSION` (default `0.60.0`) selects upstream `rules_go`: a BCR + `bazel_dep` in Bzlmod, or the release zip with its BCR integrity in + WORKSPACE. `GO_VERSION` (default `1.25.0`) selects the Go SDK. `rules_go` + `0.65.0` requires Go 1.26: + `USE_BAZEL_VERSION=8.8.2 RULES_GO_VERSION=0.65.0 GO_VERSION=1.26.3 tools/tests/integration/run_workspace_go_integration.sh` + - A new `rules_go` version in the WORKSPACE script needs its integrity in + `rules_go_integrity`. + - `FORBID_HOST_GO=1` shadows the host `go` with a failing sentinel (Unix + only). ddtest needs no host Go, and CI runs every shard this way. + - `WINDOWS_SMOKE_ONLY=1` (Bzlmod) runs only the instrumented test and its + payload check, for Windows hosts without the hermetic sandbox: + `WINDOWS_SMOKE_ONLY=1 tools/tests/integration/run_bzlmod_go_integration.sh` + - `INTEGRATION_SCENARIO_MODE=release` runs only the archive fixture in + WORKSPACE; `full` (default) runs both. + - Set `KEEP_TMP=1` to keep the generated workspaces after a failure. + - Each script validates: + - normal mode, and hermetic mode with the inline CI sandbox/network-blocking flags + - JSON payloads, the `bazel.go.instrumentation.*` and `bazel.go.sdk.*` + target metadata, and the uploader dry run - strict BEP fresh/cached uploader behavior - - structural `aquery` assertions for the Orchestrion offline module proxy wiring - - For Go Orchestrion changes, run each relevant script with both - `ORCHESTRION_MODE=general` and `ORCHESTRION_MODE=test_optimization`; the - optimized mode is the standard Go `testing` Test Optimization path and - should still produce payloads. -- Vendored rules_go variant smoke: - - `RULES_GO_UPSTREAM=v0_60_0 RULES_GO_VARIANT=base tools/dev/run_rules_go_variant_smoke.sh` - - when adding a new upstream, run the same commands with - `RULES_GO_UPSTREAM=` - - copies the selected variant into a temp tree, applies the maintainer-only - proof overlay, and runs the fast vendored-fork regression set -- Vendored rules_go extended variant coverage: - - `RULES_GO_UPSTREAM=v0_60_0 RULES_GO_VARIANT=base tools/dev/run_rules_go_variant_extended.sh` - - runs the slower extended regression set against the selected variant + - structural `aquery` assertions (`assert_ddtest_aquery.py`) in the + default, `opt`, and no-strip modes: compiles, link, and standard library + use the instrumented SDK, the compile gets the registration file, the + link gets Mini's `testopt`, and only the `TestRunner` has `no-remote-exec` + - (Bzlmod) a build of the library alone fetches neither the instrumented + SDK nor Mini, and contacts no metadata server while Test Optimization is + disabled - Hermetic smoke (mirror CI flags): - run the same test commands with sandbox/network-blocking flags from `.github/workflows/ci.yml` - Cross-repo fixture validation for mixed-runtime changes: - In `../rules_test_optimization_tests/MODULE.bazel`, temporarily enable the documented `local_path_override(...)` entries for core and each affected companion module so the fixture repo resolves this checkout. - - If the change touches a rules_go fork tree or related Go - bootstrap/orchestrion wiring, also add a temporary - `local_path_override(module_name = "rules_go", path = "../rules_test_optimization/")` - there so the sibling repo resolves the selected local fork. - For BEP artifact-resolution changes, run the Go fixture scripts with `RTO_LOCAL_ARCHIVE=1 RTO_BEP_ARTIFACT_STAGING=1` so the fixture validates staged BEP artifacts instead of only the pinned release/local-output path: @@ -218,29 +175,19 @@ This product includes software developed at Datadog - core tests with hermetic flags - companion module tests with hermetic flags - scope policy: Linux-only by design today; non-Linux hermetic expansion is tracked separately to keep CI runtime bounded -- `workspace-compat`: - - one shard per supported `rules_go` upstream and WORKSPACE/Bzlmod pair - - general and Test Optimization modes run sequentially inside each shard +- `workspace-compat-shard (rules_go , )`: + - one shard per supported `rules_go` version (`0.60.0`, `0.61.1`, `0.62.0`, + `0.63.0`, `0.64.2`, `0.65.0`) and WORKSPACE/Bzlmod pair, with + `FORBID_HOST_GO=1`; `0.65.0` runs with Go `1.26.3` - the Go integration scripts themselves cover normal mode, hermetic mode, and structural `aquery` checks -- `go-bootstrap-no-host`: - - one shard per supported `rules_go` upstream and WORKSPACE/Bzlmod pair - - installs Bazelisk without Go and shadows any hosted-runner `go` binary with - a failing sentinel - - proves both a cold Bazel-managed-SDK build and warm bootstrap-cache restore - without invoking host Go -- `rules-go-variant-smoke`: - - vendored `rules_go` variant verification and fast fork regression coverage - - one independent shard per supported upstream - - Linux-only by design so the PR gate stays fast and stable -- `rules-go-variant-extended`: - - nightly/manual vendored `rules_go` variant coverage for slower XML, proto, cross, and cgo regression suites +- `workspace-compat`: + - requires every `workspace-compat-shard` to succeed when full CI runs +- `windows-go-smoke`: + - runs `WINDOWS_SMOKE_ONLY=1 tools/tests/integration/run_bzlmod_go_integration.sh` + on Windows: one instrumented Go test and its payload check - Utility/lint lanes: - module version alignment check (`tools/dev/check_module_versions.py`) - `.bazelversion` parity check (`tools/dev/check_bazelversion_sync.py`) - - global fork drift checks plus x86-64 and ARM64 consumer patch-profile shards - for every supported `rules_go` upstream; each profile shard rebuilds the - cache-critical Test Optimization actions twice from isolated output roots - and requires identical action keys and output bytes - shell scripts, PowerShell, Buildifier, gofmt, schema sync checks, fixture JSON checks, and Python tooling tests - Workflow dependency pinning: - Keep GitHub Actions pinned by commit SHA and preserve the `# vX.Y.Z` comment. @@ -292,9 +239,9 @@ This product includes software developed at Datadog - [ ] For manifest-driven changes, proved no committed target/service mapping, exact target-set validation, disabled no-fetch behavior, and deterministic rendering. -- [ ] For Go Orchestrion changes, documented any `orchestrion_mode` behavior, - pin-file or explicit-version behavior, unsupported `testify/suite` scope, - opt-out flags, and payload metadata changes. +- [ ] For Go instrumentation changes, documented any change to the pinned + ddtest release, the `topt_go_ddtest` SDK configuration, opt-out flags, and + payload metadata. - [ ] Updated `LICENSE-3rdparty.csv` for dependency or vendored-code changes. - [ ] Confirmed no stale references to removed legacy paths (for example `//tools/go:*`, replaced by `modules/go/...` targets). @@ -313,8 +260,8 @@ This product includes software developed at Datadog Review the generated notes/assets, then publish explicitly. - The draft contains `rules_test_optimization-X.Y.Z.tar.gz`, `SHA256SUMS`, `release.json`, `INSTALL.md`, and `RELEASE_NOTES.md`. Notes come from the - matching `CHANGELOG.md` section. Installation blocks cover core, all six - companions, and every maintained Go fork, with one archive and checksum. + matching `CHANGELOG.md` section. Installation blocks cover core and all six + companions, with one archive and checksum. - The archive contains only the committed revision, including executable modes. Packaging uses a fixed gzip header, so repeated builds of the same revision with the same toolchain produce the same bytes. The workflow diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 702f7c3d..e61a1253 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -1,5 +1,4 @@ Component,Origin,License,Copyright -Bazel rules_go fork,https://github.com/bazel-contrib/rules_go.git,Apache-2.0,"The Bazel Authors and rules_go contributors" Bazel module abseil-cpp,https://github.com/abseil/abseil-cpp,Apache-2.0,"The Abseil Authors" Bazel module aspect_bazel_lib,https://github.com/aspect-build/bazel-lib,Apache-2.0,"Aspect Build and contributors" Bazel module aspect_rules_js,https://github.com/aspect-build/rules_js,Apache-2.0,"Aspect Build and contributors" diff --git a/MODULE.bazel b/MODULE.bazel index b62b2f32..20dad75c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -20,11 +20,9 @@ bazel_dep(name = "rules_shell", version = "0.6.1", dev_dependency = True) # Dev-only dependency for in-repo example builds that compile Go targets. # Consumer-facing core usage remains rules_go-free. -bazel_dep(name = "rules_go", version = "0.60.0", dev_dependency = True) -local_path_override( - module_name = "rules_go", - path = "third_party/rgo/v0_60_0/base", -) +# The Go companion, which the root loads as a local repository, and the SDK +# repositories that go_wrap_sdk writes load rules_go as @io_bazel_rules_go. +bazel_dep(name = "rules_go", version = "0.60.0", dev_dependency = True, repo_name = "io_bazel_rules_go") # Dev-only dependency for in-repo Python companion/example builds. bazel_dep(name = "rules_python", version = "1.7.0", dev_dependency = True) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index c002562f..6fbda7aa 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -19,6 +19,7 @@ "https://bcr.bazel.build/modules/aspect_rules_js/3.0.0-rc5/source.json": "475557aa324a56abd65dfcadb1d643bfba3d0e8605d4063e9edefdcdeba21855", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/MODULE.bazel": "37c764292861c2f70314efa9846bb6dbb44fc0308903b3285da6528305450183", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/source.json": "605086bbc197743a0d360f7ddc550a1d4dfa0441bc807236e17170f636153348", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", @@ -57,6 +58,9 @@ "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/source.json": "fa7b512dfcb5eafd90ce3959cf42a2a6fe96144ebbb4b3b3928054895f2afac2", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", "https://bcr.bazel.build/modules/gazelle/0.36.0/source.json": "0823f097b127e0201ae55d85647c94095edfe27db0431a7ae880dcab08dfaa04", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", @@ -89,6 +93,7 @@ "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", @@ -115,6 +120,11 @@ "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", + "https://bcr.bazel.build/modules/rules_go/0.60.0/MODULE.bazel": "4a57ff2ffc2a3570e3c5646575c5a4b07287e91bcdac5d1f72383d51502b48cb", + "https://bcr.bazel.build/modules/rules_go/0.60.0/source.json": "1e21368c5e0c3013a110bd79a8fcff8ca46b5bcb2b561713a7273cbfcff7c464", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", @@ -748,25 +758,6 @@ } } }, - "@@rules_go+//go:extensions.bzl%orchestrion": { - "general": { - "bzlTransitiveDigest": "VBxT0TR3yQjNK70jnzJBPvKgC2eoWu9XW3/ulK9xdBI=", - "usagesDigest": "9kKEG/hjK4fnk42l4jAKDLhx9xExyCYAS8r8NlbPLMw=", - "recordedInputs": [ - "REPO_MAPPING:bazel_features+,bazel_features_globals bazel_features++version_extension+bazel_features_globals", - "REPO_MAPPING:bazel_features+,bazel_features_version bazel_features++version_extension+bazel_features_version", - "REPO_MAPPING:rules_go+,bazel_tools bazel_tools", - "REPO_MAPPING:rules_go+,io_bazel_rules_go rules_go+", - "REPO_MAPPING:rules_go+,io_bazel_rules_go_bazel_features bazel_features+" - ], - "generatedRepoSpecs": { - "rules_go_orchestrion_tool": { - "repoRuleId": "@@rules_go+//go/private/orchestrion:extensions.bzl%_orchestrion_empty", - "attributes": {} - } - } - } - }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { "bzlTransitiveDigest": "03Qju4tW0vE+0RBuZGuV2A4Hx6AiSkdNahYvworx2aM=", diff --git a/README.md b/README.md index 16a58606..a12e841f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This product includes software developed at Datadog This repository provides Bazel integrations that fetch Datadog Test Optimization metadata during module/repository resolution and materialize JSON files for use in your build. It also generates public filegroups so consumers can depend on stable labels instead of wiring files manually. -> `v1.4.0` is in preparation; `v1.3.0` remains the latest published release. For releases with an `INSTALL.md` asset, copy its checksum-pinned `archive_override` (Bzlmod) or `http_archive` (WORKSPACE) blocks. Keep core, companions, and the Go fork on the same archive. `v1.3.0` predates those assets; its commit-based installation remains supported. See [release installation](docs/Installation_Reference.md#option-a-install-from-a-release-archive-recommended). +> `v1.4.0` is in preparation; `v1.3.0` remains the latest published release. For releases with an `INSTALL.md` asset, copy its checksum-pinned `archive_override` (Bzlmod) or `http_archive` (WORKSPACE) blocks. Keep core and companions on the same archive. `v1.3.0` predates those assets; its commit-based installation remains supported. See [release installation](docs/Installation_Reference.md#option-a-install-from-a-release-archive-recommended). ## Onboarding paths @@ -105,8 +105,7 @@ are informative and may lag temporarily. - [`README.md`](README.md): fast onboarding, first-run checklist, and scenario quickstarts. - [`docs/Language_Onboarding.md`](docs/Language_Onboarding.md): - language-by-language recipes, including Go/Orchestrion and large WORKSPACE - monorepos. + language-by-language recipes, including Go and large WORKSPACE monorepos. - [`docs/Installation_Reference.md`](docs/Installation_Reference.md): complete installation reference for Bzlmod, WORKSPACE, bootstrap flags, archive pins, and generated Bazel config. @@ -116,7 +115,7 @@ are informative and may lag temporarily. enrichment, uploader behavior, credentials, endpoints, and upload modes. - [`docs/Troubleshooting.md`](docs/Troubleshooting.md): operational debugging for sync, payload discovery, doctor failures, enrichment, Git/Bazel metadata, - and Go tracer drift. + and Go tests instrumented by `ddtest instrument`. - [`docs/Initial_documentation.md`](docs/Initial_documentation.md): current architecture overview and data-flow diagrams. - [`docs/RFC.md`](docs/RFC.md): historical design rationale and trade-offs. @@ -135,61 +134,11 @@ are informative and may lag temporarily. - [`tools/agent-skills/java-test-optimization-onboarding/SKILL.md`](tools/agent-skills/java-test-optimization-onboarding/SKILL.md): neutral Codex-compatible agent workflow for instrumenting Java WORKSPACE and Bzlmod consumers with Test Optimization. -- [`tools/agent-skills/rules-go-orchestrion-upstream-migration/SKILL.md`](tools/agent-skills/rules-go-orchestrion-upstream-migration/SKILL.md): - neutral Codex-compatible agent workflow for porting the vendored - Orchestrion-enabled `rules_go` fork to a new upstream tag or commit. - [`docs/internal_monorepo_go_rollout_guide.md`](docs/internal_monorepo_go_rollout_guide.md): operator checklist for large WORKSPACE Go pilot rollouts. -- [`docs/go_orchestrion_bazel_deep_dive.md`](docs/go_orchestrion_bazel_deep_dive.md): - maintainer deep dive for the Go + Orchestrion Bazel pipeline. -- [`docs/go_orchestrion_maintainer_state.md`](docs/go_orchestrion_maintainer_state.md): - current Go/Orchestrion maintainer state, kept optimizations, reverted - experiments, and next performance directions. -- [`docs/rules_go_orchestrion_support_selection.md`](docs/rules_go_orchestrion_support_selection.md) - and [`docs/rules_go_variant_maintenance_guide.md`](docs/rules_go_variant_maintenance_guide.md): - how to maintain supported `rules_go` upstream versions and generated - consumer patch profiles. -- [`docs/rules_go_orchestrion_performance_analysis.md`](docs/rules_go_orchestrion_performance_analysis.md) - and [`docs/rules_go_orchestrion_probe_measurements.md`](docs/rules_go_orchestrion_probe_measurements.md): - performance analysis and measured probe history for the vendored Go fork. - [`SECURITY.md`](SECURITY.md): vulnerability reporting and disclosure policy. - [`CHANGELOG.md`](CHANGELOG.md): release notes and unreleased changes. -## Maintainer note on the vendored rules_go fork - -The repository publishes the Go integration as complete base `rules_go` trees. -`rules_go_variant` currently supports only: - -- `base`: generic Orchestrion-enabled support. -- `tools/tests/rules_go_variant_regressions`: maintainer-only regression - overlay copied into temporary smoke-test trees. - -When multiple upstream `rules_go` versions are supported, `rules_go_upstream` -selects the upstream support line. Omitting `rules_go_upstream` preserves the -repository default. The default `rules_go_upstream` is currently `v0_60_0`, -which preserves the existing `third_party/rgo/v0_60_0/base` path. -The registry also supports `v0_61_1`, `v0_62_0`, and `v0_63_0`. - -Maintainers track each supported upstream version with both patch series under -`third_party/rules_go_orchestrion/patches//` and materialized base -trees under the registry-selected `tree_path`. The patch series is the -maintainer source for rebasing. The materialized tree is the consumer artifact. -CI verifies that they match. - -Public consumer patch profiles live under `third_party/rules_go_orchestrion/profiles/`. -They are generated and verified by `tools/dev/generate_rules_go_consumer_patch.py` -and `tools/dev/verify_rules_go_profiles.py` so consumers that already own a -`rules_go` patch stack can generate a sparse, profile-specific input and rebase -or merge it locally instead of vendoring a second complete `rules_go` tree. - -Select `--profile workspace_runtime` for WORKSPACE consumers or -`--profile bzlmod_runtime` for Bzlmod consumers. The Bzlmod profile includes -`MODULE.bazel` and the public `go/extensions.bzl` alias. Both the consumer and -patched rules_go must use `go:extensions.bzl`'s `orchestrion` extension so they -share the same tool repository. A root-only `use_repo` does not make that -repository visible inside rules_go. Neither profile includes upstream lockfile -changes or repository tests. - ## First-run checklist (all scenarios) Use this checklist before your first CI rollout: @@ -200,8 +149,9 @@ config must include public Go bootstrap helpers apply metadata gating by default. Manual Go extension wiring uses the same config-gated default. Direct use of the low-level core sync API in a config-gated Go or Python setup must opt into -`enabled_by_env = True`. Go optimized targets enable Orchestrion through their -own transition; no global `orchestrion:enabled` build setting is required. +`enabled_by_env = True`. Instrumented Go tests select the Go SDK that +`ddtest instrument` instruments through their own transition; no global build +setting is required. Removing `--config=test-optimization` provides the complete metadata and runtime opt-out for the Go and Python integrations described below. This release does not change the enablement contract of the other companions. @@ -210,7 +160,7 @@ When the selected Go sync export is disabled, `dd_topt_go_test` validates its macro-only inputs and selected service, then calls the supplied `go_test_rule` directly under the public target name with the caller's original Go rule arguments. It does not create the hidden raw test, payload selector, Bazel -metadata, Orchestrion pin, or public wrapper targets. This lets a consumer keep +metadata, registration, or public wrapper targets. This lets a consumer keep one central `dd_go_test` entry point while the named config decides whether the same BUILD call is a raw `go_test` or the existing enabled Test Optimization shape. @@ -236,8 +186,8 @@ automatically. No per-target disable attribute is required. - `DD_GIT_*` must use `--repo_env`, never `--test_env`, so Git metadata does not become part of the test action cache key. - `DD_TEST_OPTIMIZATION_AGENT_URL` and - `DD_TEST_OPTIMIZATION_AGENTLESS_URL` are not part of the Go/Orchestrion - test sandbox. The uploader reads upload endpoints at `bazel run` time. + `DD_TEST_OPTIMIZATION_AGENTLESS_URL` are not part of the Go test + sandbox. The uploader reads upload endpoints at `bazel run` time. - `FETCH_SALT` is not part of the normal flow. Use it only for a separate, explicit force-refresh sync when you intentionally want fresh backend metadata. @@ -268,41 +218,21 @@ already reachable from `origin/main`. Do not publish snippets that point at a feature-branch commit: those commits can disappear after squash merge and break consumer resolution. -Maintainers can generate the full WORKSPACE/archive tuple from this checkout: +Maintainers can generate the WORKSPACE/archive tuple from this checkout: ```bash ./bazelw run //tools/dev:print_go_onboarding_pins -- \ --commit "$(git rev-parse origin/main)" \ - --rules-go-upstream v0_60_0 \ - --variant base \ --verify-main-reachable ``` -The helper verifies the selected rules_go variant exists, verifies the commit is -reachable from `origin/main`, downloads the real GitHub codeload archive, and -prints `RTO_COMMIT`, `RTO_ARCHIVE_URL`, `RTO_ARCHIVE_SHA256`, -`RTO_ARCHIVE_PREFIX`, the selected upstream support line, the selected variant, -the default `dd-trace-go` version, and the default Orchestrion version. +The helper verifies that the commit is reachable from `origin/main`, downloads +the real GitHub codeload archive, and prints `RTO_COMMIT`, `RTO_REMOTE`, +`RTO_ARCHIVE_URL`, `RTO_ARCHIVE_SHA256`, `RTO_ARCHIVE_PREFIX`, and +`RTO_ARCHIVE_TYPE`. Add `--print-summary` to also print a Markdown summary. Published GitHub pins use `tar.gz`; other archive types must be mirrored and -documented outside this helper. - -Historical `v1.2.0` GitHub pins (not `v1.3.0`): - -```bash -RTO_COMMIT="69953536d4ef1252c8181c267d16c61263f0aa4c" -RTO_REMOTE="https://github.com/DataDog/rules_test_optimization.git" -RTO_ARCHIVE_URL="https://codeload.github.com/DataDog/rules_test_optimization/tar.gz/69953536d4ef1252c8181c267d16c61263f0aa4c" -RTO_ARCHIVE_SHA256="fd54d1871fc01ff0bb3db190dfaadaa8256edd68a4f3bb85ecc08b315fbf5bd4" -RTO_ARCHIVE_PREFIX="rules_test_optimization-69953536d4ef1252c8181c267d16c61263f0aa4c" -RTO_ARCHIVE_TYPE="tar.gz" -RULES_GO_UPSTREAM="v0_60_0" -RULES_GO_VARIANT="base" -RULES_GO_STRIP_PREFIX="third_party/rgo/v0_60_0/base" -DD_TRACE_GO_VERSION="v2.9.0" -ORCHESTRION_VERSION="v1.9.0" -``` - -The archive URL, SHA256, and prefix are tied to the repository commit. +documented outside this helper. The archive URL, SHA256, and prefix are tied to +the repository commit. Consumers using the Go bootstrap can print the same tuple or write a repository-local summary: @@ -310,15 +240,11 @@ repository-local summary: ```bash bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --print-published-pins \ - --rto-commit \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base + --rto-commit bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --write-onboarding-summary=TEST_OPTIMIZATION_GUIDE.md \ - --rto-commit \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base + --rto-commit ``` If the bootstrap runs from a checkout that has this repository's Git history, @@ -427,6 +353,13 @@ $env:DD_SITE = "datadoghq.com" ### Bzlmod + Go companion (`dd_topt_go_test`) +`dd_topt_go_test` builds each enabled test with the Go SDK that +[`ddtest instrument`](https://github.com/DataDog/ddtest/blob/main/ddto/docs/bazel.md) +instruments, and links Mini, ddtest's Test Optimization runtime. `rules_go` is +the upstream module from the Bazel Central Registry, `0.60.0` or newer. The Go +module needs no Datadog requirement in `go.mod`, and the instrumentation needs +no Go installation on the host. + For a fresh single-service Go workspace, start with the small manual prerequisite block below, then let the guided bootstrap finish the Go-specific setup. @@ -451,17 +384,14 @@ bazel_dep(name = "rules_go", version = "0.60.0") ``` Then run the Datadog bootstrap helper once from the workspace that owns your -Go module. `--dd-trace-go-version` is optional; if you omit it, the default is -`v2.9.1`. Orchestrion `v1.12.0` requires Go `1.25.0` or newer, so the -`--runtime-version` value and registered Bazel Go toolchain must satisfy that -minimum. +Go module. `--runtime-version` is the Go version of the SDK that +`ddtest instrument` instruments, and the runtime version of the sync metadata. ```bash bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --guided \ --service go-service \ --runtime-version 1.25.0 \ - --dd-trace-go-version v2.9.1 \ --write-bazelrc ``` @@ -472,48 +402,66 @@ bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --guided \ --service go-service \ --runtime-version 1.25.0 \ - --dd-trace-go-version v2.9.1 \ --go-module-dir path/to/go-module \ --write-bazelrc ``` -`--dd-trace-go-version` accepts a normal tag, a pseudo-version, a branch, or a -commit SHA. Bootstrap resolves that input to exact Go module versions before it -writes anything back to the workspace. If you rerun bootstrap without -`--dd-trace-go-version`, it preserves the managed tracer config that is already -in place. - The bootstrap helper: -- updates `MODULE.bazel` with a Datadog-managed `rules_go` override back to this repository's selected clean vendored base module and the `@rules_go//go:extensions.bzl` Orchestrion wiring required for Bazel builds +- adds a Datadog-managed block to `MODULE.bazel` that configures the + instrumented Go SDK with `topt_go_ddtest.sdk(go_version = "")` +- adds `bazel_dep(name = "rules_go", version = "0.60.0")` when `MODULE.bazel` + has no `rules_go` dependency - adds the Datadog-managed single-service Go sync block (`test_optimization_go_extension`) - creates a root `dd_test_optimization_doctor` target when missing - creates a root `dd_upload_payloads` target when missing - can print or write the recommended `.bazelrc` block with `--print-bazelrc-snippet` or `--write-bazelrc` - creates one central `//tools/build:dd_go_test.bzl` entry point for - workspace-local Go tests -- configures that wrapper to delegate every call to `dd_topt_go_test` with - `orchestrion_mode = "test_optimization"`; omitting the named config makes the - generated disabled export preserve normal `go_test` behavior under the same - public target name -- writes a deterministic `orchestrion.tool.go` that matches the Bazel-side Orchestrion wiring -- repins `dd-trace-go` and the Orchestrion-managed Go helper packages to the resolved tracer versions -- writes a starter `orchestrion.yml` when missing -- writes either `dd_trace_go_version` or `dd_trace_go_versions` into the managed `MODULE.bazel` block, depending on whether the traced Go modules resolve to one shared version or different exact versions -- keeps Bazel's injected tracer versions and the local Go module pins aligned, and the build fails fast if they drift apart - -By default, bootstrap uses `--go-mod-sync=targeted`. That mode updates only the -Orchestrion and `dd-trace-go` tool requirements, resolves the exact packages -needed by `orchestrion.tool.go`, and verifies the result with -`go list -mod=readonly`. It does not run `go mod tidy`, which keeps large -repositories from rewriting unrelated module state. Use `--go-mod-sync=tidy` -when you explicitly want the broad tidy behavior, or `--go-mod-sync=off` when -you only want bootstrap to write managed Bazel/Orchestrion files. - -Bootstrap runs module-sync commands with `go` by default. Pass -`--go-binary=/path/to/go` when the workspace must use a pinned Go SDK, such as -the same SDK Bazel uses in a monorepo. The value must be a single executable -path named `go` or `go.exe`; do not include shell arguments. + workspace-local Go tests, which delegates every call to `dd_topt_go_test`; + omitting the named config makes the generated disabled export preserve normal + `go_test` behavior under the same public target name + +The managed block looks like this: + +```bzl +# BEGIN Datadog Go Test Optimization bootstrap +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", +) +topt_go_ddtest.sdk(go_version = "1.25.0") +# END Datadog Go Test Optimization bootstrap +``` + +Without `--guided`, the bootstrap writes only this block and the `rules_go` +dependency when it is missing. Without `--runtime-version`, the block reads +`topt_go_ddtest.sdk(go_mod = "//:go.mod")` (or the `go.mod` label of the +`--go-module-dir` package), and the extension takes the Go version from the +`toolchain` directive, then the `go` directive. That package needs a `BUILD` +file; the bootstrap stops otherwise. Workspaces that do not use the bootstrap +add the same two lines by hand. The Go companion registers the toolchains, so +the root module registers none. See +[Go instrumentation SDK](docs/Configuration_Reference.md#go-instrumentation-sdk) +for the `sdk` tag attributes. + +Only instrumented tests select these toolchains, so other builds fetch neither +the instrumented SDKs nor Mini, and Bazel fetches only the SDKs of the exec +platforms it selects. Without an `sdk` tag, an instrumented test fails with a +message that says what to configure; see +[Troubleshooting](docs/Troubleshooting.md#instrumented-test-fails-no-go-sdk-to-instrument). + +Rerunning the bootstrap on a workspace set up by 1.x replaces the Go block that +1.x wrote. See the [migration note](CHANGELOG.md#unreleased) and +[Bootstrap stops on a rules_go fork override](docs/Troubleshooting.md#bootstrap-stops-on-a-rules_go-fork-override). + +If the Go module requires `github.com/DataDog/ddtest/ddto` to use Mini's Go API +in tests, replace `go_deps`' copy with the generated Mini, or the test fails at +analysis: + +```bzl +use_repo(topt_go_ddtest, "topt_go_ddtest_mini") +override_repo(go_deps, com_github_datadog_ddtest_ddto = "topt_go_ddtest_mini") +``` By default, `dd_topt_go_test` also sets `DD_SERVICE` from the selected sync metadata service name. If you already set `DD_SERVICE` in the test target's @@ -540,16 +488,23 @@ load("//tools/build:dd_go_test.bzl", "dd_go_test") dd_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], ) ``` Use the manual `dd_topt_go_test(..., topt_data = ...)` path only when the workspace already has custom sync wiring, mixed-language layout, or multi-service -Go setup. Manual Go callsites should set -`orchestrion_mode = "test_optimization"` for standard Go `testing`; the default -`general` mode preserves broader generic Orchestrion behavior. +Go setup. + +For an enabled export, `dd_topt_go_test` creates a hidden raw `go_test` built +with the instrumented Go SDK, adds Mini's `testopt` library and a registration +file to it, and exposes it through the public test target. It also adds the +test-binary linker flags `-s -w` where `rules_go` does not already strip and the +build does not ask for debug or unstripped binaries; set +`enable_test_binary_linker_optimization = False` to keep the caller's linker +flags unchanged. See +[`dd_topt_go_test` attributes](docs/Configuration_Reference.md#dd_topt_go_test-attributes). An explicit per-service integration can pass the same macro a local-static descriptor instead of loading an external repository's `export.bzl`: @@ -562,7 +517,6 @@ dd_topt_go_test( "service_name": "go-service", "runtime_module_path": "example.com/repo", }, - orchestrion_mode = "test_optimization", srcs = ["pkg_test.go"], ) ``` @@ -579,9 +533,9 @@ behavior. The `.topt` clone preserves the source test's execution policy. A source tagged `no-remote-exec` therefore still runs its `TestRunner` locally, but that policy -does not leak into deterministic build actions such as stdlib preparation, -synthetic testmain helpers, compilation, or linking. Those actions remain -eligible for normal Bazel caching and remote execution. +does not leak into deterministic build actions such as standard library +preparation, compilation, or linking. Those actions remain eligible for normal +Bazel caching and remote execution. Large consumers can warm the matching instrumented standard library in a dedicated cache-writing invocation: @@ -598,21 +552,20 @@ bazel build //tools/test_optimization:go_stdlib_warmup \ --config=remote-cache-write ``` -The warmup target applies the same target-scoped Go configuration transitions, -enables Orchestrion in `test_optimization` mode, and materializes the -instrumented stdlib plus its deterministic declared archive cache. Ordinary Go -actions neither publish nor consume that instrumented cache. The consumer -remains responsible for restricting remote-cache writes to a trusted CI -invocation. -Without the named config, the target is a no-op so ordinary broad builds can -analyze its package without enabling Test Optimization. +The warmup target applies the same Go configuration transitions as an +instrumented test and materializes the standard library built with the +instrumented Go SDK, plus its build cache directory. Ordinary Go actions +neither publish nor consume that instrumented cache. The consumer remains +responsible for restricting remote-cache writes to a trusted CI invocation. +The target is tagged `manual`, so wildcard builds skip it. Building it needs the +instrumented SDK configured, with or without the named config. ### Bzlmod + Python companion (`dd_topt_py_test`) Configure the Python sync extension with `enabled_by_env = True` and put `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1` in the -named config. Python does not declare `rules_go` and must not copy the Go-only -Orchestrion build flag. +named config. Python does not declare `rules_go` and needs no Go SDK +configuration. ```bzl bazel_dep(name = "datadog-rules-test-optimization", version = "1.4.0") @@ -954,8 +907,8 @@ and the same underlying test rule symbols, create thin local wrappers so package BUILD files do not repeat `topt_data` and `*_test_rule`. For config-gated Go onboarding, keep one central wrapper. It always delegates -to `dd_topt_go_test`; the named config selects enabled metadata and -Orchestrion, while the stable disabled export preserves normal `go_test` +to `dd_topt_go_test`; the named config selects enabled metadata and the +instrumented test, while the stable disabled export preserves normal `go_test` behavior without metadata requests. Python can use the same central-wrapper shape when its sync repository sets `enabled_by_env = True`. Other companions retain their existing enablement contract in this release. @@ -971,13 +924,6 @@ def dd_go_test(name, **kwargs): dd_topt_go_test( name = name, topt_data = topt_data, - orchestrion_mode = "test_optimization", - orchestrion_pin_files = [ - "//:go.mod", - "//:go.sum", - "//:orchestrion.tool.go", - "//:orchestrion.yml", - ], **kwargs ) ``` @@ -988,7 +934,7 @@ load("//tools/build:dd_go_test.bzl", "dd_go_test") dd_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], ) ``` @@ -1186,37 +1132,39 @@ the other companions remain unchanged. For Go in WORKSPACE mode, keep the core and Go companion as separate external repositories and load `dd_topt_go_test` from -`@datadog-rules-test-optimization-go//:topt_go_test.bzl`. Prefer the public -WORKSPACE helper so the Go companion and Orchestrion-enabled `rules_go` fork use -the same commit, repo mapping, and upstream support line: +`@datadog-rules-test-optimization-go//:topt_go_test.bzl`. Declare upstream +`rules_go` first, named `io_bazel_rules_go`: the instrumented SDK repositories +load it under that name. Then use the public WORKSPACE helper, which declares +only the Go companion, and declare the instrumented Go SDK after `rules_go`'s +own setup: ```bzl load("@datadog-rules-test-optimization//tools/go:workspace_repositories.bzl", "datadog_go_test_optimization_workspace_repositories") datadog_go_test_optimization_workspace_repositories( rto_commit = "", - rules_go_repo_name = "io_bazel_rules_go", - rules_go_upstream = "v0_60_0", - rules_go_variant = "base", ) + +load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") + +go_rules_dependencies() + +go_register_toolchains(version = "1.25.0") + +load("@datadog-rules-test-optimization-go//:topt_go_ddtest_repository.bzl", "topt_go_ddtest_workspace_repositories") + +topt_go_ddtest_workspace_repositories(go_version = "1.25.0") ``` -Use `rules_go_variant = "base"`. When multiple upstream versions are supported, -use `rules_go_upstream` to select the upstream support line; omit it to keep the -repository default. The base variant is a complete `rules_go` tree and does not -require `patches`, `patch_tool`, or a consumer-owned patch directory. The public -WORKSPACE helper also expects the default tool-repo name -`rules_go_orchestrion_tool`, so consumers should not rename that repository. -When Go tests live below the module root, pass the module-root pin files through -`orchestrion_pin_files` (for example `["//:go.mod", "//:orchestrion.tool.go"]`) -or inject them from a repo-local wrapper. -For large monorepos where root-level tool imports would churn or invalidate the -main Go module, keep Orchestrion tool wiring in Bazel and use a repo-local -wrapper with package-local pin files. An explicit -`orchestrion_pin_files = []` is valid only when the target package contains a -package-local `go.mod` that the macro can auto-discover. Otherwise pass visible -module-root labels, including `go.mod` and every relevant Orchestrion pin file; -do not add a root `orchestrion.tool.go` only to satisfy the wrapper pattern. +`topt_go_ddtest_workspace_repositories` takes the same arguments as the Bzlmod +`sdk` tag except `go_mod`, and registers the toolchains. To instrument the SDK +that `go_register_toolchains` declared instead of downloading one, pass +`base_sdk_roots = {"linux_amd64": "@go_sdk//:ROOT"}` together with +`exec_platforms = ["linux_amd64"]`, or keep `go_version` for the other exec +platforms. `datadog_go_test_optimization_workspace_repositories` fails when +`rules_go` is not declared yet. See +[step 8 of the WORKSPACE installation](docs/Installation_Reference.md#8-configure-go-support-in-workspace-with-the-public-helper) +for the complete file. For Python in WORKSPACE mode, declare `rules_python` and the core repository first, then use the public Python helper to declare only the Python companion: @@ -1263,11 +1211,16 @@ bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --print-workspace-snippet \ --rto-commit \ --service my-service \ - --runtime-version 1.25.0 \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base + --runtime-version 1.25.0 ``` +The snippet declares upstream `rules_go` first as a commented `http_archive` +placeholder with the release URL, then the core repository, +`datadog_go_test_optimization_workspace_repositories(...)`, +`go_rules_dependencies()`, `go_register_toolchains(...)`, +`topt_go_ddtest_workspace_repositories(go_version = ...)`, and the sync +repositories. + For larger WORKSPACE monorepos, let bootstrap write the generic local files and keep only the final WORKSPACE placement as a manual review step: @@ -1277,41 +1230,15 @@ bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --service my-service \ --runtime-version 1.25.0 \ --sync-repo-name test_optimization_data \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base \ - --rules-go-repo-name io_bazel_rules_go \ --write-bazelrc \ - --write-orchestrion-files \ --write-wrapper-template ``` WORKSPACE mode does not edit `WORKSPACE`. It writes only Datadog-managed local -blocks/files such as `.bazelrc`, `orchestrion.tool.go`, `orchestrion.yml`, and -an optional repo-local wrapper template. Create the single doctor/uploader pair -in the monorepo's lightweight Test Optimization package instead of asking -bootstrap to modify the root BUILD. By default bootstrap also avoids running Go -module commands; pass an explicit `--go-mod-sync=targeted` when you want it to -repair the local Orchestrion tool graph. - -If your WORKSPACE repo also checks in Gazelle-style `go_repository(...)` -declarations, ask bootstrap to validate those pins instead of discovering the -drift later during Bazel analysis: - -```bash -bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ - --workspace-mode \ - --write-orchestrion-files \ - --go-mod-sync=targeted \ - --check-go-repositories \ - --go-repositories-file repositories.bzl \ - --go-repositories-refresh-command './tools/update-go-repositories.sh' -``` - -Bootstrap does not rewrite `repositories.bzl` itself. It checks only the -Orchestrion-related modules it owns and runs the repository-owned refresh -command only after targeted Go module sync succeeds. Use -`--print-go-repository-updates` to print the expected versions without hiding -the fact that the repo still owns the actual refresh command. +blocks/files such as `.bazelrc` and an optional repo-local wrapper template. +Create the single doctor/uploader pair in the monorepo's lightweight Test +Optimization package instead of asking bootstrap to modify the root BUILD. The +bootstrap runs no Go commands and does not touch `go.mod`. Bootstrap can also generate an operator-owned validation script for large repositories. The script repeats the RFC flow without hiding Bazel behavior: @@ -1368,15 +1295,16 @@ For a generic wrapper pattern, see [Other languages (without companion macro)](# | Component | Recommended baseline | Notes | |-----------|----------------------|-------| | Bazel | `8.8.2` | Repository baseline (`.bazelversion`) and primary CI lanes | -| rules_go (Go users) | `0.60.0` / `rules_go_upstream = "v0_60_0"` | README examples use the default support line; importpath inference requires `0.51.0+` | -| Go toolchain (example) | `1.25.0` | Consumer repositories may use another supported version | +| rules_go (Go users) | `0.60.0` | Upstream `rules_go`, `0.60.0` or newer; CI covers `0.60.0`, `0.61.1`, `0.62.0`, `0.63.0`, `0.64.2`, and `0.65.0` (which requires Go `1.26`) | +| Go SDK (example) | `1.25.0` | The version that `ddtest instrument` instruments; consumer repositories may use another version | +| `ddtest instrument` | pinned | The Go companion pins one ddtest release in [`modules/go/topt_go_ddtest_repository.bzl`](modules/go/topt_go_ddtest_repository.bzl) | | Module versions | `1.4.0` (in preparation) | Latest published release: `1.3.0`; use its Git installation recipe | - **Bazel 8.8.2 (repo baseline)** - Matches `.bazelversion` and primary CI lanes - **WORKSPACE compatibility lane** - CI intentionally validates `--noenable_bzlmod --enable_workspace` on Bazel `8.8.2` during migration away from WORKSPACE mode in Bazel 9+ - **Bazel 5.0+ minimum capability** - Earliest Bazel line with required `TEST_UNDECLARED_OUTPUTS_DIR` payload support - **Tracer/runtime with DD Test Optimization file-mode support** - Must honor `DD_TEST_OPTIMIZATION_MANIFEST_FILE` and `DD_TEST_OPTIMIZATION_PAYLOADS_IN_FILES` -- **rules_go v0.51.0+** (for Go importpath inference) - This repository reads `GoInfo`/`GoArchive` providers when selecting per-module payloads +- **rules_go v0.60.0+** (Go users) - The Go companion depends on `rules_go` `0.60.0` and reads `GoInfo`/`GoArchive` providers when selecting per-module payloads - **DD_SITE format** - Accepts bare host, app/api-prefixed host, or full URL; leading/trailing ASCII whitespace is trimmed, then normalized to `https://api.` - **Default uploader tooling** - `Python 3.10+` on every platform, plus `bash` on Linux/macOS or `powershell.exe` (Windows PowerShell 5.1+ or PowerShell 7+) @@ -1467,7 +1395,7 @@ selection unit across features (avoids cross-feature collisions). For Go, the catalog also retains the exact backend module identifier behind each label. This matters for fallback `go_test` import paths whose final target name contains a period: Go escapes that period in runtime symbols (for example, -`.topt` becomes `%2e`) before the tracer reports the module. If one test binary +`.topt` becomes `%2e`) before the test runtime reports the module. If one test binary contains both `package pkg` and external `package pkg_test` tests, the base package target carries both module entries. Other runtimes keep one backend module per generated target. @@ -1835,16 +1763,17 @@ For complete uploader details, use [`docs/Uploader_Reference.md`](docs/Uploader_ The `dd_topt_go_test` macro preserves the caller's public target label in both modes. When the selected sync export is enabled, that label is a public -`orch_go_test` wrapper around a hidden raw `go_test`, with Datadog Test -Optimization data/env wiring included. When the export is disabled, the same +`ddtest_go_test` wrapper around a hidden raw `go_test` built with the Go SDK that +`ddtest instrument` instruments, with Datadog Test Optimization data/env wiring +included. When the export is disabled, the same label is the supplied raw `go_test_rule`; no Test Optimization-owned hidden targets or argument mutations are created. Because the label is stable but its rule class is mode-dependent, automation should prefer explicit target labels. If it uses Bazel's -`--test_lang_filters`, enabled Test Optimization invocations need `orch_go`, +`--test_lang_filters`, enabled Test Optimization invocations need `ddtest_go`, ordinary raw Go invocations need `go`, and automation that covers both modes -should use `--test_lang_filters=go,orch_go` or omit the language filter. +should use `--test_lang_filters=go,ddtest_go` or omit the language filter. By default, it sets `rundir` to the current Bazel package when not explicitly provided. If you enable `stage_sources = True`, it instead defaults `rundir` @@ -1881,14 +1810,11 @@ need an enablement attribute. Consumers upgrading from `1.2.0` must add the named config before updating the Rule; rerun `dd_topt_go_bootstrap` with `--write-bazelrc` for an idempotent migration. A consumer that deliberately keeps manually controlled, always-enabled metadata can set -`enabled_by_env = False`, but it must also keep the Orchestrion build setting -enabled; analysis rejects a partial activation. +`enabled_by_env = False`. -Without `--config=test-optimization`, the patched `rules_go` aliases select -package-local empty targets and the gated Orchestrion repository returns before -looking for a host Go binary or fetching/building Orchestrion source. The -disabled path therefore preserves the repository interface without requiring -Go to be installed on the analysis host. +Without `--config=test-optimization`, the export is disabled and +`dd_topt_go_test` creates a plain `go_test`, so Bazel fetches neither the +instrumented Go SDK nor Mini, and the metadata repository makes no requests. Consumer-owned central wrappers may always delegate capable packages to `dd_topt_go_test`. The generated export and named config choose the raw or @@ -1899,106 +1825,38 @@ attribute or a second macro name. still honors `GO_MODULE_PATH` first for CI overrides, but the explicit attr is the recommended default because it avoids repo-local `--repo_env` glue. -Then run the Datadog bootstrap helper once so Orchestrion is pinned into the -workspace Go module. Repository/bootstrap resolution may use network access. -After that, Orchestrion build actions consume a declared offline Go module -download cache staged in `@rules_go_orchestrion_tool`; they do not depend on a -warmed host Go module cache. Test payloads still use the Bazel file-output -contract: the tracer writes JSON files under `TEST_UNDECLARED_OUTPUTS_DIR`, and -the uploader enriches those JSON files with repository and Bazel metadata. Pass -`--dd-trace-go-version ` if you want a non-default tracer version; -otherwise the default is `v2.9.1`. - -```bash -bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ - --runtime-version -``` - -The bootstrap's default module-sync mode is `targeted`, which avoids a broad -`go mod tidy`. Use `--go-mod-sync=tidy` only when you want bootstrap to tidy the -whole module, and use `--go-mod-sync=off` when another repository-owned command -will update `go.mod` and `go.sum`. Use `--go-binary=/path/to/go` if the module -must be synced with a specific Go SDK. The path must point to a `go` or -`go.exe` executable and must not include arguments. This option controls the -one-time Go module update performed by bootstrap; enabled Bazel builds use the -SDK declared from `--runtime-version`. - -For WORKSPACE repos with checked-in `go_repository(...)` declarations, add -`--check-go-repositories` after targeted sync. This catches stale -`repositories.bzl` pins for `github.com/DataDog/orchestrion` and the three -`dd-trace-go` modules before Bazel tries to build with mismatched versions. - -Guided bootstrap declares a Bazel-managed Go SDK from `--runtime-version` and -passes its root and exact version to the Orchestrion repository. Enabled builds -therefore do not depend on a host `go` binary. The declaration is workspace-wide -and is not repeated for each service or test. - -If you wire Orchestrion manually instead of using bootstrap, declare the same -SDK and let Bazel derive the selected tracer module versions from the -repository's checked-in `go.mod` and `go.sum`: +Then configure the Go SDK that `ddtest instrument` instruments. The Go +companion registers the matching toolchains: ```bzl -test_optimization_go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") -test_optimization_go_sdk.download( - name = "test_optimization_go_sdk", - version = "", -) -use_repo(test_optimization_go_sdk, "test_optimization_go_sdk") - -orchestrion = use_extension("@rules_go//go:extensions.bzl", "orchestrion") -orchestrion.from_source( - version = "v1.12.0", - dd_trace_go_pin_files = [ - "@//:go.mod", - "@//:go.sum", - ], - go_sdk_root = "@test_optimization_go_sdk//:ROOT", - go_sdk_version = "", +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", ) -use_repo(orchestrion, "rules_go_orchestrion_tool") +topt_go_ddtest.sdk(go_mod = "//:go.mod") # or go_version = "1.25.0" ``` -The root package must export those two files. Pin-file mode resolves direct and -transitive supported dd-trace-go modules with the Bazel-managed SDK and -`-mod=readonly`; it does not modify the consumer module. +The bootstrap can write this block for you: `--runtime-version ` +sets `go_version`, and without it the block uses the `go.mod` of the workspace +root or of `--go-module-dir`. -Use an explicit shared or per-module version only as an escape hatch for a -module graph that pin-file mode cannot resolve: - -```bzl -test_optimization_go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") -test_optimization_go_sdk.download( - name = "test_optimization_go_sdk", - version = "", -) -use_repo(test_optimization_go_sdk, "test_optimization_go_sdk") - -orchestrion = use_extension("@rules_go//go:extensions.bzl", "orchestrion") -orchestrion.from_source( - version = "v1.12.0", - dd_trace_go_versions = { - "github.com/DataDog/dd-trace-go/v2": "v2.9.1", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2": "v2.9.1", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2": "v2.9.1", - }, - go_sdk_root = "@test_optimization_go_sdk//:ROOT", - go_sdk_version = "", -) -use_repo(orchestrion, "rules_go_orchestrion_tool") +```bash +bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ + --runtime-version ``` +Test payloads use the Bazel file-output contract: Mini writes JSON files under +`TEST_UNDECLARED_OUTPUTS_DIR`, and the uploader enriches those JSON files with +repository and Bazel metadata. The ddtest release is pinned by the Go companion; +the consumer's `go.mod` needs no Datadog requirement, and enabled builds need no +host `go` binary. The configuration is workspace-wide and is not repeated for +each service or test. The ddtest side of this integration is documented in +[ddtest's Bazel guide](https://github.com/DataDog/ddtest/blob/main/ddto/docs/bazel.md). + The maintained repository integration scripts validate the hermetic Go path with explicit Bazel flags in the script itself. There is no special repo-root `--config=hermetic` shortcut for this flow. -If all three selection settings are omitted, the legacy default is still -`v2.9.1`. Manual setups must keep the local Go module pins on the same effective -versions, or the build will stop with a mismatch error. Do not combine -`dd_trace_go_pin_files`, `dd_trace_go_version`, or `dd_trace_go_versions` in -the same `orchestrion.from_source(...)` call. -Bootstrap also refuses to take over tracer settings that are already managed -manually outside its own managed block. - ### Basic usage ```bzl @@ -2008,12 +1866,13 @@ load("@test_optimization_data//:export.bzl", "topt_data") go_library( name = "pkg_lib", - srcs = ["*.go"], + srcs = ["pkg.go"], + importpath = "github.com/example/service/pkg", ) dd_topt_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], # enables provider-based importpath inference topt_data = topt_data, ) @@ -2022,62 +1881,48 @@ dd_topt_go_test( `dd_topt_go_test` enables CI Visibility and payload-to-files mode by default, so opt-in Go tests emit payload files without extra `--test_env` settings. -### Go Orchestrion mode selection - -`dd_topt_go_test` accepts `orchestrion_mode`: - -| Value | Description | -|-------|-------------| -| `general` | Default. Preserves broad generic Orchestrion behavior for consumers that need instrumentation beyond the Test Optimization-only Go testing path. | -| `test_optimization` | Standard Go `testing` Test Optimization mode. Keeps the stdlib `testing`, synthetic `testmain`, helper packagefile, importcfg, and link support needed for payloads while leaving customer package compiles and external `_test` package compiles on the normal rules_go path. | - -For standard Go `testing` onboarding, including large WORKSPACE monorepos that -only need Test Optimization payloads, use the `test_optimization` mode shown -next. The basic snippet above leaves the default `general` mode implicit. - -### Standard `testing` Orchestrion mode - -`dd_topt_go_test` also accepts: +### What the macro builds + +For an enabled export, `dd_topt_go_test` creates: + +- a hidden raw `go_test` named `__raw_go_test`, tagged `manual`, with the + caller's attributes, built with the Go SDK that `ddtest instrument` + instruments; +- Mini's `@topt_go_ddtest_mini//testopt` library in that test's `deps`, unless + they already name it; +- a registration file for the test package, generated from the package clauses + of `srcs`; +- the public `` test, which runs the raw test and copies + `bazel_target_metadata.json` next to the payloads. + +The public test's transition sets `@io_bazel_rules_go//go/toolchain:sdk_name` +to `ddto`, and only the instrumented toolchains match that value. Other targets +keep the workspace's normal Go toolchain. If a test would link a copy of Mini's +packages from another repository, such as `go_deps`, analysis fails with the +`override_repo` recipe; see +[Troubleshooting](docs/Troubleshooting.md#analysis-fails-test-would-link-mini-twice). + +The macro also applies test-binary-only linker flags (`-s -w`) in Bazel modes +where rules_go is not already stripping and the build does not ask for debug or +unstripped binaries. Set `enable_test_binary_linker_optimization = False` on a +specific `dd_topt_go_test` target or repo-local wrapper if a consumer needs the +exact caller-provided linker flags. A `no-remote-exec` tag applies only to the +`TestRunner` action; compile and link actions stay remote-capable. + +Target metadata records the instrumentation in +`bazel.go.instrumentation.backend`, `bazel.go.instrumentation.version`, +`bazel.go.instrumentation.release`, `bazel.go.instrumentation.contract`, +`bazel.go.sdk.name`, and `bazel.go.sdk.version`. See +[Go target metadata](docs/Configuration_Reference.md#go-target-metadata). + +If the test runtime needs runtime-visible source files for metadata such as +`test.source.end`, enable source staging explicitly: ```bzl dd_topt_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], - embed = [":pkg_lib"], - orchestrion_mode = "test_optimization", - orchestrion_pin_files = [ - "//:go.mod", - "//:go.sum", - "//:orchestrion.tool.go", - "//:orchestrion.yml", - ], - topt_data = topt_data, -) -``` - -In this mode, the Go fork keeps the standard-library `testing` path, -synthetic `testmain` helper packagefiles, importcfg rewrites, and final link -support needed for Test Optimization payloads. Customer package compiles and -external `_test` package compiles stay on the normal rules_go compile path. - -When enabled, the macro also applies test-binary-only linker flags in Bazel modes -where rules_go is not already stripping. Set -`enable_test_binary_linker_optimization = False` on a specific -`dd_topt_go_test` target or repo-local wrapper if a consumer needs the exact -caller-provided linker flags during rollout. - -This mode is for standard Go `testing` support. Automatic `testify/suite` -instrumentation is not supported in `test_optimization` mode. - -If the tracer needs runtime-visible source files for AST-derived metadata such -as `test.source.end`, enable source staging explicitly: - -```bzl -dd_topt_go_test( - name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], - orchestrion_mode = "test_optimization", stage_sources = True, topt_data = topt_data, ) @@ -2101,9 +1946,8 @@ Declare fixture files explicitly in `data`: ```bzl dd_topt_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], data = glob(["testdata/**"]), - orchestrion_mode = "test_optimization", topt_data = topt_data, ) ``` @@ -2140,10 +1984,9 @@ module suffix: ```bzl dd_topt_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], module_label_override = "github_com_example_custom_pkg", - orchestrion_mode = "test_optimization", topt_data = topt_data, ) ``` @@ -2168,8 +2011,7 @@ load("@test_optimization_data//:export.bzl", "topt_data_by_service") dd_topt_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], - orchestrion_mode = "test_optimization", + srcs = ["pkg_test.go"], topt_data = topt_data_by_service, # pass mapping topt_service = "go_service_a", # select service ) diff --git a/code-security.datadog.yaml b/code-security.datadog.yaml deleted file mode 100644 index e13ec9eb..00000000 --- a/code-security.datadog.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Unless explicitly stated otherwise all files in this repository are licensed under -# the Apache 2.0 License. -# -# This product includes software developed at Datadog -# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. - -schema-version: v1.0 -sast: - global-config: - ignore-paths: - # Vendored rules_go forks are maintained as upstream-derived trees. Scan - # repository-owned tooling and integration code, but do not open SAST - # findings for third-party fork contents copied into these variants. - - "third_party/rgo/*/base/**" diff --git a/docs/Configuration_Reference.md b/docs/Configuration_Reference.md index deea9e22..ef30491e 100644 --- a/docs/Configuration_Reference.md +++ b/docs/Configuration_Reference.md @@ -10,50 +10,92 @@ This product includes software developed at Datadog This page contains the full configuration and environment reference. -## Go Orchestrion version selection +## Go bootstrap flags -Guided Go bootstrap accepts: +Tool: `@datadog-rules-test-optimization-go//:dd_topt_go_bootstrap`. + +Without `--workspace-mode` or a print flag, bootstrap edits the workspace's +`MODULE.bazel`; `--write-bazelrc` or `--write-validation-script` without +`--guided` writes only that file. Bootstrap writes a managed block between `# BEGIN Datadog Go Test Optimization bootstrap` +and `# END Datadog Go Test Optimization bootstrap` that configures the Go SDK +that `ddtest instrument` instruments: + +```bzl +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", +) +topt_go_ddtest.sdk(go_mod = "//:go.mod") +``` + +With `--runtime-version`, the tag is `topt_go_ddtest.sdk(go_version = "")`. +Without it, the tag reads the `go.mod` of `--go-module-dir`, and that directory +must have a `BUILD` or `BUILD.bazel` file; bootstrap stops otherwise. Bootstrap +adds `bazel_dep(name = "rules_go", version = "0.60.0")` when `MODULE.bazel` has +no `rules_go` dependency, and writes no `rules_go` override. + +Rerunning bootstrap replaces the Go bootstrap block that 1.x wrote. It stops +with a message when an override outside that block still points `rules_go` at +the removed Datadog fork; see +[Bootstrap stops on a rules_go fork override](Troubleshooting.md#bootstrap-stops-on-a-rules_go-fork-override) +and the [migration note](../CHANGELOG.md#unreleased). + +Bzlmod and guided setup accept: + +| Flag | Default | Description | +|------|---------|-------------| +| `--workspace` | `BUILD_WORKSPACE_DIRECTORY`, else the current directory | Workspace root to update | +| `--go-module-dir` | workspace root | Go module directory whose `go.mod` gives the module path and, without `--runtime-version`, the Go version to instrument | +| `--runtime-version` | empty | Go version of the SDK that `ddtest instrument` instruments. Guided setup also uses it as the sync `runtime_version` | +| `--guided` | `false` | Also write the single-service sync block, root doctor and uploader targets, and `//tools/build:dd_go_test.bzl`. Requires `--service` and `--runtime-version`, and the core and Go companion modules in `MODULE.bazel` | +| `--service` | empty | Datadog service name for guided setup | +| `--sync-repo-name` | `test_optimization_data` | External repository name used by generated doctor, uploader, wrapper, and WORKSPACE sync snippets | +| `--doctor-target-name` | `dd_test_optimization_doctor` | Root doctor target name generated by guided setup | +| `--uploader-target-name` | `dd_upload_payloads` | Root uploader target name generated by guided setup | +| `--force` | `false` | Replace unmanaged targets or files that have the name of a generated one | + +Published pins: | Flag | Default | Description | |------|---------|-------------| -| `--dd-trace-go-version` | `v2.9.1` | Go tracer query for bootstrap. Accepts a tag, pseudo-version, branch, or commit SHA and persists the exact resolved versions Bazel should use | -| `--go-binary` | `go` | Go binary used for bootstrap module graph synchronization. Set this to a pinned SDK path named `go` or `go.exe` when the repository must match Bazel's Go SDK; do not include shell syntax or arguments | -| `--go-mod-sync` | `targeted` | Local Go module synchronization strategy: `targeted` updates and verifies only Orchestrion tool packages, `tidy` also runs `go mod tidy`, and `off` skips Go module commands | +| `--rto-commit` | empty | Published rules_test_optimization commit. Required by the pin flags below | +| `--print-published-pins` | `false` | Print `RTO_COMMIT`, `RTO_REMOTE`, `RTO_ARCHIVE_URL`, `RTO_ARCHIVE_SHA256`, `RTO_ARCHIVE_PREFIX`, and `RTO_ARCHIVE_TYPE` for `--rto-commit`, then exit | +| `--print-onboarding-summary` | `false` | Print the same values as a Markdown summary, then exit | +| `--write-onboarding-summary` | empty | Workspace-relative Markdown file for that summary | +| `--verify-main-reachable` | `false` | Require `--rto-commit` to be reachable from `origin/main` in `--workspace` | WORKSPACE snippet mode is read-only. WORKSPACE generation mode writes only local files and managed blocks; it never edits `WORKSPACE` itself: | Flag | Default | Description | |------|---------|-------------| -| `--workspace-mode` | `false` | Enable WORKSPACE-oriented onboarding generation without editing `WORKSPACE` | -| `--print-workspace-snippet` | `false` | Print WORKSPACE-mode repository wiring and exit without modifying files | +| `--workspace-mode` | `false` | Enable WORKSPACE-oriented onboarding generation without editing `WORKSPACE`. Cannot be combined with `--guided` | +| `--print-workspace-snippet` | `false` | Print WORKSPACE-mode repository wiring and exit without modifying files. Requires `--service` and `--runtime-version` | | `--print-bazelrc-snippet` | `false` | Print the recommended `.bazelrc` block and exit without modifying files | | `--write-bazelrc` | `false` | Insert or replace the managed `.bazelrc` block. With `--guided`, bootstrap also continues the normal guided setup | -| `--write-root-targets` | `false` | Insert or replace managed root `BUILD.bazel` blocks for doctor, uploader, and root pin exports | -| `--write-orchestrion-files` | `false` | Write `orchestrion.tool.go`, `orchestrion.yml`, and pin-file exports for the selected Go module directory | +| `--write-root-targets` | `false` | Insert or replace managed root `BUILD.bazel` blocks for the doctor and uploader | | `--write-wrapper-template` | `false` | Write one config-gated repo-local Go wrapper template | | `--bazelrc-path` | `.bazelrc` | Path to the Bazel rc file updated by `--write-bazelrc` | | `--bazelrc-config` | `test-optimization` | Config name used by generated `common:` and `test:` lines | -| `--sync-repo-name` | `test_optimization_data` | External repository name used by generated doctor, uploader, wrapper, and WORKSPACE sync snippets | | `--expected-target` | repeatable | Local label the generated doctor should require, for example `//pkg:go_default_test` | | `--wrapper-package` | `tools/build` | Workspace-relative Bazel package for the generated wrapper template | | `--wrapper-file` | `dd_topt_go_test.bzl` | Wrapper `.bzl` filename used by `--write-wrapper-template` | | `--plain-wrapper-name` | `dd_go_test` | Central local wrapper function name generated by the wrapper template | | `--optimized-wrapper-name` | `dd_topt_go_test` | Compatibility alias for the central wrapper; not a separate enablement path | | `--datadog-fetch` | `git` | Fetch mode for `datadog-rules-test-optimization-go`: `git` or `archive` | -| `--rules-go-fetch` | `git` | Fetch mode for the Orchestrion-enabled fork: `git` or `archive`. Bzlmod infers existing archive overrides unless an explicit Git/source flag is supplied. | -| `--rules-go-upstream` | `default` | Datadog-managed upstream support line. Omit this to preserve the repository default; the current default resolves to `v0_60_0` | -| `--rules-go-variant` | `base` | Published fork variant. Only `base` is supported. | -| `--rules-go-repo-name` | `io_bazel_rules_go` | Bazel repository name that consumers use for `rules_go` | -| `--rto-commit` | empty | Published rules_test_optimization commit used in generated WORKSPACE snippets. Required when either fetch mode is `git` | -| `--rto-archive-url` | empty | Datadog source archive URL for WORKSPACE archive mode or explicit Bzlmod fork wiring | -| `--rto-archive-sha256` | empty | Archive SHA256 used when either fetch mode is `archive` | -| `--rto-archive-prefix` | empty | Archive root prefix used when either fetch mode is `archive` | -| `--rto-archive-type` | `tar.gz` | Archive type passed to `http_archive` | -| `--check-go-repositories` | `false` | Check checked-in WORKSPACE `go_repository(...)` declarations for the Orchestrion and `dd-trace-go` modules bootstrap owns | -| `--go-repositories-file` | `repositories.bzl` | Workspace-relative file inspected by `--check-go-repositories` | -| `--print-go-repository-updates` | `false` | Print the expected versions for the checked Orchestrion-related `go_repository(...)` declarations | -| `--go-repositories-refresh-command` | empty | Repository-owned command to refresh `go_repository(...)` declarations after a successful `--go-mod-sync=targeted` run; bootstrap rechecks the file after the command | +| `--rules-go-repo-name` | `io_bazel_rules_go` | Repository name of upstream `rules_go` in the snippet. Keep the default: the instrumented SDK repositories load `rules_go` as `@io_bazel_rules_go` | +| `--rto-commit` | empty | Commit used in generated WORKSPACE snippets. Required when `--datadog-fetch` is `git` | +| `--rto-archive-url` | empty | Datadog source archive URL for WORKSPACE archive mode | +| `--rto-archive-sha256` | empty | Archive SHA256 used when `--datadog-fetch` is `archive` | +| `--rto-archive-prefix` | empty | Archive root prefix used when `--datadog-fetch` is `archive` | +| `--rto-archive-type` | `tar.gz` | Archive type passed to `http_archive`. Published pins accept only `tar.gz` | + +The snippet declares upstream `rules_go` first, as a commented `http_archive` +placeholder with the release URL, then the core repository, +`datadog_go_test_optimization_workspace_repositories(...)`, +`go_rules_dependencies()`, `go_register_toolchains(...)`, +`topt_go_ddtest_workspace_repositories(go_version = ...)`, and the sync +repository. `--runtime-version` gives both Go versions. Validation script generation writes a repository-owned operational helper. It does not mutate Bazel rules or force remote output behavior outside the normal @@ -78,104 +120,152 @@ does not mutate Bazel rules or force remote output behavior outside the normal | `--shutdown-bazel-on-exit` | `false` | Run ` shutdown` when the script exits | | `--default-jobs` | `0` | Add `--jobs=` to generated test commands when greater than zero | -In WORKSPACE mode, bootstrap does not run Go module commands unless -`--go-mod-sync` is explicitly provided. This keeps large monorepo scaffolding -safe: operators can generate local files first, review the WORKSPACE snippet, -then run `--go-mod-sync=targeted` only when they are ready to update `go.mod` -and `go.sum`. - -If the repository keeps checked-in `go_repository(...)` declarations, pair the -targeted sync with `--check-go-repositories`. Bootstrap does not edit -`repositories.bzl` directly. It compares only the Orchestrion-related modules it -owns (`github.com/DataDog/orchestrion`, `github.com/DataDog/dd-trace-go/v2`, -`github.com/DataDog/dd-trace-go/contrib/net/http/v2`, and -`github.com/DataDog/dd-trace-go/contrib/log/slog/v2`) and either prints the -required versions or runs the repository-owned refresh command you provide. -The refresh command only runs after targeted sync succeeds, so a stale -`repositories.bzl` cannot mask a broken Go module graph. - -Manual Orchestrion wiring in `MODULE.bazel` accepts: - -| Setting | Default | Description | -|---------|---------|-------------| -| `orchestrion.from_source(..., dd_trace_go_pin_files = ["@//:go.mod", "@//:go.sum"])` | none | Preferred consumer mode. Derives exact direct and transitive supported tracer versions from one checked-in `go.mod` and `go.sum` using the Bazel-managed Go SDK and `-mod=readonly` | -| `orchestrion.from_source(..., dd_trace_go_version = "...")` | legacy `v2.9.1` when no selection mode is set | Explicit shared-version escape hatch that Bazel validates against the target Go module | -| `orchestrion.from_source(..., dd_trace_go_versions = {...})` | none | Exact canonical per-module tracer versions that Bazel validates against the target Go module for `github.com/DataDog/dd-trace-go/v2`, `github.com/DataDog/dd-trace-go/contrib/net/http/v2`, and `github.com/DataDog/dd-trace-go/contrib/log/slog/v2` | -| `orchestrion.from_source(..., go_sdk_root = "@repo//:ROOT")` | none | Bazel-managed Go SDK root used to build Orchestrion instead of discovering a host `go` binary | -| `orchestrion.from_source(..., go_sdk_version = "...")` | none | Exact version of `go_sdk_root`; enables bootstrap-cache lookup before SDK materialization and is verified after materialization on a miss | +## Go instrumentation SDK + +`dd_topt_go_test` builds every enabled test with a Go SDK whose compiler is +`ddtest instrument`, and links Mini, ddtest's Test Optimization runtime. The +companion module pins one ddtest release in `DDTEST_RELEASE` +([`modules/go/topt_go_ddtest_repository.bzl`](../modules/go/topt_go_ddtest_repository.bzl)): +its tag, runtime version, contract, SDK name (`ddto`), and the SHA256 of its +six executables. The ddtest side is described in +[ddtest's Bazel documentation](https://github.com/DataDog/ddtest/blob/main/ddto/docs/bazel.md). + +In Bzlmod, the root module configures the SDK to instrument; the Go companion +registers the toolchains: + +```bzl +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", +) +topt_go_ddtest.sdk(go_mod = "//:go.mod") # or go_version = "1.25.0" +``` + +`sdk` tag attributes: + +| Attribute | Type | Default | Description | +|-----------|------|---------|-------------| +| `sdk_version` | string | none | Value of `@io_bazel_rules_go//go/toolchain:sdk_version` that selects this SDK, for an SDK variant, for example `1.26.3fips`. The tag without it configures the default SDK | +| `go_mod` | label | none | `go.mod` whose `toolchain` directive, then `go` directive, gives the Go version, as rules_go's `go_sdk.from_file` reads it. The file's directory must be a Bazel package. A `go.mod` with neither directive fails | +| `go_version` | string | none | Go version of the SDK, for example `1.25.0`: the official SDK that exec platforms without `base_sdk_roots` download, and for the default SDK the version whose `sdk_version` requests it serves. Overrides `go_mod`. A base SDK from `base_sdk_roots` must have this version in its `VERSION` file, or its fetch fails | +| `base_sdk_roots` | string-keyed label dict | `{}` | Exec platform to a file at the root of the Go SDK to instrument there, for example `{"linux_amd64": "@go_sdk//:ROOT"}`. Other exec platforms download the official SDK of `go_version` | +| `exec_platforms` | string list | all six | Exec platforms to instrument: `darwin_amd64`, `darwin_arm64`, `linux_amd64`, `linux_arm64`, `windows_amd64`, `windows_arm64`. Other values fail | +| `experiments` | string list | `[]` | `GOEXPERIMENT` values of the base SDKs | +| `panic_hook` | bool | `False` | Record an unrecovered panic as the running test's failure before the process ends | Notes: -- The selected version is workspace-wide for Go. There is no per-test override. -- Pin-file mode is the normal consumer path. It requires exactly one file named - `go.mod` and one named `go.sum`, requires `go_sdk_root`, never uses host Go, - and never edits either file. If a supported tracer module is absent or the - copied module cannot be resolved read-only, use the explicit per-module map. -- Guided bootstrap declares `go_sdk_root` and `go_sdk_version` from - `--runtime-version`. Manual wiring must set both together and keep the version - equal to the registered Go toolchain and Test Optimization `runtime_version`. -- Orchestrion `v1.12.0` requires Go `1.25.0` or newer. -- Bootstrap repins the local Go module to the same effective versions. -- Bootstrap uses targeted module sync by default and does not run - `go mod tidy` unless `--go-mod-sync=tidy` is selected. -- Bootstrap accepts tags, pseudo-versions, branches, and commit SHAs through - `--dd-trace-go-version` and resolves them to canonical persisted versions. -- Manual `orchestrion.from_source(...)` tracer settings must already use - canonical persisted versions. Branches and commit SHAs are rejected there. -- The Orchestrion tool bootstrap records these versions in - `dd_trace_go_versions.json`, but it no longer rewrites the downloaded - Orchestrion source repo's own `go.mod`. -- Ordinary host-side Go commands used by the repository bootstrap resolve the - configured or platform-default `GOCACHE` and `GOMODCACHE` and reuse each one - when it is writable. A read-only cache is replaced for that repository - evaluation by `.orchestrion_bootstrap_go_cache/{cache,pkg/mod}`. The separate - persistent Datadog cache contains only the patched bootstrap artifacts and - offline module proxy. Cache-path discovery uses the installed Go binary with - `GOTOOLCHAIN=local`, so the probe cannot begin a toolchain download through a - read-only module cache. If that binary cannot report cache paths, both caches - use the repository-local fallback. Config-disabled evaluation returns before - any cache is selected. -- Bootstrap writes `dd_trace_go_version` when all traced modules resolve to one - shared version, and `dd_trace_go_versions` when they resolve to different - exact versions. -- If you rerun bootstrap without `--dd-trace-go-version`, it preserves the - existing bootstrap-managed tracer config instead of resetting it. -- Bootstrap refuses to proceed when active tracer settings already exist in - `orchestrion.from_source(...)` calls outside its managed block. Those manual - settings must be removed or migrated first. -- `dd_trace_go_pin_files`, `dd_trace_go_version`, and - `dd_trace_go_versions` are mutually exclusive. -- If the workspace setting and the effective local Go module versions differ, - the build fails instead of mixing versions. - -## Go Test Optimization Orchestrion mode - -`dd_topt_go_test(...)` accepts `orchestrion_mode`: - -| Value | Description | -|-------|-------------| -| `general` | Default. Preserves the generic Orchestrion behavior for the transitioned raw `go_test` | -| `test_optimization` | Standard Go `testing` Test Optimization mode. Keeps stdlib/`testing`, synthetic `testmain`, helper packagefiles, importcfg, and link support, while leaving customer package compiles and external `_test` package compiles on the plain rules_go path | - -`test_optimization` mode requires package-local pin files or explicit -`orchestrion_pin_files` pointing at the Go module root. It uses a reduced -synthetic Orchestrion tool file for action-time module work, so -`orchestrion.tool.go` is not required for the optimized mode even when a generic -Orchestrion setup keeps one for broader integrations. Automatic `testify/suite` -instrumentation is intentionally outside this mode. - -When `orchestrion_mode = "test_optimization"`, `dd_topt_go_test` also enables -test-binary linker optimization by default in Bazel modes where rules_go is not -already stripping. Disable it per target or in a repo-local wrapper with: +- Only the root module's tags count. It sets one `sdk` tag without + `sdk_version`, the default SDK, and one per SDK variant. +- Every selected exec platform needs `go_version` or a `base_sdk_roots` entry. +- The toolchains honor `@io_bazel_rules_go//go/toolchain:sdk_version` like + rules_go's own: the default SDK serves an unset value and the values that + rules_go matches for its `go_version` (`1`, `1.26` and `1.26.3` for + `1.26.3`); a variant serves only its `sdk_version`. Any other value fails; + see + [Instrumented test fails: no SDK for an sdk_version](Troubleshooting.md#instrumented-test-fails-no-sdk-for-an-sdk_version). +- Without an `sdk` tag, a stand-in toolchain fails instrumented tests with what + to configure; see + [Instrumented test fails: no Go SDK to instrument](Troubleshooting.md#instrumented-test-fails-no-go-sdk-to-instrument). +- Only instrumented tests and `dd_topt_go_stdlib_warmup` set + `@io_bazel_rules_go//go/toolchain:sdk_name` to `ddto`, which the toolchains + require. Other builds fetch neither the instrumented SDKs nor Mini, and + Bazel fetches only the SDKs of the exec platforms it selects. +- `ddtest instrument` writes the SDKs and Mini without the host's Go. +- Every Go target below an instrumented test, exec tools included, builds with + an instrumented SDK. Its hooks do nothing in programs that do not link Mini. + +A Go target below an instrumented test that a transition moves to another SDK, +such as `go_cross_binary(sdk_version = "1.26.3fips")` for a FIPS build, needs +an SDK variant with that `sdk_version`: ```bzl -enable_test_binary_linker_optimization = False +topt_go_ddtest.sdk(go_mod = "//:go.mod") +topt_go_ddtest.sdk( + experiments = ["boringcrypto"], + go_version = "1.26.3", + sdk_version = "1.26.3fips", +) ``` -Go target metadata records the selected mode in `bazel.go.orchestrion.mode` and -whether the linker optimization was active in +A variant takes the other attributes of the default SDK's tag, except that +`go_version` does not add the version's prefixes; with `base_sdk_roots` it +instruments an SDK that the workspace already declares for each exec +platform. +- Mini's repository is `@topt_go_ddtest_mini`. A workspace whose `go.mod` + requires `github.com/DataDog/ddtest/ddto` must replace go_deps' copy with + it; see + [Analysis fails: test would link Mini twice](Troubleshooting.md#analysis-fails-test-would-link-mini-twice). + +In WORKSPACE, declare upstream `rules_go` as `io_bazel_rules_go` (the SDK +repositories that `go_wrap_sdk` writes load it under that name). After +`go_rules_dependencies()`, call the WORKSPACE entry point, which also registers +the toolchains: + +```bzl +load("@datadog-rules-test-optimization-go//:topt_go_ddtest_repository.bzl", "topt_go_ddtest_workspace_repositories") + +topt_go_ddtest_workspace_repositories(go_version = "1.25.0") +``` + +It accepts `go_version`, `base_sdk_roots` (a dict of exec platform to label +string), `exec_platforms`, `experiments`, and `panic_hook`, with the meaning of +the `sdk` tag attributes above, and `sdk_variants`, a list of dicts with +`sdk_version` and those keys, one per SDK variant. It has no `go_mod`. See +[Configure Go support in WORKSPACE](Installation_Reference.md#8-configure-go-support-in-workspace-with-the-public-helper). + +## dd_topt_go_test attributes + +`dd_topt_go_test` (from `@datadog-rules-test-optimization-go//:topt_go_test.bzl`) +accepts: + +| Attribute | Default | Description | +|-----------|---------|-------------| +| `name` | required | Public test target name | +| `topt_data` | required | Single-service dict from `@//:export.bzl`, the multi-service `topt_data_by_service` mapping, or a local-static descriptor (below) | +| `topt_service` | `None` | With the multi-service mapping, the raw or sanitized service key to use | +| `module_label_override` | `None` | Sanitized module label suffix to use when automatic module detection does not match | +| `stage_sources` | `False` | Stage direct `srcs` and `embedsrcs` in runtime runfiles so the test can open its source files for metadata such as `test.source.end`. Sets `rundir` to `.` unless the caller sets `rundir` | +| `ci_visibility_enabled` | `True` | Set `DD_CIVISIBILITY_ENABLED=true` in the test environment. Set `False` only when the caller owns that variable | +| `enable_test_binary_linker_optimization` | `True` | Add `-s -w` to the hidden test's `gc_linkopts` where rules_go does not already strip (see below) | +| `go_test_rule` | rules_go `go_test` | Override for tests and low-level experiments; keep repository policy in a wrapper outside the macro | +| `**kwargs` | | Forwarded to `go_test` (`srcs`, `deps`, `embed`, `data`, `env`, `tags`, ...) | + +For an enabled export, the macro creates: + +- `__raw_go_test`, a hidden `go_test` (tagged `manual`, private) that the + public test builds with the instrumented Go SDK. Its `deps` get Mini's + `@topt_go_ddtest_mini//testopt` unless they already name it, and its `srcs` + get the registration file that `_ddtest_registration` writes from the + sources' package clauses. +- `_topt_payloads` and `_topt_bazel_metadata`, the payload + selector and target metadata helpers (tagged `manual`). +- ``, the public test. It runs the hidden test and copies + `bazel_target_metadata.json` into `TEST_UNDECLARED_OUTPUTS_DIR`. + +The test environment sets `DD_TEST_OPTIMIZATION_MANIFEST_FILE`, +`DD_TEST_OPTIMIZATION_PAYLOADS_IN_FILES=true`, and +`DD_TEST_OPTIMIZATION_BAZEL_TARGET_METADATA_BASENAME`; these win over caller +values. `DD_SERVICE` defaults to the sync metadata's service name unless the +caller's `env` sets it. `rundir` defaults to the test's package directory. + +For a disabled export, the macro validates its own inputs and calls +`go_test_rule(name = name, **kwargs)` with the caller's arguments only. + +The linker optimization adds `-s -w` except under `-c dbg`, `--strip=always`, +`--strip=never`, and `fastbuild` with the default `--strip=sometimes`, where +rules_go already decides about stripping. In practice it applies with `-c opt`. +It affects only the hidden test, never `go_binary` targets. Set +`enable_test_binary_linker_optimization = False` to keep the caller's +`gc_linkopts` as they are. Metadata records whether it applied in `bazel.go.test_binary_linker_optimization`. +A `no-remote-exec` tag on the test applies to its `TestRunner` action only; +the hidden test's compile and link actions stay remote-capable. + +Attributes that 2.0.0 removed fail at loading with a migration message; see the +[migration note](../CHANGELOG.md#unreleased). + ### Explicit single-service Go descriptor `dd_topt_go_test` also accepts a local-static `topt_data` descriptor for a @@ -217,10 +307,11 @@ bundle and reports `full_bundle_disabled`. ### Go target compatibility and wildcard commands `dd_topt_go_test` forwards the caller's `target_compatible_with` unchanged to -the public wrapper, hidden raw test, payload selector, and Bazel metadata -target. Lists and `select(...)` expressions are supported. Consumer wrappers -that make `.topt` targets incompatible outside `--config=test-optimization` -therefore apply the same restrictions to the selector and metadata helpers. +the public wrapper, hidden raw test, payload selector, Bazel metadata, and +registration targets. Lists and `select(...)` expressions are supported. +Consumer wrappers that make `.topt` targets incompatible outside +`--config=test-optimization` therefore apply the same restrictions to the +helpers. The rule does not introduce another enable flag or read environment variables from the macro. @@ -233,6 +324,33 @@ under Bazel's default behavior; it does not fall back to an ordinary test. Once the target is compatible, the existing repository identity and enablement checks still apply. +## Go target metadata + +`_topt_bazel_metadata` writes the Bazel-owned metadata of an enabled +`dd_topt_go_test` target. The public test copies it into +`TEST_UNDECLARED_OUTPUTS_DIR` as `bazel_target_metadata.json`, and the uploader +adds its keys to the target's events. + +| Key | Value | +|-----|-------| +| `bazel.package` | Bazel package of the public test, for example `//pkg` | +| `bazel.target` | Label of the public test | +| `bazel.test_optimization.repo_name` | Sync repository name | +| `bazel.test_optimization.service_name` | Service name of the selected sync metadata | +| `bazel.test_optimization.runtime_name` | `go` | +| `bazel.go.importpath` | Import path used for payload selection | +| `bazel.go.importpath_source` | `explicit`, `inferred`, or `fallback` | +| `bazel.go.payload_selection` | `module`, `module_override`, `full_bundle_disabled`, or `full_bundle_no_match` | +| `bazel.go.attr.cgo`, `bazel.go.attr.pure`, `bazel.go.attr.race`, `bazel.go.attr.msan`, `bazel.go.attr.linkmode` | The test's rules_go attributes, or their defaults | +| `bazel.go.attr.goos`, `bazel.go.attr.goarch` | The test's `goos` and `goarch`, only when set | +| `bazel.go.test_binary_linker_optimization` | Whether the macro added `-s -w` | +| `bazel.go.instrumentation.backend` | `ddtest` | +| `bazel.go.instrumentation.version` | Mini's runtime version, for example `v0.0.0-pre-release-f5df4db` | +| `bazel.go.instrumentation.release` | The pinned ddtest release tag, for example `pre-release-f5df4db` | +| `bazel.go.instrumentation.contract` | The `ddtest instrument bazel` contract, an integer (`1`) | +| `bazel.go.sdk.name` | `ddto`, the SDK name that selects the instrumented toolchains | +| `bazel.go.sdk.version` | Version of the instrumented Go SDK, when rules_go reports it | + ## Sync extension attributes Extension tag: `test_optimization_sync.test_optimization_sync(...)` @@ -540,29 +658,25 @@ invocation. They do not include `DD_CIVISIBILITY_AGENTLESS_ENABLED`. For Go onboarding, the generated block also contains -`common: --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`. Optimized Go -targets enable Orchestrion through their own transition, so no global -`orchestrion:enabled` build setting is generated. `--config=` remains -the single user-facing metadata gate. - -In that disabled state, the patched `rules_go` aliases select package-local -empty targets and the gated Orchestrion repository writes its stable empty -interface before host-Go discovery or source fetching. Analysis can therefore -resolve ordinary Go targets without a host Go binary while the config is absent. +`common: --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`. Instrumented Go +tests select the instrumented Go SDK through their own transition, so no other +build setting is generated. `--config=` remains the single user-facing +metadata gate. For Go, the disabled generated export also changes the macro expansion. `dd_topt_go_test` performs its macro-input and service-selection validation, then invokes the supplied `go_test_rule(name = name, **kwargs)` directly. It does not run importpath inference, select metadata payloads, inject Datadog -environment/data/linker inputs, or create hidden Test Optimization or -Orchestrion targets. The enabled export preserves the existing instrumented -expansion. Consumer-owned wrappers can therefore expose one public Go test -macro in both modes. +environment/data/linker inputs, or create hidden Test Optimization targets. +The resulting `go_test` uses the workspace's normal Go SDK, so Bazel fetches +neither the instrumented SDKs nor Mini. The enabled export creates the +instrumented expansion. Consumer-owned wrappers can therefore expose one public +Go test macro in both modes. Config-gated Python onboarding uses the same -`DD_TEST_OPTIMIZATION_ENABLED=1` repository environment entry but does not use -the Go-only Orchestrion setting. The Java, NodeJS, .NET, and Ruby companions -retain their existing enablement contract in this release. +`DD_TEST_OPTIMIZATION_ENABLED=1` repository environment entry. The Java, +NodeJS, .NET, and Ruby companions retain their existing enablement contract in +this release. ## How data is fetched @@ -612,7 +726,7 @@ changes can invalidate fetch cache entries as expected. - Sync metadata requests (repository rule phase), and - Agentless uploader intake requests (`bazel run //:dd_upload_payloads`). -For Go/Orchestrion Bazel file mode, neither +For Go tests, which write payloads to files, neither `DD_TEST_OPTIMIZATION_AGENTLESS_URL` nor `DD_TEST_OPTIMIZATION_AGENT_URL` belongs in the test sandbox. Put `DD_TEST_OPTIMIZATION_AGENTLESS_URL` in `--repo_env` only when the sync metadata fetch or uploader needs the override, @@ -736,9 +850,8 @@ same test inputs; request timing in post-test telemetry may differ. Filtering affects human-readable diagnostics on their existing stdout/stderr streams. It does not change JSON reports, helper return values, payload selection, uploads, or exit codes. Errors remain visible at every level. -Bazel's own output, compiler/Orchestrion subprocess diagnostics, explicitly -requested bootstrap probe records, and test/tracer logs keep their own -controls. This setting does not enable `DD_TRACE_DEBUG` or add GitLab sections. +Bazel's own output, compiler subprocess diagnostics, and test/tracer logs +keep their own controls. This setting does not enable `DD_TRACE_DEBUG` or add GitLab sections. ## Uploader runtime environment variables @@ -873,3 +986,18 @@ sync repository rule or uploader runtime paths above): - Linux/macOS harness supports existing shell env overrides documented in `tools/tests/integration/run_mock_server_tests.sh`. - Windows harness is PowerShell-only; no Git Bash path override variable is used. +- The Go consumer fixtures, + `tools/tests/integration/run_workspace_go_integration.sh` and + `tools/tests/integration/run_bzlmod_go_integration.sh`, read: + + | Variable | Default | Purpose | + |----------|---------|---------| + | `RULES_GO_VERSION` | `0.60.0` | Upstream rules_go version: a BCR `bazel_dep` in Bzlmod, the release zip in WORKSPACE (the WORKSPACE script knows the integrity of 0.60.0, 0.61.1, 0.62.0, 0.63.0, 0.64.2, and 0.65.0) | + | `GO_VERSION` | `1.25.0` | Go version of the instrumented SDK and the sync `runtime_version`; in WORKSPACE also the registered `@go_sdk`. rules_go 0.65.0 needs Go 1.26 | + | `FORBID_HOST_GO` | `0` | `1` puts a `go` on `PATH` that fails the run when called (Unix hosts only) | + | `INTEGRATION_SCENARIO_MODE` | `full` | `full` or `release`. In WORKSPACE, `release` runs only the archive fixture | + | `WINDOWS_SMOKE_ONLY` | `0` | Bzlmod only: `1` runs only the instrumented test and its payload check | + | `BAZEL_DISTDIR` | empty | Bzlmod only: passed to Bazel as `--distdir` | + | `KEEP_TMP` | `0` | `1` keeps the generated workspaces after the run | + | `BAZEL` | `./bazelw` | Bazel launcher | + | `BAZEL_VERSION` | `.bazelversion` | Bazel version passed as `USE_BAZEL_VERSION` | diff --git a/docs/Installation_Reference.md b/docs/Installation_Reference.md index ed105a0c..3add6ffa 100644 --- a/docs/Installation_Reference.md +++ b/docs/Installation_Reference.md @@ -25,9 +25,9 @@ publication. In `MODULE.bazel`, use `archive_override`, which uses Bazel's HTTP downloader and repository cache. In WORKSPACE, use `http_archive`. Reuse the same archive -and SHA256 for core, companions, and the selected Go fork. Each companion uses -`/modules/`; a Go fork uses -`/third_party/rgo//base`. +and SHA256 for core and companions. Each companion uses +`/modules/`. Go uses upstream `rules_go`, which you +declare yourself. The following is the shape of a core Bzlmod install. Replace every placeholder with the tuple from one release; do not copy a checksum from another version. @@ -42,14 +42,9 @@ archive_override( ) ``` -For Go, the bootstrap can infer the archive from a literal core/Go companion -override or preserve an existing supported `rules_go` archive override. It -keeps URL mirrors, SHA256, and the selected fork. A compatible manual fork -override moves into the managed block, so there is only one override per -module. Custom fork patches or computed Starlark values are not silently -rewritten. For computed dependency values, supply -`--rules-go-fetch=archive --rto-archive-url=... --rto-archive-sha256=... --rto-archive-prefix=...`. -`--rules-go-fetch=git` retains explicit Git-mode bootstrapping. +The Go bootstrap does not write or change core and companion overrides. Copy +them from `INSTALL.md`; the bootstrap adds the Go SDK configuration described +in [Go companion module](#go-companion-module). Release bundles are prepared by the [release workflow](../CONTRIBUTING.md#release-runbook-core--companion-modules). GitHub's automatic source downloads are distinct from the uploaded, checksum-pinned @@ -84,7 +79,8 @@ git_override( strip_prefix = "modules/go", ) -# Only needed when using dd_topt_go_test +# Only needed when using dd_topt_go_test. Also configure the Go SDK to +# instrument with topt_go_ddtest.sdk(...); see "Go companion module" below. bazel_dep(name = "rules_go", version = "0.60.0") # Optional companion module (only needed if you use dd_topt_py_test) @@ -143,24 +139,21 @@ For `v1.2.0`, use commit ### Generate published Go pins -Before copying Go/Orchestrion pins into a consumer repository, generate them -from a squash-merged commit that is already reachable from `origin/main`. +Before copying Go pins into a consumer repository, generate them from a +squash-merged commit that is already reachable from `origin/main`. From a `rules_test_optimization` checkout: ```bash ./bazelw run //tools/dev:print_go_onboarding_pins -- \ --commit "$(git rev-parse origin/main)" \ - --rules-go-upstream v0_60_0 \ - --variant base \ --verify-main-reachable ``` -The command prints the full tuple used by WORKSPACE archive mode: +The command prints the six values used by WORKSPACE git and archive mode: `RTO_COMMIT`, `RTO_REMOTE`, `RTO_ARCHIVE_URL`, `RTO_ARCHIVE_SHA256`, -`RTO_ARCHIVE_PREFIX`, `RTO_ARCHIVE_TYPE`, `RULES_GO_UPSTREAM`, -`RULES_GO_VARIANT`, `RULES_GO_STRIP_PREFIX`, `DD_TRACE_GO_VERSION`, and -`ORCHESTRION_VERSION`. Published GitHub codeload pins are `tar.gz`; use a +`RTO_ARCHIVE_PREFIX`, and `RTO_ARCHIVE_TYPE`. Add `--print-summary` to also +print a Markdown summary. Published GitHub codeload pins are `tar.gz`; use a separate repository-owned mirror process for any other archive format. #### Historical `v1.2.0` published tuple @@ -172,30 +165,23 @@ RTO_ARCHIVE_URL="https://codeload.github.com/DataDog/rules_test_optimization/tar RTO_ARCHIVE_SHA256="fd54d1871fc01ff0bb3db190dfaadaa8256edd68a4f3bb85ecc08b315fbf5bd4" RTO_ARCHIVE_PREFIX="rules_test_optimization-69953536d4ef1252c8181c267d16c61263f0aa4c" RTO_ARCHIVE_TYPE="tar.gz" -RULES_GO_UPSTREAM="v0_60_0" -RULES_GO_VARIANT="base" -RULES_GO_STRIP_PREFIX="third_party/rgo/v0_60_0/base" -DD_TRACE_GO_VERSION="v2.9.0" -ORCHESTRION_VERSION="v1.9.0" ``` -The archive URL, SHA256, and prefix are tied to the repository commit. +The archive URL, SHA256, and prefix are tied to the repository commit. This +tuple installs `v1.2.0`, which predates `ddtest instrument`; the Go setup on +this page needs a newer commit. From a consumer that already has the Go companion available, the bootstrap can -print the same tuple or write a checked-in Markdown summary: +print the same values or write a checked-in Markdown summary: ```bash bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --print-published-pins \ - --rto-commit \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base + --rto-commit bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --write-onboarding-summary=TEST_OPTIMIZATION_GUIDE.md \ - --rto-commit \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base + --rto-commit ``` The bootstrap summary command does not inspect `MODULE.bazel` or `go.mod`; it @@ -273,9 +259,36 @@ lives in companion modules: ### Go companion module +`dd_topt_go_test` builds each enabled test with the Go SDK that +`ddtest instrument` instruments, and links Mini, ddtest's Test Optimization +runtime. `rules_go` is plain upstream: Bzlmod consumers use +`bazel_dep(name = "rules_go", version = "0.60.0")` or newer, with no override. +The ddtest side is documented in +[ddtest's Bazel guide](https://github.com/DataDog/ddtest/blob/main/ddto/docs/bazel.md). + +The root module configures the Go SDK to instrument: + +```bzl +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", +) +topt_go_ddtest.sdk(go_mod = "//:go.mod") # or go_version = "1.25.0" +``` + +`go_mod` reads the `toolchain` directive, then the `go` directive, like +`rules_go`'s `go_sdk.from_file`; the package that holds `go.mod` needs a BUILD +file. `go_version` overrides `go_mod`. The Go companion registers the +toolchains, so the root module registers none. Only instrumented tests select +those toolchains, so other builds fetch neither the instrumented SDK nor Mini, +and Bazel fetches only the SDKs of the exec platforms it selects. ddtest does +not need a Go installation on the host. All `sdk` attributes are listed in +[`Configuration_Reference.md`](./Configuration_Reference.md#go-instrumentation-sdk). + For a fresh single-service Go workspace, the recommended path is: -1. add the core + Go companion dependency/override block +1. add the core + Go companion dependency/override block and the `rules_go` + `bazel_dep` 2. run guided bootstrap 3. use the generated local wrapper @@ -286,7 +299,6 @@ bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --guided \ --service go-service \ --runtime-version 1.25.0 \ - --dd-trace-go-version v2.9.1 \ --write-bazelrc ``` @@ -297,29 +309,23 @@ bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --guided \ --service go-service \ --runtime-version 1.25.0 \ - --dd-trace-go-version v2.9.1 \ --go-module-dir path/to/go-module \ --write-bazelrc ``` -`--dd-trace-go-version` is optional. If omitted, the workspace uses the default -`v2.9.1`. It accepts a tag, pseudo-version, -branch, or commit SHA. Bootstrap resolves that input to exact tracer versions, -keeps the local Go module pins on those same versions, and prevents Bazel and -the Go module from silently drifting apart. - -Bootstrap uses `--go-mod-sync=targeted` by default. Targeted sync updates the -Orchestrion and `dd-trace-go` tool requirements, resolves only the packages -needed by the generated `orchestrion.tool.go`, and verifies those packages with -`go list -mod=readonly`. It intentionally does not run `go mod tidy`, so large -workspaces avoid unrelated module rewrites. Use `--go-mod-sync=tidy` only when -you explicitly want bootstrap to tidy the whole module, or `--go-mod-sync=off` -when another repository-owned process manages `go.mod` and `go.sum`. - -Bootstrap runs those Go module commands with `go` by default. Use -`--go-binary=/path/to/go` when the repository must sync with a specific Go SDK, -for example the same SDK Bazel uses in a monorepo. The value must be a single -executable path named `go` or `go.exe`; do not include shell arguments. +Bootstrap writes a managed `MODULE.bazel` block between +`# BEGIN Datadog Go Test Optimization bootstrap` and +`# END Datadog Go Test Optimization bootstrap` with the `topt_go_ddtest` +extension. With `--runtime-version`, which guided mode requires, the block sets +`topt_go_ddtest.sdk(go_version = "")`. Without it, the block +sets `go_mod = "//:go.mod"` for the `--go-module-dir` module; bootstrap +stops when that directory has no BUILD file. Bootstrap also inserts +`bazel_dep(name = "rules_go", version = "0.60.0")` when `MODULE.bazel` has no +`rules_go` dependency. It does not write a `rules_go` override, and it does +not run Go commands or change `go.mod`. + +Guided mode also writes the single-service sync block, the root doctor and +uploader targets, and `//tools/build:dd_go_test.bzl`. Guided bootstrap is intentionally for single-service Go workspaces. If the workspace already uses conflicting or multi-service sync wiring: @@ -333,6 +339,25 @@ If the workspace already has a matching single-service `test_optimization_go_extension` plus `use_repo(...)`, guided bootstrap can reuse that wiring and continue. +Rerunning bootstrap on a workspace that 1.x set up replaces the Go bootstrap +block that 1.x wrote with the block above. It stops with a message when +`MODULE.bazel` still overrides `rules_go` with the 1.x fork outside that block; +remove that override and rerun. If the old block's fork commit no longer loads, +run bootstrap from another workspace with `--workspace `, or delete the +old block by hand. Managed pin-file blocks that 1.x wrote in BUILD files are +harmless and can be deleted. See the +[migration note](../CHANGELOG.md#unreleased). + +A Bzlmod workspace whose `go.mod` requires `github.com/DataDog/ddtest/ddto`, to +use Mini's Go API in tests, must replace `go_deps`' copy of that module with +the generated Mini. Otherwise the test fails analysis because it would link +Mini's packages twice: + +```bzl +use_repo(topt_go_ddtest, "topt_go_ddtest_mini") +override_repo(go_deps, com_github_datadog_ddtest_ddto = "topt_go_ddtest_mini") +``` + For WORKSPACE monorepos, use `--workspace-mode` instead of `--guided`. WORKSPACE mode deliberately does not edit `WORKSPACE`; it prints the repository wiring for manual placement and writes only local managed files: @@ -345,24 +370,23 @@ bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --runtime-version 1.25.0 \ --sync-repo-name test_optimization_data \ --rto-commit \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base \ --rules-go-repo-name io_bazel_rules_go ``` +The snippet declares upstream `rules_go` first, as a commented `http_archive` +placeholder with the release URL, then the core repository, +`datadog_go_test_optimization_workspace_repositories(...)`, +`go_rules_dependencies()`, `go_register_toolchains(...)`, +`topt_go_ddtest_workspace_repositories(go_version = ...)`, and the sync +repositories. Step 8 below shows the same wiring. + After placing the WORKSPACE snippet, generate the safe local scaffolding: ```bash bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --workspace-mode \ - --service go-service \ - --runtime-version 1.25.0 \ --sync-repo-name test_optimization_data \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base \ - --rules-go-repo-name io_bazel_rules_go \ --write-bazelrc \ - --write-orchestrion-files \ --write-wrapper-template \ --expected-target //pkg:go_default_test ``` @@ -375,31 +399,8 @@ The generated wrapper template creates one central local wrapper and retains the former optimized name only as a compatibility alias. Keep repository-specific scheduling, tags, flaky behavior, Docker defaults, and platform constraints in the local policy helper; the central wrapper owns only -`topt_data`, `orchestrion_mode = "test_optimization"`, and -`orchestrion_pin_files`. Omitting `--config=test-optimization` preserves normal -`go_test` behavior through that same entry point. -WORKSPACE mode does not run Go module commands unless `--go-mod-sync` is passed -explicitly, so large repos can review generated files before changing -`go.mod`/`go.sum`. - -If the repo also has checked-in `go_repository(...)` declarations, validate -them immediately after targeted sync: - -```bash -bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ - --workspace-mode \ - --write-orchestrion-files \ - --go-mod-sync=targeted \ - --check-go-repositories \ - --go-repositories-file repositories.bzl \ - --go-repositories-refresh-command './tools/update-go-repositories.sh' -``` - -The refresh command is repository-owned. Bootstrap never edits -`repositories.bzl` directly; it checks the Orchestrion and `dd-trace-go` module -versions, runs the refresh command only after targeted sync succeeds, and then -checks the file again. Use `--print-go-repository-updates` when you want the -expected versions printed for manual repair. +`topt_data`. Omitting `--config=test-optimization` preserves normal `go_test` +behavior through that same entry point. Generate a repeatable validation script when onboarding needs several control and instrumented targets: @@ -886,10 +887,11 @@ git_repository( Pin an immutable commit SHA (or internal mirrored archive) for reproducibility. -For WORKSPACE Go usage, declare the core repository first, then use the public -helper in step 8 to declare the Go companion and the Orchestrion-enabled -`rules_go` fork at the same revision: +For WORKSPACE Go usage, declare upstream `rules_go` as `io_bazel_rules_go` and +the core repository first, then use the public helper in step 8 to declare the +Go companion at the same Datadog revision: +- `io_bazel_rules_go` for Go rules and toolchains - `datadog-rules-test-optimization` for sync and uploader rules - `datadog-rules-test-optimization-go` for `dd_topt_go_test` @@ -1034,8 +1036,9 @@ and Python onboarding also includes: common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 ``` -Optimized Go targets enable Orchestrion through their own transition, so the -config has no global `orchestrion:enabled` setting. Java, NodeJS, .NET, and Ruby +Instrumented Go tests select the Go SDK that `ddtest instrument` instruments +through their own transition, so the config has no Go build setting. Java, +NodeJS, .NET, and Ruby retain their existing enablement contract and omit the metadata gate in this release. @@ -1118,7 +1121,7 @@ common:test-optimization --repo_env=RUBY_MODULE_PATH # Do not pass DD_GIT_* through --test_env. Git metadata belongs to the # repository-rule phase through --repo_env so it cannot invalidate test actions. # Do not pass DD_TEST_OPTIMIZATION_AGENT_URL or -# DD_TEST_OPTIMIZATION_AGENTLESS_URL through --test_env for Go/Orchestrion. +# DD_TEST_OPTIMIZATION_AGENTLESS_URL through --test_env for instrumented Go tests. ``` Security note: keep secret values out of `.bazelrc`. Forward variable names @@ -1244,7 +1247,7 @@ test_optimization_sync( Pair this opt-in repository rule with `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`. Python -does not use the Go-only `@rules_go//go/private/orchestrion:enabled` flag. +needs no Go setting. Then in package BUILD files, use either managed pytest mode: @@ -1365,105 +1368,102 @@ load("@datadog-rules-test-optimization-java//:topt_java_test.bzl", "dd_topt_java ### 8) Configure Go support in WORKSPACE with the public helper For Bzlmod single-service Go workspaces, prefer guided bootstrap instead. For -WORKSPACE consumers, prefer this helper over hand-written companion and -`rules_go` declarations. It keeps the Go companion repo mapping and the selected -Orchestrion-enabled `rules_go` variant consistent. +WORKSPACE consumers, prefer this helper over a hand-written companion +declaration. It declares only the Go companion and maps the companion's +`@io_bazel_rules_go` to your `rules_go` repository. It fails when that +repository is not declared yet. -The helper assumes the core `datadog-rules-test-optimization` repository has -already been declared in step 1. - -Default Git fetch mode: +Declare upstream `rules_go` first, under the name `io_bazel_rules_go`: the SDK +repositories that `go_wrap_sdk` writes for the instrumented SDK load it under +that name. Then declare the core repository (step 1), the Go companion, +`rules_go`'s dependencies and toolchains, the instrumented SDK, and the sync +repository, in this order: ```bzl +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "io_bazel_rules_go", + sha256 = "", + urls = ["https://github.com/bazel-contrib/rules_go/releases/download/v0.60.0/rules_go-v0.60.0.zip"], +) + +# Declare datadog-rules-test-optimization here (step 1). + load("@datadog-rules-test-optimization//tools/go:workspace_repositories.bzl", "datadog_go_test_optimization_workspace_repositories") datadog_go_test_optimization_workspace_repositories( rto_commit = "", - rules_go_repo_name = "io_bazel_rules_go", - rules_go_upstream = "v0_60_0", - rules_go_variant = "base", +) + +load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") + +go_rules_dependencies() + +# Bazel's coverage helper needs a shell toolchain even for a plain go_test. +# Bzlmod registers it automatically; WORKSPACE consumers must do so. +load("@rules_shell//shell:repositories.bzl", "rules_shell_dependencies", "rules_shell_toolchains") + +rules_shell_dependencies() + +rules_shell_toolchains() + +go_register_toolchains(version = "1.25.0") + +load("@datadog-rules-test-optimization-go//:topt_go_ddtest_repository.bzl", "topt_go_ddtest_workspace_repositories") + +topt_go_ddtest_workspace_repositories(go_version = "1.25.0") + +load("@datadog-rules-test-optimization-go//:topt_go_workspace.bzl", "dd_topt_go_workspace_sync_repositories") + +dd_topt_go_workspace_sync_repositories( + name = "test_optimization_data", + service = "go-service", + module_path = "github.com/example/service", + runtime_version = "1.25.0", + require_git_metadata = True, ) ``` Archive mode for mirrored environments: ```bzl -load("@datadog-rules-test-optimization//tools/go:workspace_repositories.bzl", "datadog_go_test_optimization_workspace_repositories") - datadog_go_test_optimization_workspace_repositories( rto_commit = "", datadog_fetch = "archive", - rules_go_fetch = "archive", - rules_go_repo_name = "io_bazel_rules_go", - rules_go_upstream = "v0_60_0", - rules_go_variant = "base", rto_archive_url = "https://artifacts.example.internal/bazel-mirror/datadog/rules_test_optimization/.tar.gz", rto_archive_sha256 = "", rto_archive_prefix = "rules_test_optimization-", ) ``` -Supported helper combinations are `git/git`, `git/archive`, and -`archive/archive`. Use `rules_go_variant = "base"` for supported consumers. -When multiple upstream `rules_go` versions are supported, use -`rules_go_upstream` to select the upstream support line; omit it to preserve -the repository default. The default `rules_go_upstream` is currently `v0_60_0`, -which preserves the existing `third_party/rgo/v0_60_0/base` path. The -helper never applies `patches`, `patch_tool`, or `patch_args`; it points -consumers at a complete base `rules_go` tree. - -Then configure Go, Gazelle, and the Orchestrion tool repository: - -```bzl -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -http_archive( - name = "bazel_gazelle", - urls = [ - "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.39.1/bazel-gazelle-v0.39.1.tar.gz", - ], - sha256 = "", -) - -load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") -load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") -load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo") - -dd_topt_go_orchestrion_tool_repo( - version = "", - dd_trace_go_pin_files = [ - "@//:go.mod", - "@//:go.sum", - ], - go_sdk_root = "@go_sdk//:ROOT", - go_sdk_version = "1.25.0", -) - -go_rules_dependencies() -go_register_toolchains(version = "1.25.0") -gazelle_dependencies() -``` - -Notes for the helper: - -- `version` is required in WORKSPACE mode. -- Export the root `go.mod` and `go.sum` from its BUILD package. Pin-file mode - derives every supported direct or transitive tracer module with - `-mod=readonly` and does not modify those files. -- `dd_trace_go_pin_files`, `dd_trace_go_version`, and - `dd_trace_go_versions` are mutually exclusive. The explicit forms are escape - hatches for module graphs that the normal pin-file mode cannot resolve. -- `go_sdk_root` must reference the SDK registered by - `go_register_toolchains`, and `go_sdk_version` must equal that toolchain - version. Enabled bootstrap uses this SDK instead of a host `go` binary. -- Keep the default tool-repo name `rules_go_orchestrion_tool`; the current fork - resolves that name internally. -- Declare the real tool repository before `go_rules_dependencies()`. The fork - supplies its own empty fallback for ordinary WORKSPACE consumers, so do not - load or declare a private stub repository yourself. -- Do not configure `patches`, `patch_tool`, or `patch_args` for this integration; - repositories that already own their `rules_go` patch stack should generate the - public consumer patch profile and rebase or merge it locally. +Notes for the helpers: + +- `datadog_fetch` is `git` (default) or `archive`. Archive mode requires + `rto_archive_url`, `rto_archive_sha256`, and `rto_archive_prefix`; + `rto_archive_type` defaults to `tar.gz`. Use values generated from one + published commit; see [Generate published Go pins](#generate-published-go-pins). +- `rules_go_repo_name` must be `io_bazel_rules_go`, its default; the helper fails with any other name. +- `topt_go_ddtest_workspace_repositories` declares the ddtest executable, + Mini's repository, and one instrumented SDK per exec platform, and registers + their toolchains. It takes the same arguments as the Bzlmod + `topt_go_ddtest.sdk` tag except `go_mod`: `go_version`, `base_sdk_roots`, + `exec_platforms`, `experiments`, and `panic_hook`, plus `sdk_variants` for + SDK variants that an `sdk_version` selects. See + [`Configuration_Reference.md`](./Configuration_Reference.md#go-instrumentation-sdk). +- To instrument the SDK that `go_register_toolchains` registered instead of + downloading the official one, name its root file per exec platform: + + ```bzl + topt_go_ddtest_workspace_repositories( + base_sdk_roots = {"linux_amd64": "@go_sdk//:ROOT"}, + exec_platforms = ["linux_amd64"], + ) + ``` + +- Only instrumented tests select the instrumented toolchains, so other builds + fetch neither the SDKs nor Mini, and Bazel fetches only the SDKs of the exec + platforms it selects. ddtest does not need a Go installation on the host. Then in your Go package `BUILD.bazel`: @@ -1474,36 +1474,34 @@ load("@test_optimization_data//:export.bzl", "topt_data") go_library( name = "pkg_lib", - srcs = ["*.go"], + srcs = ["pkg.go"], + importpath = "github.com/example/service/pkg", ) dd_topt_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], # Enables provider-based importpath inference - orchestrion_mode = "test_optimization", topt_data = topt_data, ) ``` -Use `orchestrion_mode = "test_optimization"` for standard Go `testing` Test -Optimization. `general` remains the default mode for broader generic -Orchestrion behavior; the full mode contract is summarized in -[`Configuration_Reference.md`](./Configuration_Reference.md#go-test-optimization-orchestrion-mode). -The `test_optimization` mode keeps the stdlib `testing` instrumentation and -synthetic `testmain` support required for payloads while leaving customer -package compiles on the normal rules_go path. It does not automatically -instrument `testify/suite`. +With Test Optimization enabled, the macro builds a hidden raw `go_test` with +the Go SDK that `ddtest instrument` instruments, adds Mini's `testopt` library +and a registration file to it, and exposes `pkg_go_test` as the public test. +The macro's attributes are listed in +[`Configuration_Reference.md`](./Configuration_Reference.md#dd_topt_go_test-attributes). +The ddtest side is documented in +[ddtest's Bazel guide](https://github.com/DataDog/ddtest/blob/main/ddto/docs/bazel.md). -If the tracer needs runtime-visible source files for AST-derived metadata such -as `test.source.end`, enable source staging explicitly: +To make the direct test sources available at run time, enable source staging +explicitly: ```bzl dd_topt_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], - orchestrion_mode = "test_optimization", stage_sources = True, topt_data = topt_data, ) @@ -1513,40 +1511,10 @@ dd_topt_go_test( `embedsrcs`. When enabled, it changes the default `rundir` to `.` only if the caller did not already set `rundir`. -Note: in WORKSPACE mode, Go support uses two repositories: +Note: in WORKSPACE mode, Go support uses three repositories: +- `io_bazel_rules_go` for upstream `rules_go` - `datadog-rules-test-optimization` for the core rules - `datadog-rules-test-optimization-go` for the Go companion -The repository bound to `@io_bazel_rules_go` must be an Orchestrion-enabled -`rules_go` fork or a consumer-owned merge that includes the Orchestrion -workspace helper. Add `repo_mapping = {"@rules_go": "@io_bazel_rules_go"}` on -the `datadog-rules-test-optimization-go` repository declaration so the Go -companion resolves that fork consistently. - -Also note that Orchestrion-backed Go tests expect the local Go module files to -be pinned consistently for instrumentation. `dd_topt_go_test` auto-stages -package-local pin files when they live next to the BUILD file, but nested test -packages should pass the module-root labels explicitly through -`orchestrion_pin_files`, for example: - -```bzl -dd_topt_go_test( - name = "pkg_go_test", - srcs = ["*_test.go"], - embed = [":pkg_lib"], - orchestrion_mode = "test_optimization", - orchestrion_pin_files = [ - "//:go.mod", - "//:go.sum", - "//:orchestrion.tool.go", - "//:orchestrion.yml", - ], - topt_data = topt_data, -) -``` - -For pure `test_optimization` mode, `go.mod` plus `go.sum` are the required module -pins and `orchestrion.yml` is included when the repository uses one. A generic -Orchestrion setup may still keep `orchestrion.tool.go`; the optimized mode uses -a reduced synthetic tool file for action-time module work. +No pin files or Go module changes are needed for the instrumentation. diff --git a/docs/Language_Onboarding.md b/docs/Language_Onboarding.md index a33e2f72..6bacb144 100644 --- a/docs/Language_Onboarding.md +++ b/docs/Language_Onboarding.md @@ -80,14 +80,14 @@ enablement contract of the other companions: common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 ``` -Go optimized targets enable Orchestrion through their own transition. The -generated block therefore needs no global `orchestrion:enabled` setting; this -also allows ordinary and optimized Go tests to run together without changing -the ordinary target's configuration. +Instrumented Go tests select the Go SDK that `ddtest instrument` instruments +through their own transition. The generated block therefore needs no Go build +setting, and ordinary and instrumented Go tests can run together without +changing the ordinary target's configuration. Pass `DD_GIT_*` only through `--repo_env`. Never forward it as test environment data because that makes Git metadata part of the test action cache -key. For Go/Orchestrion, do not put `DD_TEST_OPTIMIZATION_AGENT_URL` or +key. For instrumented Go tests, do not put `DD_TEST_OPTIMIZATION_AGENT_URL` or `DD_TEST_OPTIMIZATION_AGENTLESS_URL` in `--test_env`; the uploader reads upload endpoints at `bazel run` time. @@ -228,6 +228,11 @@ archive_override( bazel_dep(name = "rules_go", version = "0.60.0") ``` +Go tests build with the Go SDK that `ddtest instrument` instruments and link +Mini, ddtest's Test Optimization runtime. `rules_go` is plain upstream, with no +override. The ddtest side is documented in +[ddtest's Bazel guide](https://github.com/DataDog/ddtest/blob/main/ddto/docs/bazel.md). + Bootstrap once: ```bash @@ -235,26 +240,37 @@ bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --guided \ --service go-service \ --runtime-version 1.25.0 \ - --dd-trace-go-version v2.9.1 \ --write-bazelrc ``` -The default Go module sync mode is `targeted`, so bootstrap does not run -`go mod tidy` unless you pass `--go-mod-sync=tidy`. In large repositories, pass -`--go-binary=/path/to/go` when bootstrap must use the same pinned Go SDK as -Bazel. The path must point to a `go` or `go.exe` executable and must not include -arguments. +The bootstrap adds a managed `MODULE.bazel` block that configures the Go SDK to +instrument: + +```bzl +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", +) +topt_go_ddtest.sdk(go_version = "1.25.0") +``` + +To follow the Go module instead, write the same block by hand with +`topt_go_ddtest.sdk(go_mod = "//:go.mod")`; the package that holds `go.mod` +needs a BUILD file. The Go companion registers the toolchains. Only +instrumented tests select them, so other builds fetch neither the instrumented +SDK nor Mini. The `sdk` attributes are listed in the +[Configuration Reference](Configuration_Reference.md#go-instrumentation-sdk). The bootstrap writes `//tools/build:dd_go_test.bzl` and creates `//:dd_test_optimization_doctor` plus `//:dd_upload_payloads` when they are missing. With `--write-bazelrc`, it also writes the managed -`test-optimization` config used by the command examples above. +`test-optimization` config used by the command examples above. It does not run +Go commands or change `go.mod`. -`--dd-trace-go-version` is optional. If omitted, the default is -`v2.9.1`. It accepts a tag, pseudo-version, -branch, or commit SHA. Bootstrap resolves that input to the exact tracer -versions Bazel will use, repins the local Go module to match, and later builds -fail fast if the workspace setting and local pins no longer match. +Rerunning the bootstrap on a workspace that 1.x set up replaces the Go +bootstrap block that 1.x wrote. See the +[Installation Reference](Installation_Reference.md#go-companion-module) and the +[migration note](../CHANGELOG.md#unreleased). ```bzl # package BUILD.bazel @@ -263,12 +279,13 @@ load("//tools/build:dd_go_test.bzl", "dd_go_test") go_library( name = "pkg_lib", - srcs = ["*.go"], + srcs = ["pkg.go"], + importpath = "github.com/example/service/pkg", ) dd_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], ) ``` @@ -279,7 +296,7 @@ supports `stage_sources = True` directly: ```bzl dd_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], stage_sources = True, ) @@ -290,61 +307,47 @@ dd_go_test( did not already set `rundir` yourself. If the workspace already has custom sync wiring, skip guided bootstrap and use -the manual `dd_topt_go_test(..., topt_data = ...)` path from `README.md`. -In WORKSPACE mode, that manual path still uses the Go companion as its own -repository and loads the macro from -`@datadog-rules-test-optimization-go//:topt_go_test.bzl`. The companion must -resolve an Orchestrion-enabled `rules_go` fork, so the WORKSPACE wiring for -that fork needs `repo_mapping = {"@rules_go": "@io_bazel_rules_go"}` or the -equivalent mapping used by the consumer's repository layout. That fork also -needs to expose the public `go_orchestrion_tool_repo(...)` helper, preserve the -`//go/private/orchestrion:*` targets that the companion transition uses, and -keep the default tool-repo name `rules_go_orchestrion_tool`. When Go tests live -below the module root, pass the module-root pin files through -`orchestrion_pin_files` or inject them from a repo-local wrapper. -`dd_topt_go_test` supports two Orchestrion modes: `general`, the default generic -Orchestrion path, and `test_optimization`, the standard Go `testing` Test -Optimization path. For standard Go `testing`, set -`orchestrion_mode = "test_optimization"` on the `dd_topt_go_test` call or -central repo-local wrapper. Automatic `testify/suite` instrumentation is not -part of that mode. +the manual `dd_topt_go_test(..., topt_data = ...)` path from `README.md`, with +the `topt_go_ddtest.sdk(...)` block above. In WORKSPACE mode, that manual path +still uses the Go companion as its own repository and loads the macro from +`@datadog-rules-test-optimization-go//:topt_go_test.bzl`. Upstream `rules_go` +must be named `io_bazel_rules_go`, and `topt_go_ddtest_workspace_repositories` +declares the instrumented SDK; see +[step 8 of the Installation Reference](Installation_Reference.md#8-configure-go-support-in-workspace-with-the-public-helper). +No pin files or Go module changes are needed. For WORKSPACE monorepos, prefer bootstrap `--workspace-mode` to generate the -generic local scaffolding. It can write the `.bazelrc` block, Orchestrion pin -files, and a config-gated central wrapper template while leaving `WORKSPACE` -placement under repository control. Put the single doctor/uploader pair in a -lightweight package instead of modifying the root BUILD. A large repository -can keep one public `dd_go_test`, route its enrolled package set to -`dd_topt_go_test` internally, and rely on `--config=test-optimization` to -choose the raw or instrumented shape without adding per-target Test -Optimization attributes. +generic local scaffolding. It can write the `.bazelrc` block and a config-gated +central wrapper template while leaving `WORKSPACE` placement under repository +control. Put the single doctor/uploader pair in a lightweight package instead +of modifying the root BUILD. A large repository can keep one public +`dd_go_test`, route its enrolled package set to `dd_topt_go_test` internally, +and rely on `--config=test-optimization` to choose the raw or instrumented +shape without adding per-target Test Optimization attributes. ### Large WORKSPACE monorepos Use this flow when the repository already has substantial `WORKSPACE` wiring, -custom Go wrappers, checked-in Gazelle output, or a non-default `rules_go` -repository name. The goal is to add Test Optimization without replacing the -repository's existing build policy. +custom Go wrappers, or checked-in Gazelle output. The goal is to add Test +Optimization without replacing the repository's existing build policy. Start by identifying the existing Go shape: -- The Bazel repository name used for `rules_go`, for example - `io_bazel_rules_go`. +- The Bazel repository name used for `rules_go`. It must be + `io_bazel_rules_go`: the SDK repositories that `go_wrap_sdk` writes for the + instrumented SDK load `rules_go` under that name. - The Go module path that owns the pilot tests. -- The Go SDK version used by the repository. +- The Go SDK version used by the repository, and the exec platforms that run + its tests. - The smallest set of runtime test targets that should emit payloads. - Any plain or build-only control targets that should keep using the existing wrapper path. -Wire the public WORKSPACE helper instead of copying patch directories or -declaring `patches = [...]`. Use `rules_go_variant = "base"`. When multiple -upstream `rules_go` versions are supported, use `rules_go_upstream` to choose -the upstream support line; omit it to preserve the repository default. The -default `rules_go_upstream` is currently `v0_60_0`, which preserves the -existing `third_party/rgo/v0_60_0/base` path. Use the current -published tuple from -[`docs/Installation_Reference.md`](Installation_Reference.md#current-v120-published-tuple) -for the commit, archive URL, archive SHA256, and archive prefix values. +Wire the public WORKSPACE helpers. Use the values generated as described in +[Generate published Go pins](Installation_Reference.md#generate-published-go-pins) +for the commit and, in archive mode, the archive URL, SHA256, and prefix. +[Step 8 of the Installation Reference](Installation_Reference.md#8-configure-go-support-in-workspace-with-the-public-helper) +shows the complete order. ```bzl load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") @@ -362,26 +365,17 @@ load( datadog_go_test_optimization_workspace_repositories( rto_commit = "", - datadog_fetch = "git", - rules_go_repo_name = "", - rules_go_fetch = "archive", - rules_go_upstream = "v0_60_0", - rules_go_variant = "base", - rto_archive_url = "https://codeload.github.com/DataDog/rules_test_optimization/tar.gz/", - rto_archive_sha256 = "", - rto_archive_prefix = "rules_test_optimization-", -) - -load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo") - -dd_topt_go_orchestrion_tool_repo( - version = "v1.12.0", - dd_trace_go_pin_files = [ - "@//:go.mod", - "@//:go.sum", - ], - go_sdk_root = "@go_sdk//:ROOT", - go_sdk_version = "", +) + +# After go_rules_dependencies() and go_register_toolchains(version = ""): +load( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_repository.bzl", + "topt_go_ddtest_workspace_repositories", +) + +topt_go_ddtest_workspace_repositories( + base_sdk_roots = {"linux_amd64": "@go_sdk//:ROOT"}, + exec_platforms = ["linux_amd64"], ) load( @@ -398,32 +392,30 @@ dd_topt_go_workspace_sync_repositories( ) ``` +`base_sdk_roots` instruments the repository's own SDK, the one that +`go_register_toolchains` registers as `@go_sdk`, so instrumented tests use the +same Go version as ordinary tests. List every exec platform that runs +instrumented tests. To download the official SDK instead, pass +`go_version = ""`. ddtest does not need a Go installation on +the host, and only instrumented tests fetch the instrumented SDK and Mini. + The public Go helper enables metadata gating by default. The single -`--config=test-optimization` switch controls both metadata sync and the -Orchestrion aliases; no per-target or per-repository enable attribute is needed. -`@go_sdk//:ROOT` comes from the repository's central -`go_register_toolchains(version = "")` declaration. Keep that -version, `go_sdk_version`, and `runtime_version` equal so enabled bootstrap does -not depend on a host `go` binary. Export the root `go.mod` and `go.sum` from -their BUILD package. Pin-file mode resolves the complete selected module graph, -so changing `go.mod` is the only tracer-version update required for normal -onboarding. Use `dd_trace_go_version` or `dd_trace_go_versions` only as an -explicit escape hatch for module graphs that pin-file mode cannot represent. -Without the config, `dd_topt_go_test` delegates directly to the consumer's -original `go_test_rule` under the public name, preserves the caller's Go rule -kwargs, and creates no Test Optimization-owned hidden targets. With the config, -the same BUILD call uses the existing Orchestrion-backed Test Optimization -shape. +`--config=test-optimization` switch controls metadata sync and, through the +enabled export, the instrumentation; no per-target or per-repository enable +attribute is needed. Without the config, `dd_topt_go_test` delegates directly +to the consumer's original `go_test_rule` under the public name, preserves the +caller's Go rule kwargs, and creates no Test Optimization-owned hidden targets. +With the config, the same BUILD call builds the test with the Go SDK that +`ddtest instrument` instruments. Python follows the same single-switch contract when its sync declaration uses `enabled_by_env = True`: without the config, the wrapper preserves the normal test runner but omits Test Optimization metadata, instrumentation, and payloads. -If the environment can fetch Git repositories reliably, use -`rules_go_fetch = "git"` and omit the archive attributes. If the environment -mirrors or blocks GitHub codeload archives, publish the same commit to a mirror -controlled by the consuming organization and point `rto_archive_url` at that -mirror. +If the environment mirrors or blocks GitHub, publish the same commit to a mirror +controlled by the consuming organization and call the helper with +`datadog_fetch = "archive"`, `rto_archive_url` pointing at that mirror, +`rto_archive_sha256`, and `rto_archive_prefix`. `module_path` on the public Go helper is preferred for checked-in configuration because it makes module selection explicit and does not depend on operator shell state. If the @@ -436,17 +428,9 @@ Use bootstrap to write the local pieces that are safe to generate, but keep ```bash bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --workspace-mode \ - --service "" \ - --runtime-version "" \ - --rules-go-repo-name "" \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base \ - --dd-trace-go-version v2.9.1 \ --write-bazelrc \ - --write-orchestrion-files \ --write-wrapper-template \ --write-validation-script \ - --check-go-repositories \ --large-monorepo \ --shutdown-bazel-on-exit \ --default-jobs=1 \ @@ -463,29 +447,15 @@ The generated wrapper template should replace the implementation behind the repository's existing central `dd_go_test` entry point. Keep scheduling, tags, flaky policy, Docker defaults, platform constraints, and other repository-specific behavior in the local helper. The same central wrapper -should call `dd_topt_go_test`, pass `topt_data`, set -`orchestrion_mode = "test_optimization"`, and provide module-root pin files: +should call `dd_topt_go_test` and pass `topt_data`. Do not add a second +optimized macro name: omitting `--config=test-optimization` makes the generated +disabled export preserve normal `go_test` behavior under the existing entry +point. -```bzl -orchestrion_mode = "test_optimization", -orchestrion_pin_files = [ - "//:go.mod", - "//:go.sum", - "//:orchestrion.tool.go", - "//:orchestrion.yml", -] -``` - -Export the pin files from the root package if the repository's package layout -requires that for cross-package labels. Do not add a second optimized macro -name: omitting `--config=test-optimization` makes the generated disabled export -preserve normal `go_test` behavior under the existing entry point. - -If the repository has checked-in `go_repository` declarations, run bootstrap -with `--check-go-repositories` and then use the repository-owned refresh command -to add or update the Orchestrion and tracer dependencies. Do not hand-edit large -generated dependency files unless that is already the repository's documented -maintenance path. +The instrumentation needs no pin files and no `go.mod` or `go_repository` +changes. If tests use Mini's Go API, the analysis check described in +[Troubleshooting](Troubleshooting.md#analysis-fails-test-would-link-mini-twice) +applies. Validate in this order: @@ -532,7 +502,7 @@ but they do not include uploader dry-run or upload results. If the doctor reports missing Git metadata, missing Bazel metadata, msgpack payloads, or `full_bundle_no_match` for a known pilot that requires module -selection, fix the sync, wrapper, tracer, or uploader configuration. Generic +selection, fix the sync, wrapper, or uploader configuration. Generic inferred/derived selection may intentionally fall back to the canonical bundle; that path reports `full_bundle_no_match` and requires the doctor target to set `forbid_full_bundle_no_match = False`. Keep the default rejection for known @@ -565,6 +535,12 @@ archive_override( bazel_dep(name = "rules_go", version = "0.60.0") +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", +) +topt_go_ddtest.sdk(go_mod = "//:go.mod") + go_topt = use_extension( "@datadog-rules-test-optimization-go//:topt_go_extension.bzl", "test_optimization_go_extension", @@ -592,14 +568,14 @@ load("@test_optimization_data//:export.bzl", "topt_data_by_service") go_library( name = "pkg_lib", - srcs = ["*.go"], + srcs = ["pkg.go"], + importpath = "github.com/example/service/pkg", ) dd_topt_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], - orchestrion_mode = "test_optimization", topt_data = topt_data_by_service, topt_service = "go_service_a", ) diff --git a/docs/Maintainers.md b/docs/Maintainers.md index aa0b960f..d105c8a2 100644 --- a/docs/Maintainers.md +++ b/docs/Maintainers.md @@ -246,6 +246,19 @@ Notes: uploader script/runtime evolution can ship without forcing a rules contract bump, while payload metadata still carries both values for observability. +## ddtest instrument pin + +- `modules/go/topt_go_ddtest_repository.bzl` pins one ddtest release in + `DDTEST_RELEASE`: the release tag, the runtime version that ddtest writes + (`v0.0.0-` before a pre-release tag), the contract, the SDK name, and the + SHA256 of the six executables. Update all of them from the same release in + one change. +- The repository rules compare every `DDTEST_INSTRUMENT` file that ddtest + writes with the pinned contract, SDK name, and runtime version, and fail on a + mismatch. Target metadata reports the same values. +- The ddtest side of the integration is documented in + [ddtest's Bazel guide](https://github.com/DataDog/ddtest/blob/main/ddto/docs/bazel.md). + ## CI and reproducibility policy - CI includes a Linux hermetic lane (`bazel-tests-hermetic`) with sandboxed @@ -275,11 +288,11 @@ Notes: - CI also keeps a dedicated WORKSPACE-compat probe on Bazel `8.8.2` (separate from the `8.8.2` baseline lanes) so legacy `--enable_workspace` behavior is continuously exercised during Bazel 9 migration. -- The generated `rules_go` profile verifier rebuilds each maintained upstream - in two isolated output roots and compares action keys and bytes for - `GoStdlib`, `GoSyntheticTestmainHelpers`, synthetic `GoCompilePkg` - (`~testmain.a`), and `GoLink`. Ordinary `GoStdlib` outputs must not publish an - instrumented cache. Local compact execution-log checks require `zstd`. +- `workspace-compat-shard` runs the WORKSPACE and Bzlmod Go consumer scripts + once per supported upstream `rules_go` version, with the host's `go` replaced + by a failing sentinel. The scripts check with `aquery` that the test's + compiles, link, and standard library use the Go SDK that `ddtest instrument` + instruments. `windows-go-smoke` runs one instrumented Bzlmod test on Windows. - Current PR baseline checks: ```sh diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md index f9e51c54..a1d13337 100644 --- a/docs/Troubleshooting.md +++ b/docs/Troubleshooting.md @@ -27,14 +27,14 @@ languages set the metadata repository environment: common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 ``` -Go optimized targets enable Orchestrion through their own transition; do not -add a global `orchestrion:enabled` setting. +Instrumented Go tests select the Go SDK that `ddtest instrument` instruments +through their own transition; no other build setting is needed. When the config is omitted, the public Go extension's config-gated default and repositories explicitly configured with `enabled_by_env = True` generate the -documented no-fetch stubs. Go aliases select local empty targets; Python keeps -the normal consumer runner without Test Optimization metadata or payload -wiring. Java, NodeJS, .NET, and Ruby retain their existing enablement contract +documented no-fetch stubs. `dd_topt_go_test` then creates a plain `go_test`; +Python keeps the normal consumer runner without Test Optimization metadata or +payload wiring. Java, NodeJS, .NET, and Ruby retain their existing enablement contract in this release. ## Quick triage map @@ -42,7 +42,7 @@ in this release. | Symptom | First checks | Likely section | |---------|--------------|----------------| | No files fetched or stale data | `DD_API_KEY` forwarded with `--repo_env`; use `FETCH_SALT` only for an explicit force-refresh sync | Repository rule not fetching data | -| Uploader says no payload files | tracer file-mode contract + payload files under `bazel-testlogs/*/test.outputs/` | Uploader not finding payloads | +| Uploader says no payload files | tracer file-mode contract + payload files under `bazel-testlogs/*/test.outputs/` | Uploader not finding payloads, Instrumented test writes no payloads | | Doctor reports msgpack payloads | tracer is not in Bazel JSON file mode | Doctor failures | | Doctor reports missing Git or Bazel metadata | sync metadata context or sidecar metadata is absent | Doctor failures | | Uploaded tests miss Git or Bazel tags | run uploader dry-run enrichment validation | Uploader enrichment dry-run | @@ -51,8 +51,12 @@ in this release. | Upload network errors | credential mode (agentless vs EVP), intake reachability | Tests not uploading (network errors) | | CI failure requires log archaeology | archive the support bundle from the failing run | Collect diagnostic reports | | Module selection misses | `bazel query` for `module_*` targets and importpath/module label expectations | Per-module files not found | -| Go build fails with a tracer version mismatch | `dd_trace_go_pin_files`, explicit version escape hatches, local `go.mod`/`go.sum` | Go tracer version drift | -| Bazel resolves an older tracer or Orchestrion module in WORKSPACE mode | checked-in `go_repository(...)` pins | WORKSPACE go_repository drift | +| Instrumented Go test fails with "need a Go SDK to instrument" | `topt_go_ddtest.sdk(...)` in the root `MODULE.bazel`, or `topt_go_ddtest_workspace_repositories(...)` in WORKSPACE | Instrumented test fails: no Go SDK to instrument | +| Go test analysis fails with "would link Mini's packages twice" | `override_repo` of go_deps' `github.com/DataDog/ddtest/ddto` | Analysis fails: test would link Mini twice | +| Repository rule fails with "DDTEST_INSTRUMENT has" | the pinned ddtest release | DDTEST_INSTRUMENT contract mismatch | +| Instrumented Go test fails with "No instrumented Go SDK matches sdk_version" | an `sdk_version` transition below the test, and the SDK variants | Instrumented test fails: no SDK for an sdk_version | +| Repository rule fails with "base SDK for ... is Go ..., but its go_version is" | `go_version` or `go_mod` against the SDK in `base_sdk_roots` | Instrumented SDK fails: base SDK is not its go_version | +| Go bootstrap stops on a `rules_go` override | override that points at the removed Datadog fork | Bootstrap stops on a rules_go fork override | | WORKSPACE archive pins fail after a PR was squash-merged | generated pins commit reachability and archive SHA | Published Go pins | | Private/internal WORKSPACE fetch returns 404 | SSH git or authenticated archive access | Private repository fetch | | Bazel downloads unrelated toolchains or analyzes unrelated packages | cold monorepo state or root-package doctor/uploader placement | Monorepo analysis cost | @@ -340,17 +344,17 @@ no-op. If a cached target is reported as missing instead, verify that doctor received the BEP from the exact matching `bazel test` invocation. Use separate cache-isolation tests when validating action-key behavior. -### Disabled mode or enabled bootstrap invokes host Go +### Disabled mode or enabled run invokes host Go -Disabled manifest sync must not read the manifest, contact Datadog, or resolve -the real Orchestrion tool repository. Enabled Go bootstrap uses the -Bazel-managed Go SDK and must not depend on a host `go` binary. If a host-Go -sentinel fires, verify the patched `rules_go` profile, generated SDK wiring, -and central wrapper before installing Go on the host. +Disabled manifest sync must not read the manifest or contact Datadog. Neither +mode needs a host `go` binary: `ddtest instrument` writes the instrumented Go +SDK and Mini without it, and rules_go builds with its registered SDK. If a +host-Go sentinel fires, check which repository rule or action called `go` +before installing Go on the host. ## Published Go pins -**Symptom**: A consumer cannot fetch the published Go/Orchestrion archive, or a +**Symptom**: A consumer cannot fetch the published Go companion archive, or a pin that worked during review stops working after a squash merge. **Cause**: The consumer was given a feature-branch commit, a stale archive hash, @@ -362,8 +366,6 @@ or a tuple that was not generated from the real GitHub codeload archive. ```bash ./bazelw run //tools/dev:print_go_onboarding_pins -- \ --commit "$(git rev-parse origin/main)" \ - --rules-go-upstream v0_60_0 \ - --variant base \ --verify-main-reachable ``` @@ -372,16 +374,13 @@ or a tuple that was not generated from the real GitHub codeload archive. ```bash bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --print-published-pins \ - --rto-commit \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base + --rto-commit ``` 3. **Keep the generated tuple together**. If `RTO_COMMIT`, `RTO_ARCHIVE_URL`, `RTO_ARCHIVE_SHA256`, and `RTO_ARCHIVE_PREFIX` come from - different commits, archive mode will fail or fetch the wrong source. The - current published tuple is tracked in - [`docs/Installation_Reference.md`](Installation_Reference.md#current-v120-published-tuple). + different commits, archive mode will fail or fetch the wrong source. See + [Generate published Go pins](Installation_Reference.md#generate-published-go-pins). 4. **Authenticate private archive downloads**. The helper uses `GITHUB_TOKEN`, `GH_TOKEN`, or `gh auth token` when available. If codeload returns `404` for @@ -393,9 +392,7 @@ or a tuple that was not generated from the real GitHub codeload archive. ```bash bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --write-onboarding-summary=TEST_OPTIMIZATION_GUIDE.md \ - --rto-commit \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base + --rto-commit ``` ## Private repository fetch @@ -512,11 +509,10 @@ fails before upload. **Solutions**: -1. **Msgpack payloads**: Go/Orchestrion Bazel mode must write JSON payloads to - `TEST_UNDECLARED_OUTPUTS_DIR`. If the doctor finds `.msgpack` or - `.msgpack.gz`, verify that the target uses the Go test optimization macro or - wrapper and that the configured `dd-trace-go` version includes Bazel JSON - file-mode support. +1. **Msgpack payloads**: Go tests built by `dd_topt_go_test` write JSON + payloads to `TEST_UNDECLARED_OUTPUTS_DIR`. If the doctor finds `.msgpack` or + `.msgpack.gz`, verify that the target uses `dd_topt_go_test` or the + repository's wrapper around it. 2. **No JSON payloads**: Confirm the test target actually ran with Test Optimization enabled and inspect: @@ -888,152 +884,219 @@ full bundle. dd_topt_go_test( name = "my_test", module_label_override = "my_expected_module", # Matches :module_my_expected_module - orchestrion_mode = "test_optimization", ... ) ``` -## Go tracer version drift +## Go tests instrumented by ddtest -**Symptom**: Go bootstrap or Bazel build fails with a message showing a -configured tracer version map and a different resolved local `dd-trace-go` -module version. +`dd_topt_go_test` builds enabled tests with the Go SDK that `ddtest instrument` +instruments and links Mini, ddtest's Test Optimization runtime. The SDK and +Mini come from the ddtest release that +[`modules/go/topt_go_ddtest_repository.bzl`](../modules/go/topt_go_ddtest_repository.bzl) +pins. See [Go instrumentation SDK](Configuration_Reference.md#go-instrumentation-sdk) +for the configuration and +[ddtest's Bazel documentation](https://github.com/DataDog/ddtest/blob/main/ddto/docs/bazel.md) +for the ddtest side. To move a 1.x setup, see the +[migration note](../CHANGELOG.md#unreleased). -**Solutions**: +### Instrumented test fails: no Go SDK to instrument -1. **If you use guided bootstrap**, rerun it with the query you want: - ```bash - bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ - --dd-trace-go-version - ``` - Bootstrap accepts tags, pseudo-versions, branches, and commit SHAs. It - rewrites the workspace to the exact resolved versions that Go actually uses. - By default it uses targeted module sync, not `go mod tidy`. +**Symptom**: Building or testing a `dd_topt_go_test` target fails with: -2. **If targeted sync reports readonly module errors**, rerun bootstrap with - explicit targeted sync and the same Go SDK your repository expects: - ```bash - bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ - --go-mod-sync=targeted \ - --go-binary /path/to/go - ``` - If the workspace uses checked-in `go_repository` declarations, refresh those - declarations after `go.mod` or `go.sum` changes so Bazel and the Go module - graph agree. +```text +Go tests instrumented by dd_topt_go_test need a Go SDK to instrument: add topt_go_ddtest.sdk(go_mod = "//:go.mod") to the root MODULE.bazel, or call topt_go_ddtest_workspace_repositories(go_version = ...) in WORKSPACE +``` + +**Cause**: The root module has no `topt_go_ddtest.sdk(...)` tag. The Go +companion then registers a stand-in toolchain that only instrumented tests +select, and it fails with this message. Builds without instrumented tests are +not affected. Tags in modules other than the root module are ignored. + +**Solutions**: -3. **If you wire Orchestrion manually**, use the checked-in module as the - normal source of truth: +1. In Bzlmod, add the tag to the root `MODULE.bazel`: ```bzl - orchestrion.from_source( - dd_trace_go_pin_files = ["@//:go.mod", "@//:go.sum"], - go_sdk_root = "@test_optimization_go_sdk//:ROOT", - go_sdk_version = "", - version = "", + topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", ) + topt_go_ddtest.sdk(go_mod = "//:go.mod") # or go_version = "1.25.0" + ``` + The directory of `go_mod` must be a Bazel package. + +2. In WORKSPACE, call `topt_go_ddtest_workspace_repositories` after + `go_rules_dependencies()`. The message names `topt_go_ddtest_repositories`, + which declares the repositories but does not register the toolchains: + ```bzl + load("@datadog-rules-test-optimization-go//:topt_go_ddtest_repository.bzl", "topt_go_ddtest_workspace_repositories") + + topt_go_ddtest_workspace_repositories(go_version = "1.25.0") ``` - Export both root files from their BUILD package. Bazel resolves all - supported direct and transitive tracer modules with hermetic Go and - `-mod=readonly`; it does not update `go.sum`. + Without this call, WORKSPACE has neither the instrumented toolchains nor + the `@topt_go_ddtest_mini` repository, so analysis of an instrumented test + fails on the missing repository instead of this message. See + [Configure Go support in WORKSPACE](Installation_Reference.md#8-configure-go-support-in-workspace-with-the-public-helper). + +### Analysis fails: test would link Mini twice + +**Symptom**: Analysis of a `dd_topt_go_test` target fails with: + +```text +//pkg:my_test would link Mini's packages twice: come from outside @topt_go_ddtest_mini. rules_go links one copy of each package without an error, so the test could run without Test Optimization. ... +``` -4. **If pin-file resolution reports an absent module, unsupported replacement, - or read-only failure**, fix the module with its normal dependency workflow. - If that graph intentionally cannot use pin-file mode, configure the exact - `dd_trace_go_versions` map as an escape hatch. Do not combine pin files with - either explicit version field. +**Cause**: A package of `github.com/DataDog/ddtest/ddto` reaches the test +through `deps` or `embed` from a repository other than the generated Mini, +usually go_deps' copy when the workspace's `go.mod` requires that module to use +Mini's Go API in tests. The macro also adds the generated Mini, and rules_go +would silently link only one of the two copies. -5. **If you omitted every selection mode**, remember the legacy default is - `v2.9.1`. +**Solution**: In the root `MODULE.bazel`, replace go_deps' copy with the +generated Mini. If the file already has a `topt_go_ddtest` extension proxy for +the `sdk` tag, add the last two lines next to it: -The build fails on purpose here. It is preventing Bazel from injecting one -set of tracer versions while the local Go module still resolves another. +```bzl +topt_go_ddtest = use_extension("@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", "topt_go_ddtest") +use_repo(topt_go_ddtest, "topt_go_ddtest_mini") +override_repo(go_deps, com_github_datadog_ddtest_ddto = "topt_go_ddtest_mini") +``` -`orchestrion.tool.go` still matters, but as required import/config wiring for -Orchestrion, not as the source of truth for tracer versions. +After the override, a test whose `deps` name `testopt` keeps that dependency; +the macro does not add it a second time. When `deps` is a `select()`, which +the macro cannot inspect, it adds Mini through a private alias, so the same +archive can appear under both labels. In WORKSPACE, depend on `@topt_go_ddtest_mini` labels rather than on another +repository that provides the same module. -## Enabled Orchestrion cannot find Go +### DDTEST_INSTRUMENT contract mismatch -**Symptom**: +**Symptom**: Fetching `@topt_go_ddtest_mini` or an instrumented SDK fails with +a message such as: ```text -Could not find 'go' binary. Please ensure Go is installed. +Mini's repository: DDTEST_INSTRUMENT has version="", want "v0.0.0-pre-release-f5df4db" (ddtest pre-release-f5df4db) ``` -**Cause**: the workspace uses Orchestrion wiring generated before the -Bazel-managed SDK contract was added, or a manual block omitted the SDK root -and version. Disabled mode still avoids the real repository, but enabled mode -must compile the patched Orchestrion binary on a cold cache miss. +For an SDK, the message starts with `the instrumented SDK for `. -**Solution**: +**Cause**: After `ddtest instrument bazel` writes Mini or an SDK, the companion +reads the `DDTEST_INSTRUMENT` file that ddtest wrote and compares its +`contract`, `sdk_name`, and `version` with `DDTEST_RELEASE` in +[`modules/go/topt_go_ddtest_repository.bzl`](../modules/go/topt_go_ddtest_repository.bzl). +The downloaded executables are checked against the pinned SHA256 values, so +a mismatch usually means that `DDTEST_RELEASE` itself is inconsistent: its tag +and checksums name one ddtest release, and its `runtime_version`, `contract`, +or `sdk_name` another. -1. For Bzlmod, rerun guided bootstrap with the workspace's real Go toolchain - version: +**Solution**: Use a released version of the Go companion without local edits +to `DDTEST_RELEASE`. When you change the pin, take the tag, the runtime +version, the contract, the SDK name, and the six checksums from the same ddtest +release. A different `contract` means the companion's rules and that ddtest +release do not agree on what ddtest writes; it needs a companion change, not a +different expected value. + +### Instrumented test writes no payloads + +**Symptom**: A Go test passes, but `bazel-testlogs//test.outputs/` +has no payloads or no `bazel_target_metadata.json`. + +**Causes and solutions**: + +1. **The export is disabled.** Without `--config=test-optimization`, or when + the sync repository is disabled, `dd_topt_go_test` creates a plain + `go_test` under the same name. Check the rule kind with the config you run + tests with: ```bash - bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ - --guided \ - --service \ - --runtime-version \ - --write-bazelrc + bazel query --config=test-optimization --output=label_kind //pkg:my_test ``` -2. For WORKSPACE, regenerate the reviewed snippet with - `--workspace-mode --print-workspace-snippet --runtime-version `. -3. Verify the generated SDK version equals the version passed to the - repository's central `go_register_toolchains(...)` call and the Test - Optimization sync repository. + An enabled test prints `ddtest_go_test rule`; `go_test rule` means the + export is disabled. -Do not install Go on the analysis host as the fix. The supported wiring uses -the Bazel-managed SDK on a cache miss and can restore a compatible warm -Orchestrion bootstrap before materializing that SDK. +2. **The hidden test ran instead of the public one.** Only the public + `` target builds `__raw_go_test` with the instrumented Go SDK + and copies `bazel_target_metadata.json`. Run ``. -## WORKSPACE go_repository drift +3. **`ci_visibility_enabled = False`.** The macro then does not set + `DD_CIVISIBILITY_ENABLED=true`. Set it in the test's `env`, or remove the + attribute. -**Symptom**: `go.mod` and `go.sum` look correct, but Bazel still resolves an -older `github.com/DataDog/orchestrion` or `dd-trace-go` module from a checked-in -`repositories.bzl` file. +4. **The result was cached.** A `(cached)` result reuses the outputs of an + earlier run, and BEP freshness skips them. Rerun the target with + `--nocache_test_results` when you need new payloads from it. -**Cause**: WORKSPACE repositories often keep generated `go_repository(...)` -declarations separate from `go.mod`. Updating the Go module graph is not enough -if the generated repository file still pins old versions. +5. **The outputs stayed remote.** With remote execution or a remote cache, + follow item 4 of + [Uploader not finding payloads](#uploader-not-finding-payloads). -**Solutions**: +### Instrumented test fails: no SDK for an sdk_version -1. **Run bootstrap diagnostics after targeted sync**: - ```bash - bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ - --workspace-mode \ - --write-orchestrion-files \ - --go-mod-sync=targeted \ - --check-go-repositories \ - --go-repositories-file repositories.bzl - ``` - The checker compares only the modules bootstrap owns: - `github.com/DataDog/orchestrion`, `github.com/DataDog/dd-trace-go/v2`, - `github.com/DataDog/dd-trace-go/contrib/net/http/v2`, and - `github.com/DataDog/dd-trace-go/contrib/log/slog/v2`. +**Symptom**: Building or testing a `dd_topt_go_test` target fails with: -2. **Let your repo-owned refresh command repair the file**: - ```bash - bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ - --workspace-mode \ - --write-orchestrion-files \ - --go-mod-sync=targeted \ - --check-go-repositories \ - --go-repositories-file repositories.bzl \ - --go-repositories-refresh-command './tools/update-go-repositories.sh' - ``` - Bootstrap runs the refresh command only after targeted sync succeeds, then - validates `repositories.bzl` again. It never edits `repositories.bzl` - directly. +```text +No instrumented Go SDK matches sdk_version "". Configured: . Add an SDK variant with that sdk_version: topt_go_ddtest.sdk(sdk_version = ...) in MODULE.bazel, or sdk_variants in topt_go_ddtest_workspace_repositories(...) in WORKSPACE +``` -3. **Print expected versions for manual updates**: - ```bash - bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ - --workspace-mode \ - --go-mod-sync=off \ - --check-go-repositories \ - --print-go-repository-updates - ``` - This is useful when the repository has a custom Gazelle/update-repos flow - that must be run manually or in a separate review step. +**Cause**: The public test's transition sets +`@io_bazel_rules_go//go/toolchain:sdk_name` to `ddto`, so every Go target in +its configuration, exec tools included, builds with an instrumented SDK. A +transition below the test, such as `go_cross_binary(sdk_version = ...)` for a +FIPS binary in `data`, asked for an `sdk_version` that no instrumented SDK +serves: the default SDK serves an unset value and the values that rules_go +matches for its `go_version`, and each SDK variant its own `sdk_version`. + +**Solution**: Add an SDK variant with that `sdk_version`, built from the same +Go release and `experiments`, or from the SDK the workspace already declares +through `base_sdk_roots`; see +[Go instrumentation SDK](Configuration_Reference.md#go-instrumentation-sdk). +Targets outside instrumented tests are not affected. + +When an SDK serves the `sdk_version` but not the build's exec platform, Bazel +reports its own `No matching toolchains found` error for +`@io_bazel_rules_go//go:toolchain`, as it does when no exec platform has an +instrumented SDK. Add the exec platform to that SDK's `exec_platforms`. Target +platforms are ddtest's six (`linux`, `darwin` and `windows` on `amd64` and +`arm64`) for every SDK. + +### Instrumented SDK fails: base SDK is not its go_version + +**Symptom**: Fetching an instrumented SDK fails with: + +```text +topt_go_ddtest: the default SDK's base SDK for linux_amd64 is Go 1.25.9, but its go_version is 1.26.3; set go_version to the base SDK's version, or point base_sdk_roots at a Go 1.26.3 SDK +``` + +**Cause**: The SDK's `go_version`, set directly or read from `go_mod`, says +which `sdk_version` requests it serves, but the SDK that `base_sdk_roots` names +for that exec platform has another version in its `VERSION` file. A `go.mod` +whose `go` directive moved while the base SDK stayed behind is a common case. + +**Solution**: Make them agree: set `go_version` (or the `go.mod` `toolchain` +directive) to the base SDK's version, or point `base_sdk_roots` at an SDK of +that version. Without `go_version`, the base SDK's version is not checked and +the default SDK serves only an unset `sdk_version`. + +### Bootstrap stops on a rules_go fork override + +**Symptom**: `dd_topt_go_bootstrap` stops with: + +```text +MODULE.bazel has a git_override of rules_go with the removed Datadog rules_go fork (strip_prefix = ); remove it so that rules_go comes from the Bazel Central Registry, then rerun the bootstrap +``` + +The same message names `archive_override` or `local_path_override` (with +`path`) for those override kinds. + +**Cause**: `MODULE.bazel` overrides `rules_go` with the Datadog fork that 1.x +used, outside the Go bootstrap block that 1.x wrote. Bootstrap replaces that +block itself, but leaves other overrides to you. + +**Solution**: Delete the override so that `bazel_dep(name = "rules_go", ...)` +resolves from the Bazel Central Registry (0.60.0 or newer), then rerun the +bootstrap. If the workspace cannot load because the fork commit in the old block +is no longer fetchable, run the bootstrap from another Bazel workspace that +depends on the Go companion and pass `--workspace /path/to/your/workspace`, or +delete the old block by hand and rerun. A `# BEGIN Datadog Go Pin Files` block +that 1.x left in a `BUILD` file is not used anymore; you can delete it. ## Large monorepo validation is easy to run incorrectly diff --git a/docs/Uploader_Reference.md b/docs/Uploader_Reference.md index 4f2af01e..6e59ec82 100644 --- a/docs/Uploader_Reference.md +++ b/docs/Uploader_Reference.md @@ -18,8 +18,10 @@ small repositories. 1. Tests write payloads to `$TEST_UNDECLARED_OUTPUTS_DIR/payloads/tests/*.json` and - `$TEST_UNDECLARED_OUTPUTS_DIR/payloads/coverage/*.json`; tracers may also - write `$TEST_UNDECLARED_OUTPUTS_DIR/payloads/telemetry/*.json` + `$TEST_UNDECLARED_OUTPUTS_DIR/payloads/coverage/*.json`; test runtimes may + also write `$TEST_UNDECLARED_OUTPUTS_DIR/payloads/telemetry/*.json`. For Go, + the runtime is Mini, ddtest's Test Optimization runtime, which + `dd_topt_go_test` links into the test 2. Bazel automatically collects these to `bazel-testlogs///test.outputs/` 3. After tests complete, run the doctor via `bazel run` to validate local @@ -49,14 +51,14 @@ same config: common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 ``` -Optimized Go targets enable Orchestrion through their own transition. Removing -`--config=test-optimization` disables metadata resolution; no global -Orchestrion setting or second user-facing bool is required. +Instrumented Go tests select the Go SDK that `ddtest instrument` instruments +through their own transition. Removing `--config=test-optimization` disables +metadata resolution; no other build setting or second user-facing bool is +required. -Config-gated Python consumers use only the -`DD_TEST_OPTIMIZATION_ENABLED=1` entry and omit the Go-specific Orchestrion -line. Java, NodeJS, .NET, and Ruby retain their existing enablement contract in -this release. +Config-gated Python consumers use the same +`DD_TEST_OPTIMIZATION_ENABLED=1` entry. Java, NodeJS, .NET, and Ruby retain +their existing enablement contract in this release. ```bash # Vendor the full tools/test_optimization/ helper directory, or set @@ -136,15 +138,15 @@ Enrichment validation: - Default required enriched tags are `git.repository_url`, `git.commit.sha`, `bazel.target`, and `bazel.package`. Use repeatable `--expected-enriched-tag=` flags to validate additional runtime-specific - tags such as `bazel.go.payload_selection` during Go/Orchestrion rollouts. + tags such as `bazel.go.payload_selection` during Go rollouts. Credential handling: - Pass `DD_API_KEY`, `DD_SITE`, and `DD_TEST_OPTIMIZATION_AGENT_URL` at uploader runtime via environment variables only. - Do not pass `DD_TEST_OPTIMIZATION_AGENT_URL`, - `DD_TEST_OPTIMIZATION_AGENTLESS_URL`, or `DD_GIT_*` to Go/Orchestrion tests - with `--test_env`. + `DD_TEST_OPTIMIZATION_AGENTLESS_URL`, or `DD_GIT_*` to Go tests with + `--test_env`. - Do not hardcode secrets in `BUILD.bazel`, scripts committed to git, or CI logs. - The generated uploader scripts read env vars directly (no shell `eval` @@ -825,7 +827,7 @@ result. - Bazel metadata is included as stable tags: `bazel.rule_name`, `bazel.rule_version`, `bazel.os`, and `bazel.arch`. - When enrichment is active, those Bazel keys are merged into each test event - alongside the existing CI, Git, OS, and runtime tags. This includes Go tracer + alongside the existing CI, Git, OS, and runtime tags. This includes Go payloads that encode CI Visibility data as `span` events; those events still need the same Git and Bazel tags before upload. - CODEOWNERS enrichment remains limited to non-span CI Visibility lifecycle and diff --git a/docs/go_orchestrion_bazel_deep_dive.md b/docs/go_orchestrion_bazel_deep_dive.md deleted file mode 100644 index 1251f1a8..00000000 --- a/docs/go_orchestrion_bazel_deep_dive.md +++ /dev/null @@ -1,910 +0,0 @@ - - -# Go, Orchestrion, and Bazel deep dive - -> Scope note: consumers choose `third_party/rgo/v0_60_0/base` or a -> versioned base tree. Maintainer-only local regression fixtures live in -> `tools/tests/rules_go_variant_regressions`. - -## Purpose - -This document explains the Go instrumentation path as it exists today in this -repository. - -It focuses on the current steady-state design: - -- how `dd_topt_go_test` is exposed to users -- how bootstrap wires a workspace for Orchestrion -- how the vendored `rules_go` fork invokes Orchestrion under Bazel -- how stdlib weaving, synthetic `testmain`, and final link stay consistent -- which invariants matter if you need to maintain or extend this path - -If you need the current optimization status, the experiments that were kept or -reverted, or the latest measured bottlenecks, start with -[go_orchestrion_maintainer_state.md](./go_orchestrion_maintainer_state.md) and -then return here for the lower-level architectural detail. - -This is a maintainer document about the current system, not its debugging -history. - -## Scope - -The Go integration spans four layers: - -1. User-facing macro and analysis-time selection -2. Bootstrap and workspace wiring -3. Vendored `rules_go` fork with Orchestrion support -4. Datadog Orchestrion itself, built from patched source - -Primary implementation entry points follow the default upstream path -`third_party/rgo/v0_60_0/base`. For another supported upstream, resolve the -tree with: - -```bash -python3 tools/dev/materialize_rules_go_fork.py resolve --upstream --variant base -``` - -Then use the same relative files under that tree. - -- [modules/go/topt_go_test.bzl](../modules/go/topt_go_test.bzl) -- [modules/go/topt_go_orchestrion.bzl](../modules/go/topt_go_orchestrion.bzl) -- [modules/go/tools/dd_topt_go_bootstrap/main.go](../modules/go/tools/dd_topt_go_bootstrap/main.go) -- [third_party/rgo/v0_60_0/base/go/private/orchestrion/extensions.bzl](../third_party/rgo/v0_60_0/base/go/private/orchestrion/extensions.bzl) -- [third_party/rgo/v0_60_0/base/go/private/actions/archive.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/archive.bzl) -- [third_party/rgo/v0_60_0/base/go/private/actions/compilepkg.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/compilepkg.bzl) -- [third_party/rgo/v0_60_0/base/go/private/actions/link.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/link.bzl) -- [third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go) -- [third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go) -- [third_party/rgo/v0_60_0/base/go/tools/builders/link.go](../third_party/rgo/v0_60_0/base/go/tools/builders/link.go) -- [third_party/rgo/v0_60_0/base/go/tools/builders/orchestrion.go](../third_party/rgo/v0_60_0/base/go/tools/builders/orchestrion.go) - -## Mental model - -The system is easiest to understand as three concentric layers: - -- Bazel remains the outer build and test system. -- `rules_go` remains the Go rule implementation, but we vendor a fork that knows - how to invoke Orchestrion coherently. -- Orchestrion remains the inner compile-time instrumentation engine. - -The user still writes a Bazel-native target: - -```bzl -dd_topt_go_test( - name = "pkg_test", - srcs = ["*_test.go"], - orchestrion_mode = "test_optimization", - topt_data = topt_data, -) -``` - -When Test Optimization is enabled, the raw `go_test` is built under a -transition that enables the requested Orchestrion mode in the vendored -toolchain. When the selected sync export is disabled, the macro instead creates -only the caller's public raw `go_test`. - -Orchestrion is not an external post-processing step. Bazel, the vendored -`rules_go`, and Orchestrion form one compile pipeline. - -## High-level architecture - -```mermaid -flowchart TD - A[MODULE.bazel] --> B[Go companion extension] - A --> C[dd_topt_go_bootstrap] - C --> D[Bzlmod MODULE.bazel patching or WORKSPACE local scaffolding] - C --> E[orchestrion pin files] - D --> F[vendored rules_go fork] - D --> W[Bazel-managed Go SDK] - F --> G[rules_go Orchestrion extension] - G --> U{Test Optimization enabled?} - U -->|no| V[stable empty repository\n no host Go or source fetch] - U -->|yes| H[patched Orchestrion binary] - W --> H - - I[BUILD: dd_topt_go_test] --> R{Sync export enabled?} - R -->|no| S[public raw go_test\n no Test Optimization targets] - R -->|yes| J[hidden raw go_test] - R -->|yes| K[public orch_go_test wrapper] - K --> L[function transition] - L --> J - - J --> M[compilepkg / stdlib / link actions] - M --> N[Orchestrion toolexec path] - N --> O[woven stdlib + package compile + synthetic testmain] - O --> P[final Bazel test binary] - P --> Q[CI Visibility runtime + payloads] -``` - -## Current user-facing flow - -The public API hides most of the toolchain mechanics described later. - -### 1. Module setup - -The consumer adds: - -- the core module -- the Go companion module -- `rules_go` -- the Go companion extension repo - -In the guided Go path, bootstrap writes `test_optimization_go_extension`, which -materializes the metadata repo used by `dd_topt_go_test`. - -- In guided single-service setup, bootstrap writes the managed - `test_optimization_go_extension` block and `use_repo(...)` call. -- In manual single-service or multi-service setup, the Go macro only requires a - compatible exported `topt_data` shape. That can come from the Go extension or - from the core sync and multi-sync extensions. -- In a consumer-managed Go/Python monorepo, the separate manifest-sync - aggregate exports `topt_data_by_target`; the central Go wrapper passes the - matching entry to the same macro. Target discovery and service naming happen - before repository resolution in the consumer command, not in Orchestrion or - `rules_go`. - -The generated metadata repo then provides the per-service and per-module -payload labels consumed by `dd_topt_go_test`. - -Analysis-time payload selection depends on repository-level metadata. Module -setup creates the repos and exports the macro needs later. - -### 2. Bootstrap - -The bootstrap binary is the one-time workspace mutation step. - -Implementation: -- [main.go](../modules/go/tools/dd_topt_go_bootstrap/main.go) - -In guided Bzlmod mode, bootstrap does six things that matter for the current -architecture: - -1. Ensures `MODULE.bazel` contains `bazel_dep(name = "rules_go", version = "0.60.0")` -2. Writes a managed `git_override` for `rules_go` pointing back to this repo - with `strip_prefix = "third_party/rgo/v0_60_0/base"` -3. Declares a Bazel-managed Go SDK from `--runtime-version` -4. Enables the `@rules_go//go:extensions.bzl` Orchestrion extension, passes the - SDK root and exact version, and imports - `use_repo(orchestrion, "rules_go_orchestrion_tool")` -5. Sets the workspace-wide tracer selection with either - `orchestrion.from_source(..., dd_trace_go_version = "...")` or - `orchestrion.from_source(..., dd_trace_go_versions = {...})` -6. Runs `orchestrion pin` in the Go module and ensures: - - `go.mod` - - `go.sum` - - `orchestrion.tool.go` - - `orchestrion.yml` - -Bootstrap does more than install a tool. It aligns: - -- Bazel module wiring -- the vendored `rules_go` fork -- the Bazel-managed Go SDK used to build Orchestrion -- the selected `dd-trace-go` version used by Bazel injection -- the pinned Go module files that Orchestrion expects - -In WORKSPACE mode, bootstrap deliberately does not edit `WORKSPACE`. It prints -repository wiring for review and can write only repository-local scaffolding: -the managed `.bazelrc` block, root doctor/uploader targets, Orchestrion pin -files, wrapper template, and validation script. Large WORKSPACE monorepos keep -their repository placement and local wrapper policy under owner review while -still using the same Orchestrion-enabled `rules_go` variants. - -If no tracer setting is present, the default is still -`v2.9.1`. Bootstrap keeps the local Go module -on the same effective versions, and the Bazel build now fails fast if the -workspace setting and the local Go module pins drift apart. - -The downloaded Orchestrion tool source is still patched before Bazel builds the -binary, but Bazel now builds that tool from Orchestrion's upstream module graph -instead of rewriting the tool repo's own `go.mod`. The selected tracer version -is enforced later against the target module through the emitted -`dd_trace_go_versions.json` file and the builder-side validation path. - -Manual consumer wiring normally replaces bootstrap's explicit tracer selection -with `dd_trace_go_pin_files = ["@//:go.mod", "@//:go.sum"]`. The repository rule -uses the Bazel-managed Go SDK to resolve every supported direct or transitive -module with `-mod=readonly`, emits the same canonical -`dd_trace_go_versions.json`, and keys the bootstrap cache by that resolved map. -Explicit shared and per-module selections remain compatibility escape hatches. - -Bootstrap centralizes the one-time mutations needed to make Bazel and -Orchestrion agree on the workspace shape. Without that central step, each test -target would need to carry fragile setup knowledge. - -### 3. Test macro expansion - -`dd_topt_go_test` is the public macro surface. - -Implementation: -- [topt_go_test.bzl](../modules/go/topt_go_test.bzl) - -With an enabled sync export, the macro does three distinct jobs: - -1. Select Datadog payload data -2. Prepare runtime env/data wiring for Test Optimization -3. Route the public test through the Orchestrion wrapper transition - -The enabled macro expands into: - -- a hidden raw `go_test` -- a public `orch_go_test` wrapper - -With a disabled export, the macro validates its macro-only inputs and selected -service, then creates only the caller's public raw `go_test`. It does not create -the hidden test, payload selector, target metadata, Orchestrion pin, or wrapper -targets. The public label is therefore stable across modes, but the rule class -is not: it is `go_test` while disabled and `orch_go_test` while enabled. - -The wrapper forwards `orchestrion_mode` into the vendored `rules_go` fork. The -default mode is `general`, which preserves broad generic Orchestrion behavior. -For standard Go `testing`, use `orchestrion_mode = "test_optimization"`. That -mode keeps stdlib `testing`, synthetic `testmain`, helper packagefile, importcfg, -and link support coherent while ordinary customer packages and external `_test` -packages compile on the plain rules_go path. Nested packages still need -package-local pin files or explicit `orchestrion_pin_files`, and callers can -disable the test-binary linker flag optimization with -`enable_test_binary_linker_optimization = False`. - -When present in the current Bazel package, the raw `go_test` stages the local -Orchestrion pin files as hidden data: - -- `go.mod` -- `go.sum` -- `orchestrion.tool.go` -- `orchestrion.yml` - -The macro discovers them with a package-local glob, so this is automatic for -packages that contain those files, but it is not a cross-workspace guarantee -for arbitrary subpackages. - -That keeps typical module-root packages simple without implying that every Go -test target in every package automatically receives those files. - -The macro is the policy boundary. It keeps user BUILD files simple while -combining payload selection, runtime wiring, and Orchestrion-enabled compilation -in one place. - -### 4. Transitioned wrapper rule - -Implementation: -- [topt_go_orchestrion.bzl](../modules/go/topt_go_orchestrion.bzl) - -In the enabled path, the wrapper rule applies a function transition that sets: - -```bzl -"@rules_go//go/private/orchestrion:enabled": True, -"@rules_go//go/private/orchestrion:mode": "general" or "test_optimization", -``` - -Enablement therefore follows the optimized target edge instead of a global -build setting. Ordinary tests in a mixed invocation remain untransitioned. - -The wrapper then symlinks the executable produced by the raw target and returns -the same runfiles. - -This preserves normal Bazel test ergonomics while moving the actual build onto -an Orchestrion-enabled configuration. - -The transition wrapper lets the raw test build under a different configuration -without changing the public target label that users and CI invoke. Callers that -filter tests by rule language must account for the mode-dependent rule class: -`go` for the disabled raw target and `orch_go` for the enabled wrapper. - -## Why the vendored `rules_go` fork exists - -Orchestrion is fundamentally a `toolexec`-style integration. In a plain Go -workflow, the intended shape is conceptually: - -```text -go test -toolexec="orchestrion toolexec" -``` - -`rules_go` does not expose a single public seam that means "attach this -toolexec everywhere and preserve normal Go behavior". Under Bazel, the Go -pipeline is decomposed into builder actions: - -- stdlib list and stdlib build -- package compilation -- synthetic `testmain` generation and compile -- final link - -For Orchestrion to behave correctly, all of those steps must agree on: - -- module context -- GOROOT and SDK layout -- importcfg contents -- Datadog helper packagefiles -- stdlib export family -- synthetic test binary link inputs - -The repository therefore vendors a `rules_go` fork instead of attaching -Orchestrion as an external wrapper. - -## Current toolchain topology - -```mermaid -flowchart LR - A[dd_topt_go_test] --> B[raw go_test] - B --> C[vendored rules_go actions] - C --> D[patched Orchestrion binary] - D --> E[go compile] - D --> F[go link] - C --> G[importcfg rewriting] - C --> H[stdlib cache/export handling] - C --> I[synthetic testmain manifest + helper tree] - I --> F -``` - -## What the vendored `rules_go` fork owns - -The vendored fork owns the Orchestrion integration at the action and builder -layer. - -The fork is not just a copy of upstream `rules_go`; it is the layer that makes -the Bazel pipeline and Orchestrion pipeline behave coherently. - -### Starlark action layer - -- [archive.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/archive.bzl) -- [compilepkg.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/compilepkg.bzl) -- [link.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/link.bzl) - -Responsibilities: - -- declare the outputs needed by Orchestrion-aware compile and link steps -- pass the Orchestrion binary into builders -- pass the synthetic `testmain` manifest and declared helper archive tree - through analysis and execution - -Starlark is where Bazel decides which files and arguments are real. If the -manifest, its helper archive tree, or the Orchestrion tool are not declared -here, a later sandbox or remote worker cannot use them. - -### Builder layer - -- [compilepkg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go) -- [importcfg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go) -- [link.go](../third_party/rgo/v0_60_0/base/go/tools/builders/link.go) -- [orchestrion.go](../third_party/rgo/v0_60_0/base/go/tools/builders/orchestrion.go) -- [stdlib.go](../third_party/rgo/v0_60_0/base/go/tools/builders/stdlib.go) -- [stdliblist.go](../third_party/rgo/v0_60_0/base/go/tools/builders/stdliblist.go) - -Responsibilities: - -- prepare a safe Go module environment for Orchestrion -- normalize GOROOT and SDK behavior in Bazel sandboxes -- rewrite importcfg so woven stdlib and Datadog helper packages are resolvable -- persist synthetic `testmain` helper selections across compile and link -- ensure final link reuses the same helper export family chosen during compile - -Execution-time coherence cannot be solved in analysis alone. The builder layer -is where the environment, importcfg, and archive family are made consistent. - -## Patched Orchestrion source - -The vendored `rules_go` fork does not use upstream Orchestrion unchanged. - -Implementation: -- [extensions.bzl](../third_party/rgo/v0_60_0/base/go/private/orchestrion/extensions.bzl) - -The Orchestrion repository rule downloads Orchestrion source and patches it -before building the binary. The patches fall into a few categories: - -### Resolver and tempdir compatibility - -Orchestrion sometimes shells out to `go list` while resolving injectors and -package files. Under Bazel, that has to work in a synthetic module layout and -inside sandboxes. - -The patches make dependency resolution less recursive and more Bazel-friendly, -and they create compatibility symlinks in temporary work directories where -needed. - -Upstream Orchestrion expects a more conventional Go execution environment than -Bazel provides. These patches adapt resolver behavior to Bazel sandboxes and -synthetic work directories. - -### Compile proxy and archive metadata support - -The compile proxy is patched so it can correctly understand linker-object -outputs and cooperate with the synthetic `testmain` flow. - -The synthetic `testmain` path needs metadata to survive across Bazel's separate -compile and link stages. The compile proxy has to preserve that information. - -### Resolver-context patches - -The `oncompile`, `oncompile-main`, and `onlink` hooks are patched so dependency -lookups run with the Bazel-specific import-path context they actually need, not -only with upstream assumptions about a normal Go tool invocation. - -Under Bazel, package identity can differ from what upstream Orchestrion would -infer from a normal `go` command. These patches keep lookup context aligned with -the package Bazel is actually compiling. - -### Injector prefilter and stdlib lookup behavior - -Some patches keep Orchestrion from dropping required aspects too early or -failing to discover stdlib archives when Bazel's importcfg layout differs from -what upstream Orchestrion normally sees. - -If Orchestrion rejects a package too early or cannot locate stdlib archives, -later steps cannot recover. These patches keep the instrumentation path open. - -The result is still "Orchestrion from source", but it is a Bazel-adapted build -of Orchestrion, not a stock upstream binary. - -The Orchestrion binary is part of the supported integration surface. The design -depends on this Bazel-adapted build as well as the vendored `rules_go` changes. - -## Compile path - -Compile turns the configured instrumentation into build artifacts. - -### Package compile - -The compile entry point is: - -- [compilepkg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go) - -At compile time, the builder: - -1. Parses the normal `rules_go` compile arguments -2. Creates a work directory -3. Prepares a Go module/cache environment for Orchestrion -4. Filters sources and applies cgo/coverage/nogo handling as usual -5. Invokes the Go compiler through Orchestrion when enabled - -In `test_optimization` mode, package compile is deliberately narrower: -customer packages and external `_test` packages do not receive Orchestrion -toolexec. The synthetic `testmain` compile still receives Orchestrion context -so it can produce the Datadog helper packagefile manifest consumed by final -link. - -The key Orchestrion-specific compile responsibilities are: - -- module/cache preparation -- importcfg rewriting -- synthetic `testmain` helper capture - -Package compile is the first place where wrong module context, wrong importcfg, -or wrong stdlib selection can poison everything downstream. - -### Synthetic `testmain` - -`rules_go` synthesizes a `testmain.go` package for Go tests. That synthetic -package is special because it is the point where Datadog CI Visibility hooks -around `testing` become visible in the final test binary. - -For synthetic `testmain`, compilepkg generates two declared outputs: - -- logical manifest name inside the workdir: `orchestrion.pack` -- declared Bazel sidecar output: `.a.orchestrion.pack` -- declared Bazel helper archive tree: `.a.orchestrion.helpers` - -Implementation details: - -- [archive.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/archive.bzl) -- [compilepkg.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/compilepkg.bzl) -- [compilepkg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go) - -The sidecar records the compile-time `packagefile` directives for the Datadog -helper packages that synthetic `testmain` was rooted against. Archives created -outside the execroot are copied into the declared helper tree, and the sidecar -is rewritten to point there. This makes the compile-to-link contract portable -across Bazel sandboxes and remote workers. - -`testing` instrumentation enters through synthetic `testmain`. The sidecar -exists so final link can reuse the exact helper package family chosen during -compile instead of reconstructing a different one. - -## Importcfg management - -The importcfg layer is where the Bazel/Orchestrion integration becomes most -concrete. - -Implementation: -- [importcfg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go) - -This file owns several distinct jobs: - -Importcfg is the explicit statement of what the toolchain can import. This -section explains how the integration shapes that package universe. - -### 1. Stdlib archive resolution - -Orchestrion may need the woven stdlib archive family, not just the default one. -The builder can source stdlib packagefiles from: - -- normal Bazel stdlib archives -- seeded stdlib cache exports -- persisted stdlib export manifests - -Orchestrion can weave stdlib packages, so compile and link need to resolve the -woven stdlib archive family rather than silently falling back to the default -unwoven one. - -### 2. Datadog helper package resolution - -The Datadog helper packages used for CI Visibility and tracing must exist as -real `packagefile` entries in the importcfg seen by compile and link. - -Injected CI Visibility hooks become ordinary package dependencies after weaving. -If their exports are missing from importcfg, the final binary cannot preserve -the instrumentation path. - -### 3. Importcfg rewriting - -The builder rewrites existing `packagefile` directives and appends missing ones -so the toolchain sees a coherent package universe. - -Bazel's default importcfg contents do not fully describe the instrumented build -graph. Rewriting is how the builders present the package set Orchestrion -actually needs. - -### 4. Helper-root reuse - -The final link must not invent a different Datadog helper export family than -the one synthetic `testmain` compile already used. The importcfg layer is where -that reuse is made explicit. - -Compile and link are separate actions. Helper-root reuse is the mechanism that -keeps them inside the same Datadog package universe. - -## Link path - -The link entry point is: - -- [link.go](../third_party/rgo/v0_60_0/base/go/tools/builders/link.go) - -The link builder has two broad paths: - -- normal Orchestrion-enabled link -- synthetic test binary link - -In `test_optimization` mode, the final synthetic test binary link does not run -with `-orchestrion`. It consumes the synthetic `testmain` manifest and helper -packagefiles produced earlier, then keeps the link action free of unused -Orchestrion proxy and pin-file inputs. Generic mode can still use the broader -Orchestrion link behavior. - -Link is the last point where separate compile outputs can converge into a -coherent binary or drift apart. - -### Normal link - -When linking a normal main package, link can run through Orchestrion directly -and can append the broader Datadog helper closure it needs for the final binary. - -Normal link establishes the baseline behavior: link may complete the helper -closure, but it must still stay inside one consistent export family. - -### Synthetic test binary link - -When linking the Bazel synthetic test binary, the path is more constrained. - -`link.go` detects a synthetic test link from the main archive name: - -- `~testmain.a` - -For synthetic test links it: - -1. Reads the synthetic `testmain` sidecar manifest -2. Receives the manifest's helper archive tree as a declared Bazel input -3. Applies those `packagefile` directives into the link importcfg -4. Reuses the compile-time Datadog helper root -5. Completes the broader Datadog closure from that same root when link itself is - still Orchestrion-enabled - -In `test_optimization` mode, customer package compiles remain plain and the -helper closure is reduced around the standard Go `testing` path. The mode still -lets the woven stdlib cover standard packages such as `net/http` and `log/slog`, -but Datadog contrib HTTP/slog helpers and profiler roots stay out of the -synthetic `testmain` helper/link closure. Automatic `testify/suite` support is -outside this mode. - -That is the core consistency rule of the current implementation: - -> synthetic `testmain` compile and final link must operate on the same Datadog -> helper export family - -Without that, compile may instrument `testing`, but the final linked test binary -may not preserve those hooks coherently. - -Bazel's synthetic test wrapper and Datadog's `testing` instrumentation meet on -this link path. The sidecar manifest makes that join deterministic. - -## Orchestrion runtime environment inside builders - -Implementation: -- [orchestrion.go](../third_party/rgo/v0_60_0/base/go/tools/builders/orchestrion.go) - -This file prepares the environment Orchestrion needs inside Bazel actions, -which do not run in a normal developer shell. - -### Go cache and module cache provisioning - -The builder ensures writable: - -- `GOPATH` -- `GOMODCACHE` -- `GOCACHE` - -That matters because Orchestrion shells out to Go tooling while resolving -injectors and package files. - -Without writable caches and module storage, Orchestrion's internal `go` calls -fail inside Bazel sandboxes even when the outer action is otherwise correct. - -### Cache ownership - -The repository bootstrap and sandboxed builders have different cache needs: - -- ordinary host-side `go build` and `go list` commands resolve Go's configured - or platform-default `GOCACHE` and `GOMODCACHE` and reuse each writable cache; - a read-only cache falls back to the repository-local - `.orchestrion_bootstrap_go_cache` tree instead of failing bootstrap; the - path probe itself sets `GOTOOLCHAIN=local` so it cannot start an automatic - toolchain download before that fallback is selected, and an unsupported or - failed cache query falls back both cache paths to that local tree -- the persistent host `datadog-orchestrion-go-cache` selected by the repository - bootstrap stores only reusable Test Optimization artifacts: the patched - Orchestrion binary, its exact version metadata, and the offline module proxy -- builder subprocesses keep writable action-specific Go caches because Bazel - sandboxes cannot safely depend on a developer's host cache and must resolve - instrumented dependencies through the declared offline proxy; those - transient roots may use the same namespace but are not the host bootstrap - cache -- stdlib instrumentation remains in Bazel-declared outputs rather than either - host cache - -The config-disabled repository returns its empty interface before it discovers -the Go SDK or selects a bootstrap cache, so an ordinary build does not create a -Test Optimization cache directory. - -### GOROOT and SDK compatibility - -Some Bazel sandboxes expose an SDK layout that differs from what Orchestrion's -subprocesses assume. The builder normalizes that by: - -- resolving absolute SDK/GOROOT paths -- creating compatibility symlinks when `GOROOT/src` is missing - -Orchestrion and the Go toolchain expect a usable GOROOT layout. Bazel's SDK -presentation can differ enough that the builders need to repair that view. - -### Synthetic module preparation - -Orchestrion expects a real Go module context. The builder prepares a synthetic -module environment when the Bazel workdir does not already match that shape. - -Orchestrion resolves injectors and pinned module files through Go module -semantics. Synthetic module preparation gives it a workspace shape compatible -with those expectations. - -### Woven dependency warmup - -The builder can probe or warm the dependencies Orchestrion commonly weaves. -In generic mode this includes the broader tracing/profiling and contrib helper -set. In `test_optimization` mode the synthetic module and helper closure are -reduced to the standard Go `testing` Test Optimization path and exclude profiler -and Datadog contrib HTTP/slog helper roots. - -Some required woven dependencies are first touched inside sandboxed steps. -Warming them reduces failures caused by lazy first access in those contexts. - -### Deterministic build outputs - -The Go build cache itself is writable process state, so it cannot be exposed as -a Bazel-declared output. `GoStdlib` builds in a private scratch `GOCACHE` and -publishes only sorted, manifested woven archives to its declared cache. An -ordinary Go action leaves that declared Orchestrion cache empty; a non-optimized -target must never consume the instrumented stdlib. - -Later synthetic helper, compile, and link actions seed their private caches from -the read-only published archives. Before hashing or copying results, the builder -normalizes the temporary Go work directory, Bazel execroot references, CGO -random seed, synthetic source trim paths, and helper archive build IDs. Actual -relative CGO inputs still resolve from the physical execroot. - -The generated-profile verifier repeats the build in isolated output roots and -compares action keys and bytes for `GoStdlib`, -`GoSyntheticTestmainHelpers`, synthetic `GoCompilePkg` (`~testmain.a`), and -`GoLink`. This is the same action chain that downstream Reprise checks observe. - -## End-to-end flow for an enabled Go test - -The disabled path stops at the macro: it creates the public raw `go_test` and -does not enter the wrapper, vendored Orchestrion, or payload path below. - -```mermaid -sequenceDiagram - participant User - participant Macro as dd_topt_go_test - participant Wrapper as orch_go_test - participant RG as vendored rules_go - participant Orch as patched Orchestrion - participant Bin as final test binary - - User->>Macro: declare Go test - Macro->>Macro: select payload data + add pin files - Macro->>Wrapper: public test target - Wrapper->>RG: build raw go_test with mode transition; preserve enabled flag - RG->>RG: compile customer packages - RG->>Orch: compile woven stdlib as needed - RG->>Orch: prepare synthetic testmain helper packagefiles - RG->>RG: publish helper archives + manifest as declared outputs - RG->>RG: link final binary using same helper family - RG->>Bin: produce instrumented test executable - Bin->>Bin: emit tracer / CI Visibility runtime -``` - -The system crosses many boundaries. This sequence compresses them into one path -so a maintainer can reason from the declared test target to the observed runtime -behavior. - -## Runtime result - -At runtime, a correctly wired binary shows evidence that the compile-time path -worked: - -- Datadog tracer debug logs can appear when enabled -- CI Visibility hooks around Go `testing` are preserved in the final test binary - -Those runtime markers are not produced by the macro alone. They depend on the -compile, synthetic `testmain`, and final link steps all agreeing on the same -instrumented package universe. - -Separate from runtime behavior, the builders also use archive-content heuristics -such as `instrumentTestingTFunc` and `instrumentTestingM` while scanning helper -exports during compile/link consistency checks. Those names are useful -build-time indicators, but they are not themselves user-visible runtime -signals. - -The global goal is not just "a successful build". It is a final test binary -whose runtime behavior proves that CI Visibility instrumentation survived the -full Bazel compile and link pipeline. - -## Invariants maintainers should preserve - -If you change this system, keep these invariants intact. - -### 1. The public API stays Bazel-native - -Users should keep writing `dd_topt_go_test`, not a custom shell rule or an -alternate test runner. - -The integration is meant to feel like normal Bazel usage to consumers. If the -public entry point changes shape, the maintenance burden moves into every -consumer workspace. - -### 2. Bootstrap owns workspace mutation - -The bootstrap tool is the place that mutates: - -- `MODULE.bazel` -- `orchestrion.tool.go` -- `orchestrion.yml` -- pinned Go module files - -Do not push that complexity onto each test target. - -Workspace mutation has to happen in one place or the setup becomes fragile and -non-idempotent. Bootstrap is that one place. - -### 3. Synthetic `testmain` compile and final link must share helper state - -The sidecar manifest, declared helper archive tree, and helper-root reuse are -structural, not optional. - -This is the specific invariant that keeps `testing` instrumentation alive in the -final binary. Losing it produces apparently successful builds with incomplete -runtime behavior. - -### 4. Importcfg must remain the single place where package universes are made coherent - -If compile or link needs a packagefile and it is not in importcfg, that is an -importcfg problem first. - -Putting package-universe fixes in multiple places makes the pipeline impossible -to reason about. Importcfg must remain the primary source of truth. - -### 5. Orchestrion must run in a real-enough Go module context - -Builder-side synthetic module and cache preparation are not cosmetic. They are -required because Orchestrion shells out to Go tooling internally. - -If Orchestrion cannot see a usable Go module environment, it stops behaving like -the tool this design expects and starts failing in ways that look unrelated to -the actual instrumentation logic. - -### 6. The Orchestrion binary is part of the integration surface - -The source patches in `extensions.bzl` are part of the supported design. Treat -them as first-class integration code, not as temporary local hacks. - -The vendored `rules_go` changes alone are not enough. The built Orchestrion -binary carries Bazel-specific behavior that the rest of the design relies on. - -## Debugging map - -When something breaks, this is the fastest place to look. - -The entries below map failure classes to the files that control them. - -### Bootstrap and workspace shape - -- [main.go](../modules/go/tools/dd_topt_go_bootstrap/main.go) - -Look here if: - -- `MODULE.bazel` wiring is wrong -- `rules_go_orchestrion_tool` is missing -- `orchestrion.tool.go` was not pinned - -Many apparent compile bugs originate in the workspace shape. - -### Macro and transition behavior - -- [topt_go_test.bzl](../modules/go/topt_go_test.bzl) -- [topt_go_orchestrion.bzl](../modules/go/topt_go_orchestrion.bzl) - -Look here if: - -- the wrapper target is not enabling Orchestrion -- runfiles or executable naming are wrong -- payload data selection is wrong - -Check analysis-time mistakes before builder internals. A wrong target graph -never reaches the intended toolchain path. - -### Orchestrion source patching - -- [extensions.bzl](../third_party/rgo/v0_60_0/base/go/private/orchestrion/extensions.bzl) - -Look here if: - -- the Orchestrion tool fails during resolver or injector setup -- dependency resolution differs from local `go` behavior -- patched upstream assumptions drift - -Some failures originate inside the generated Orchestrion binary rather than -Starlark or the builders. - -### Compile/link consistency - -- [compilepkg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go) -- [importcfg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go) -- [link.go](../third_party/rgo/v0_60_0/base/go/tools/builders/link.go) - -Look here if: - -- stdlib weaving appears missing -- Datadog helper packages are unresolved -- synthetic test binaries lose instrumentation at final link - -Most correctness bugs eventually reduce to compile/link inconsistency. This is -the fastest entry point for the class of bugs that most directly threaten the -global goal. - -## Summary - -The current architecture is: - -- a Bazel-native public Go test macro -- a bootstrap step that wires the workspace once -- a vendored `rules_go` fork that integrates Orchestrion at the builder layer -- a patched Orchestrion binary built from source -- a synthetic `testmain` manifest contract that keeps compile and final link in - the same Datadog helper package universe - -That combination is what makes the current Go CI Visibility path work under -Bazel while still behaving like a normal `go_test` target from the user's point -of view. diff --git a/docs/go_orchestrion_maintainer_state.md b/docs/go_orchestrion_maintainer_state.md deleted file mode 100644 index 5a4a3898..00000000 --- a/docs/go_orchestrion_maintainer_state.md +++ /dev/null @@ -1,501 +0,0 @@ - - -# Go Orchestrion maintainer state - -## Purpose - -This page summarizes the current Go and Orchestrion integration for -maintainers. - -Use it to find: - -- how the integration is wired end to end -- why the fork exists and what it changes -- why the system still rebuilds Orchestrion on a true cold start -- which performance optimizations were kept -- which experiments failed and why -- what the next useful optimization directions are - -This is intentionally different from the other Go Orchestrion docs: - -- [go_orchestrion_bazel_deep_dive.md](./go_orchestrion_bazel_deep_dive.md) - explains the steady-state architecture in detail -- [rules_go_orchestrion_performance_analysis.md](./rules_go_orchestrion_performance_analysis.md) - is the earlier code-reading analysis -- [rules_go_orchestrion_probe_measurements.md](./rules_go_orchestrion_probe_measurements.md) - is the running measurement log - -The supported upstream lines are `rules_go` v0.60.0, v0.61.1, v0.62.0, and -v0.63.0. They share the same Orchestrion behavior and determinism contract. - -## Current architecture - -```mermaid -flowchart TD - A["Consumer BUILD: dd_topt_go_test"] --> Z{"Selected sync export enabled?"} - Z -->|"no"| R["Public raw go_test; no TO-owned targets"] - Z -->|"yes"| B["Hidden raw go_test"] - Z -->|"yes"| C["Test Optimization data repo"] - B --> D["Vendored rules_go fork"] - D --> E["compilepkg / stdlib / link builders"] - E --> F["orchestrion toolexec"] - F --> G["Patched Orchestrion binary"] - G --> H["Woven packages and woven stdlib"] - H --> I["Final Bazel test binary"] - I --> J["CI Visibility runtime and payload files"] - - K["Bootstrap or manual MODULE wiring"] --> L["rules_go Orchestrion extension"] - L --> N{"DD_TEST_OPTIMIZATION_ENABLED?"} - N -->|"no"| O["Stable empty repository and local aliases"] - N -->|"yes"| G - N -->|"yes"| M["dd_trace_go_versions.json"] - M --> E -``` - -In this design: - -- Bazel is still the build and test system -- `rules_go` still owns the Go build pipeline -- Orchestrion is not a post-processing step -- the vendored fork makes Orchestrion part of the normal compile, stdlib, and - link path -- without `--config=test-optimization`, the public test is the raw `go_test`, - the stable Orchestrion aliases select package-local empty targets, and the - gated repository returns before host-Go discovery or source fetching -- `test_optimization` mode is the narrower standard Go `testing` path: - customer packages and external `_test` packages compile normally, while - stdlib `testing`, synthetic `testmain`, helper packagefiles, importcfg, and - link support stay coherent -- the metadata input may come from static sync or from an invocation-scoped - manifest aggregate; this does not change Orchestrion compilation. In the - managed case, the consumer's central wrapper selects the exact - `topt_data_by_target` entry before calling `dd_topt_go_test`. - -## Mode contract - -`dd_topt_go_test` exposes two Orchestrion modes: - -- `general` is the default and preserves the generic Orchestrion behavior. -- `test_optimization` is the standard Go `testing` Test Optimization mode. It - keeps the stdlib/synthetic-`testmain` path needed for payloads while avoiding - Orchestrion on ordinary customer package compiles and external `_test` - compiles. - -`test_optimization` intentionally does not provide automatic `testify/suite` -instrumentation. Metadata emitted by the Go macro should include -`bazel.go.orchestrion.mode`, and it should include -`bazel.go.test_binary_linker_optimization` so reviewers can tell whether the -test-binary linker flag optimization was active. - -## Why the fork exists - -We are not using upstream `rules_go` and upstream Orchestrion as-is. - -The vendored fork exists because the Bazel path needs behavior that upstream -Orchestrion and upstream `rules_go` do not provide together out of the box: - -- Bazel-compatible `toolexec` invocation inside sandboxed Go actions -- synthetic `testmain` handling that keeps Datadog helper packages and Bazel's - generated test binary layout consistent -- woven stdlib archive handling that stays compatible with `importcfg`, - packagefile resolution, and later link steps -- fallback stdlib and archive lookup behavior when Bazel's archive layout does - not match Orchestrion's default assumptions -- a bootstrap path that builds a patched Orchestrion binary and exposes its - version metadata to later builder actions - -In short: the fork is not ornamental. It is the compatibility layer that keeps -Orchestrion coherent inside Bazel. - -## Why the Orchestrion tool is built when enabled - -On an enabled true cold start, Bazel still builds the Orchestrion binary -because: - -- the extension downloads Orchestrion source, not a prebuilt binary -- we patch that source for Bazel compatibility before building it -- the resulting binary is platform-specific -- the bootstrap cache is host-local, so a fresh CI runner starts empty - -The build uses the Bazel-managed Go SDK declared by guided bootstrap, not a -host `go` binary. The SDK version is also part of the bootstrap cache identity. -On a compatible cache hit, the extension restores the tool before materializing -the SDK repository; on a miss, it materializes the SDK and verifies that its -reported version matches the declaration. - -This cost does not apply to the config-disabled path. The patched repository -rule writes the stable empty interface before SDK or host-Go discovery, and the -vendored `rules_go` aliases avoid referencing the real tool repository while -the Orchestrion build setting is false. - -The recent work changed one important part of this story: - -- Bazel no longer rewrites Orchestrion's own `go.mod` to force the target - `dd-trace-go` version into the tool build -- instead, Bazel builds the patched tool from Orchestrion's upstream module - graph and separately validates the selected Datadog tracer versions against - the target module through `dd_trace_go_versions.json` - -That reduced cold bootstrap cost substantially without changing the target-side -tracer version that the instrumented binary actually loads. - -## Why the target uses the pinned tracer version - -There are two different module contexts: - -1. the temporary Orchestrion tool repository that Bazel downloads and builds -2. the target Go module being compiled and tested - -The expensive old bootstrap path was rewriting the first one. The runtime -behavior we care about comes from the second one. - -The kept design now does this: - -- build the patched Orchestrion tool from its own upstream module graph -- write the configured Datadog tracer versions into - `dd_trace_go_versions.json` -- validate the target module against that version file in the builder path -- let the instrumented target binary load the tracer version pinned by the - target workspace - -That is why runtime validation must show the target workspace's configured -Datadog tracer version, even after tool-side `go.mod` rewriting was removed. - -## Deterministic action outputs - -Bazel can reuse the instrumented build only when identical inputs produce -identical action keys and bytes. The generated-profile verifier therefore runs -two isolated builds and compares these action families: - -- `GoStdlib`; -- `GoSyntheticTestmainHelpers`; -- the synthetic `GoCompilePkg` action that produces `~testmain.a`; -- `GoLink`. - -An ordinary `GoStdlib` action must leave its declared Orchestrion cache empty. -Only a Test Optimization action may publish the woven stdlib cache, and that -cache must contain deterministic, manifested archive data. This prevents an -ordinary Go build from consuming an instrumented standard library. - -The instrumented path normalizes values that vary between otherwise equivalent -Bazel executions: the temporary Go work directory, the Bazel execroot in -compiler arguments, the CGO random seed, synthetic source trim paths, and build -IDs in copied helper archives. Relative CGO inputs still resolve against the -real execroot; normalization changes identity, not input lookup. - -`tools/dev/verify_rules_go_profiles.py` implements this check with compact Bazel -execution logs and isolated output roots. Local runs need `zstd` to read those -logs. CI splits the supported versions across x86_64 and ARM64 shards. A changed -action key with identical bytes is reported as avoidable rebuilding; changed -output bytes are a reproducibility failure. - -## Kept performance changes - -These changes were kept because they improved performance without changing -runtime behavior. - -### 1. Synthetic helper decision and export caching - -Kept in the builder path: - -- stable cache keys across fresh Bazel output bases -- a persisted synthetic helper decision graph -- a persisted shared module-export cache - -Effect: - -- repeated synthetic `testmain` rebuilds stopped paying the full - `go list -export -deps` cost again -- the synthetic helper cold-miss path became materially cheaper - -### 2. Orchestrion bootstrap artifact cache - -Kept in the extension path: - -- host-side cache for the built Orchestrion binary, exact version metadata, - and offline module proxy -- standard Go `GOMODCACHE` and `GOCACHE` reuse for the ordinary `go build` and - `go list` work that produces those artifacts, with a repository-local - fallback when either standard cache is read-only - -Effect: - -- warm bootstrap reuse is now real across fresh Bazel output bases -- warm bootstrap dropped from "rebuild the tool again" to "restore cached - artifact and continue" -- the Test Optimization cache no longer duplicates Go's normal module and - build caches - -The config-disabled repository exits before Go discovery and cache selection, -so it does not create or update the Test Optimization cache. - -### 3. Removing tool-side Orchestrion module repinning - -Kept in the extension path: - -- no more `go mod edit -require=...` -- no more unconditional `go mod tidy` in the extension/bootstrap build of the - downloaded Orchestrion repo -- build the patched tool from Orchestrion's upstream module graph - -Effect: - -- large cold bootstrap reduction -- no loss of runtime tracer correctness in the target binary - -The builder still has its own synthetic-module preparation in some paths. In -particular, stdlib weaving can still run a synthetic-module tidy on the -no-`orchsrc` path. The optimization here was specific to the extension-side -tool bootstrap flow. - -### 4. Safer stdlib persistence/sync trimming - -Kept in the stdlib path: - -- avoid rewriting the Bazel stdlib cache onto itself -- trim redundant cache-sync work where the copied result is already at the - required location - -Effect: - -- stdlib action time improved -- no runtime regression was observed in the validated kept version - -## Experiments that failed - -These failed experiments are worth documenting because they are tempting to try -again. - -### 1. Host-side woven stdlib snapshot cache - -Why it looked promising: - -- it improved build timings -- it reduced repeated stdlib work on paper - -Why it was reverted: - -- builds still passed -- but runtime weaving was broken -- CI Visibility stopped starting at runtime -- tracer startup logs disappeared -- no payload files were written - -Lesson: - -- stdlib performance work is not safe to judge by build success alone -- any stdlib optimization must also be validated with tracer startup and - payload-file output - -### 2. Hardlink-based stdlib archive persistence - -Why it looked promising: - -- it reduced local copy overhead - -Why it was reverted: - -- GitHub runners hit permission errors when later cache-seeding logic tried to - rewrite those files - -Lesson: - -- local filesystem behavior is not enough to validate stdlib cache changes -- hosted CI runners expose permission behavior that local macOS runs may not - -### 3. Manifest-only or partial stdlib reuse - -Why it looked promising: - -- the visible rooted stdlib package set is small - -Why it failed: - -- later compile and link paths still require a valid cache-local stdlib closure -- the true closure is much larger than the visible root package list -- manifest-only reuse and partial reseeding broke later cache-local reads - -Lesson: - -- the stdlib path needs more than "archives somewhere on disk" -- it needs a coherent cache-local archive family for a larger dependency - closure - -### 4. Compile-time stdlib seed narrowing - -Why it looked promising: - -- the fixed stdlib seed looked broader than some compile actions might need - -Why it was rejected: - -- the safe narrowed experiment was runtime-correct -- but it made the build materially slower in A/B comparison - -Lesson: - -- the current broader seed is not obviously accidental -- narrowing it shifts work around in ways that can make the full build worse - -### 5. Narrowing the stdlib root package build set - -Why it looked promising: - -- `cmd/internal/cov` and `cmd/internal/bio` looked like candidates for - conditional handling - -Why it was rejected: - -- the cold build got worse -- the hermetic fixture flow broke locally - -Lesson: - -- the current stdlib root set is coupled to the real Bazel test flow more than - the surface comment suggests - -## Current best-known baseline - -The current baseline should be read in two modes: - -- true cold start -- warm bootstrap reuse - -### Cold start - -Measured in the fixture repo with: - -- fresh `--output_base` -- isolated `XDG_CACHE_HOME` -- local override mode - -Current cold numbers: - -- total build elapsed: `249.053s` -- critical path: `75.20s` -- Orchestrion bootstrap total: `153.057s` -- inside bootstrap: - - download/extract: `32.066s` - - tool `go build`: `113.334s` - -The largest remaining cold-start cost is building the patched Orchestrion tool -from scratch. - -### Warm bootstrap reuse - -Measured with: - -- a new `--output_base` -- the same shared cache root - -Current isolated warm-bootstrap observation: - -- fresh Bazel output root with the same bootstrap cache: about `12.9s` total - command elapsed -- Orchestrion repository bootstrap within that command: about `1.8s` to `2.5s` -- cache identity source: the declared Bazel SDK, allowing restore before SDK - materialization - -Once the bootstrap artifact cache hits, the tool bootstrap is no longer the -main cost. Bazel startup, repository mapping, and analysis account for most of -the remaining fresh-output-root command time. - -### Runtime correctness validation - -Measured with: - -- `DD_TRACE_DEBUG=1` -- `DD_CIVISIBILITY_ENABLED=1` -- a fresh test execution - -What must be verified: - -- tracer startup logs are present -- CI Visibility initializes -- the test binary reports the target workspace's configured Datadog tracer - version -- a payload JSON file is written under `test.outputs/payloads/tests` - -This validation is part of the baseline, not optional extra checking. - -## Remaining expensive work - -### 1. Cold Orchestrion tool build - -This is still the largest single cost on an enabled true cold start. The -config-disabled path does not build the tool. - -At this point, the likely remaining wins here are more architectural than -incremental: - -- prebuilt patched binaries -- a maintained Bazel-compatible Orchestrion fork -- cross-runner artifact reuse beyond the current host-local cache - -### 2. Real stdlib build work - -Once bootstrap is warm, stdlib is still substantial. - -The remaining stdlib cost now appears to be mostly real work: - -- real woven stdlib build time -- real archive persistence and cache-local synchronization that later steps - still depend on - -The easy cache tricks have mostly been exhausted or disproved. - -## Possible next steps - -### If the goal is the biggest remaining performance win - -Go back to the Orchestrion bootstrap problem. - -The best remaining options are: - -1. publish prebuilt patched Orchestrion binaries -2. maintain a Bazel-compatible Orchestrion fork and distribute binaries from - it -3. upstream enough Bazel support to shrink the patch surface, then distribute - a stable patched binary artifact - -### If the goal is to keep iterating locally without a larger architectural move - -Stay on stdlib, but only with very careful validation. - -The most plausible local direction is: - -- understand whether the real stdlib build mode can be reduced safely -- do not assume cache-layout tricks are safe -- keep validating runtime behavior, not just build success - -## Validation checklist for future changes - -Future changes in this area should not be considered done until they pass all -of these checks: - -1. focused builder tests in this repo -2. relevant vendored Starlark tests in this repo -3. consumer validation in `../rules_test_optimization_tests` -4. Go integration harnesses for both: - - `ORCHESTRION_MODE=general` - - `ORCHESTRION_MODE=test_optimization` -5. runtime validation with: - - `DD_TRACE_DEBUG=1` - - `DD_CIVISIBILITY_ENABLED=1` -6. two isolated generated-profile runs compare `GoStdlib`, synthetic helpers, - synthetic `~testmain.a`, and `GoLink` action keys and bytes -7. confirmation that: - - tracer startup logs are present - - the pinned tracer version is the one loaded at runtime - - a payload file is written under `test.outputs/payloads/tests` - - metadata includes the expected `bazel.go.orchestrion.mode` - - metadata includes the expected `bazel.go.test_binary_linker_optimization` - -The runtime check caught the rejected stdlib snapshot approach; the isolated -action comparison prevents that class of cache drift from reaching a consumer. diff --git a/docs/go_test_optimization_stdlib_testing_only_plan.md b/docs/go_test_optimization_stdlib_testing_only_plan.md deleted file mode 100644 index 18b60657..00000000 --- a/docs/go_test_optimization_stdlib_testing_only_plan.md +++ /dev/null @@ -1,444 +0,0 @@ - - -# Go Test Optimization Stdlib/Testing-Only Plan - -This is the historical implementation plan for -`feat/go-topt-stdlib-only-correctness`. The branch now implements the mode -plumbing, pin-file validation, reduced helper/link closure, linker optimization -opt-out, metadata fields, and payload-producing validation described here. - -## Objective - -Prove a correctness-first Go Test Optimization mode where `test_optimization` -keeps Orchestrion out of customer package compiles while preserving the -stdlib/`testing`, synthetic `testmain`, Datadog helper packagefile, final link, -and importcfg support required to emit standard Go `testing` payloads. - -The goal is to verify that standard Go `testing` payloads are still emitted -without applying Orchestrion to customer package compiles or external `_test` -package archives. Performance measurement and the final pure testing-only -stdlib/helper closure are intentionally out of scope for this first pass. - -## Decisions - -- D-1: Work starts from a clean branch based on `origin/main`: - `feat/go-topt-stdlib-only-correctness`. -- D-2: In this branch, `orchestrion_mode = "test_optimization"` - means the aggressive Test Optimization mode for correctness: customer package - compiles stay plain, while stdlib/synthetic `testmain`/link keep the - Orchestrion context needed for standard `testing` payloads. Do not add a - second public optimized mode unless implementation evidence proves a separate - mode is necessary. -- D-3: v1 supports standard Go `testing` only. Automatic `testify/suite` - instrumentation is an explicit non-goal. -- D-4: Correctness is the only success criterion for this pass: tests must - emit usable payloads, doctor must pass, and uploader dry-run enrichment must - pass. Timing and performance benchmarking come later. -- D-5: Validate first in `dd-source` using the existing - `code-workload-runner-pipelines-descriptor` Go pilot service. -- D-6: Preserve generic Orchestrion behavior outside `test_optimization`. - `general` mode must continue to instrument normal customer code paths. - -## Current Baseline - -This section records the pre-branch baseline used when the experiment started. - -- `origin/main` does not yet have an Orchestrion mode setting. The wrapper - transition in `modules/go/topt_go_orchestrion.bzl` only sets - `@rules_go//go/private/orchestrion:enabled`. -- When Orchestrion is enabled today, `compilepkg.bzl` passes `-orchestrion` to - every compile action. That is the behavior this plan changes only for - `test_optimization`. -- Synthetic `testmain` support already has a distinct builder path: the builder - augments synthetic `testmain` roots before compile and then disables actual - compile toolexec for the synthetic `testmain` package itself. -- Stdlib weaving already persists woven stdlib exports and includes - `testing`/`testing/internal/testdeps` in the persisted set. -- The prior `feat/go-topt-performance-mode` branch may be inspected as a - reference for mode plumbing, helper closure reduction, and tests, but this - branch should not blindly merge or cherry-pick it because the desired v1 - behavior is stricter: external `_test` archives should also compile without - Orchestrion. - -## Non-Goals - -- NG-1: Do not benchmark or claim performance improvements in this pass. -- NG-2: Do not preserve automatic `testify/suite` weaving in - `test_optimization` v1. -- NG-3: Do not remove generic Orchestrion support from `general`. -- NG-4: Do not broaden the `dd-source` pilot beyond the selected Go service - until the small correctness proof passes. -- NG-5: Do not run a real upload during correctness validation; use uploader - dry-run with enrichment validation. - -## Implementation Plan - -### S-1: Add Mode Plumbing - -Add an Orchestrion mode build setting to both vendored variants: - -- `third_party/rules_go_orchestrion_base/go/private/orchestrion/BUILD` -- `third_party/rules_go_orchestrion_complete/go/private/orchestrion/BUILD` - -The setting must accept at least: - -- `"general"`: current behavior. -- `"test_optimization"`: stdlib/`testing`-only behavior. - -Propagate that setting through: - -- `modules/go/topt_go_orchestrion.bzl` - - add an `orchestrion_mode` attr to `orch_go_test` - - have `orch_transition` set both `enabled = True` and `mode` -- `modules/go/topt_go_test.bzl` - - add `orchestrion_mode = "general"` - - validate allowed values - - forward the selected mode to `orch_go_test` - - require module-root Orchestrion pin files in `test_optimization` when the - BUILD package is below the Go module root -- `modules/go/topt_go_infer.bzl` - - include `bazel.go.orchestrion.mode` in generated Bazel metadata -- both vendored forks' `go/private/context.bzl` and transition plumbing - - carry `orchestrion_mode` in the Go context so actions and builders can read - it - -Keep public defaults conservative: existing callers remain on `general` unless -they opt into `orchestrion_mode = "test_optimization"`. - -### S-2: Gate Compile Orchestrion by Mode - -In both vendored variants, update compile action wiring so: - -- `general`: preserve existing behavior. -- `test_optimization`: do not pass `-orchestrion` to customer package compiles. -- `test_optimization`: do not pass `-orchestrion` to external `_test` package - archives (`testfilter == "only"`). -- `test_optimization`: preserve the synthetic `testmain` augmentation path so - helper imports and `orchestrion.pack` are still produced. - -Likely files: - -- `third_party/rules_go_orchestrion_base/go/private/actions/compilepkg.bzl` -- `third_party/rules_go_orchestrion_complete/go/private/actions/compilepkg.bzl` -- `third_party/rules_go_orchestrion_base/go/tools/builders/compilepkg.go` -- `third_party/rules_go_orchestrion_complete/go/tools/builders/compilepkg.go` - -The key rule is: disabling compile toolexec must not disable synthetic -`testmain` helper/root resolution. If necessary, separate "action has -Orchestrion context for helper/testmain work" from "run the compiler through -Orchestrion toolexec". - -### S-3: Preserve Stdlib Weaving - -Keep `GoStdlib` Orchestrion-enabled in `test_optimization`. - -Keep the stdlib closure focused on packages required for standard `testing` -payload correctness. The implementation still includes stdlib packages such as -`log`, `log/slog`, and `net/http`; the important reduction is that Datadog -contrib HTTP/slog helper roots are removed from the synthetic helper/link -closure in `test_optimization` mode. The initial candidate closure was: - -- `testing` -- `testing/internal/testdeps` -- `flag` -- `fmt` -- `log` -- `os` -- `os/exec` -- `runtime` -- `io/ioutil` where still required by helper dependency resolution - -Validated implementation note: `test_optimization` still lets the woven stdlib -cover standard packages such as `net/http` and `log/slog`, but the synthetic -`testmain` helper/link closure no longer carries the matching Datadog contrib -helper roots. Generic contrib roots remain part of `general` mode only. - -Likely files: - -- `third_party/rules_go_orchestrion_base/go/private/actions/stdlib.bzl` -- `third_party/rules_go_orchestrion_complete/go/private/actions/stdlib.bzl` -- `third_party/rules_go_orchestrion_base/go/tools/builders/stdlib.go` -- `third_party/rules_go_orchestrion_complete/go/tools/builders/stdlib.go` -- `third_party/rules_go_orchestrion_base/go/tools/builders/importcfg.go` -- `third_party/rules_go_orchestrion_complete/go/tools/builders/importcfg.go` - -### S-4: Preserve Synthetic Testmain And Helper Packagefiles - -Keep synthetic `testmain` as the bridge that forces Datadog helper packages -into the final test binary. - -In `test_optimization`, reduce helper roots to standard `testing` support where -the current Orchestrion/stdlib closure permits it: - -- keep `github.com/DataDog/dd-trace-go/v2/internal/civisibility/integrations/gotesting` -- keep `github.com/DataDog/dd-trace-go/v2/internal/civisibility/integrations` -- keep `github.com/DataDog/dd-trace-go/v2/ddtrace/tracer` -- keep `github.com/DataDog/dd-trace-go/v2/internal` only if validation proves it - is still required by the selected helper closure -- keep gotesting coverage helpers if coverage validation requires them -- keep generic contrib roots and profiler out of this mode unless standard - `testing` payload validation proves one is still required: - - `github.com/DataDog/dd-trace-go/contrib/net/http/v2` - - `github.com/DataDog/dd-trace-go/contrib/net/http/v2/internal/orchestrion` - - `github.com/DataDog/dd-trace-go/contrib/log/slog/v2` - - `github.com/DataDog/dd-trace-go/v2/profiler` - -Likely files: - -- `third_party/rules_go_orchestrion_base/go/tools/builders/compilepkg.go` -- `third_party/rules_go_orchestrion_complete/go/tools/builders/compilepkg.go` -- `third_party/rules_go_orchestrion_base/go/tools/builders/link.go` -- `third_party/rules_go_orchestrion_complete/go/tools/builders/link.go` -- `third_party/rules_go_orchestrion_base/go/tools/builders/orchestrion_synthetic_tool.go` -- `third_party/rules_go_orchestrion_complete/go/tools/builders/orchestrion_synthetic_tool.go` -- `third_party/rules_go_orchestrion_base/go/tools/builders/orchestrion_version.go` -- `third_party/rules_go_orchestrion_complete/go/tools/builders/orchestrion_version.go` - -### S-5: Preserve Link And Importcfg Coherence - -Final test binary links must still receive: - -- the synthetic `testmain` packagefile manifest -- Datadog helper packagefiles needed by the selected helper closure -- woven stdlib packagefiles for `testing` and its selected closure -- the reduced module/proxy inputs needed to resolve helper packages offline - -Do not interpret "remove Orchestrion from customer/test compiles" as "remove -all link/importcfg Orchestrion support". A build can pass while runtime -CI Visibility silently fails if the link step falls back to unwoven stdlib or -misses helper packagefiles. - -Likely files: - -- `third_party/rules_go_orchestrion_base/go/private/actions/link.bzl` -- `third_party/rules_go_orchestrion_complete/go/private/actions/link.bzl` -- `third_party/rules_go_orchestrion_base/go/tools/builders/link.go` -- `third_party/rules_go_orchestrion_complete/go/tools/builders/link.go` -- `third_party/rules_go_orchestrion_base/go/tools/builders/importcfg.go` -- `third_party/rules_go_orchestrion_complete/go/tools/builders/importcfg.go` - -### S-6: Update Tests And Docs - -Add or update tests that prove the new shape: - -- macro analysis tests in `modules/go/tests/test_macro.bzl` - - `orchestrion_mode = "test_optimization"` is accepted - - selected mode is forwarded to the wrapper - - module-root pin files are required for nested packages in this mode - - runtime payload data remains in runfiles - - `enable_test_binary_linker_optimization = False` preserves caller linker - flags - - metadata records `bazel.go.orchestrion.mode` and - `bazel.go.test_binary_linker_optimization` -- builder unit tests in both vendored variants - - mode validation - - mode-aware helper closure - - stdlib package list reduction - - synthetic helper cache keys include mode - - `test_optimization` keeps only the generic contrib roots still required by - woven stdlib/link correctness -- integration/aquery tests - - `GoStdlib` remains Orchestrion-enabled - - ordinary customer `GoCompilePkg` actions do not contain `-orchestrion` - - external `_test` `GoCompilePkgExternal` actions do not contain - `-orchestrion` - - synthetic `testmain` still produces and consumes `orchestrion.pack` - - final `GoLink` still has the manifest/importcfg/helper packagefile support - -Update docs: - -- `README.md` -- `docs/Configuration_Reference.md` -- `docs/go_orchestrion_bazel_deep_dive.md` -- `docs/go_orchestrion_maintainer_state.md` - -Docs must state that `test_optimization` v1 is standard Go `testing` only and -does not support automatic `testify/suite` instrumentation. - -### S-7: Keep Fork Variants In Sync - -Apply semantic changes to both: - -- `third_party/rules_go_orchestrion_base` -- `third_party/rules_go_orchestrion_complete` - -Then refresh and verify fork metadata: - -```bash -python3 tools/dev/diff_rules_go_fork.py --metadata third_party/rules_go_orchestrion_base.METADATA.json --write-report -python3 tools/dev/diff_rules_go_fork.py --metadata third_party/rules_go_orchestrion_complete.METADATA.json --write-report -python3 tools/dev/verify_rules_go_variants.py -``` - -## Local Validation - -Run narrow checks first: - -```bash -./bazelw test //modules/go/tests:all -``` - -Run vendored builder tests for both variants. If root labels do not expose the -vendored fork tests directly, use the existing variant smoke scripts: - -```bash -RULES_GO_VARIANT=base tools/dev/run_rules_go_variant_smoke.sh -RULES_GO_VARIANT=complete tools/dev/run_rules_go_variant_smoke.sh -``` - -Then run Go consumer integration harnesses: - -```bash -USE_BAZEL_VERSION=8.8.2 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh -USE_BAZEL_VERSION=8.8.2 RULES_GO_VARIANT=complete tools/tests/integration/run_workspace_go_integration.sh -USE_BAZEL_VERSION=8.8.2 RULES_GO_VARIANT=base tools/tests/integration/run_bzlmod_go_integration.sh -USE_BAZEL_VERSION=8.8.2 RULES_GO_VARIANT=complete tools/tests/integration/run_bzlmod_go_integration.sh -``` - -Run full repo validation only after focused checks pass: - -```bash -./bazelw test //... -``` - -## dd-source Pilot Validation - -Use the local `dd-source` checkout at: - -```text -/Users/tony.redondo/dd/dd-source -``` - -Current pilot service: - -- service key: `code_workload_runner_pipelines_descriptor` -- service name: `code-workload-runner-pipelines-descriptor` -- Go sync repo: - `test_optimization_data_go_code_workload_runner_pipelines_descriptor` -- pilot targets: - - `//domains/ci-app/apps/apis/code-workload-runner-pipelines-descriptor/internal/inspector:go_default_test` - - `//domains/ci-app/apps/apis/code-workload-runner-pipelines-descriptor/internal/finder:go_default_test` - -The current `dd-source` target -`//tools/test_optimization:dd_test_optimization_doctor` also expects the Python -`query_validator` target. For a Go-only proof, either temporarily narrow that -doctor target in a separate `dd-source` validation branch/worktree, or also run: - -```bash -bin/bzl test --config=test-optimization \ - //domains/ffe/apps/apis/query_validator/internal/validator/tests:test_sql_validator -``` - -Do not overwrite existing `dd-source` local changes. Before modifying that -checkout, inspect status with: - -```bash -git -c core.fsmonitor=false status --short --branch -``` - -Point `dd-source` to this local rules checkout for validation. Because the -current `dd-source` WORKSPACE uses archive pins, use the least invasive local -override pattern available in that checkout. If no helper exists, patch the -validation worktree to use this checkout's `datadog-rules-test-optimization` -and matching local `rules_go` variant rather than the pinned archive. - -Run the Go pilot: - -```bash -bin/bzl sync --config=test-optimization \ - --only=test_optimization_data_go_code_workload_runner_pipelines_descriptor \ - --repo_env=FETCH_SALT="$(date +%s)" - -bin/bzl test --config=test-optimization \ - //domains/ci-app/apps/apis/code-workload-runner-pipelines-descriptor/internal/inspector:go_default_test - -bin/bzl test --config=test-optimization \ - //domains/ci-app/apps/apis/code-workload-runner-pipelines-descriptor/internal/finder:go_default_test -``` - -Run doctor and uploader dry-run: - -```bash -bin/bzl run --config=test-optimization \ - //tools/test_optimization:dd_test_optimization_doctor - -bin/bzl run --config=test-optimization \ - //tools/test_optimization:dd_upload_payloads -- \ - --dry-run \ - --validate-enrichment \ - --expected-enriched-tag=bazel.go.payload_selection -``` - -Evidence required from `dd-source`: - -- both Go pilot tests pass -- each pilot has `bazel-testlogs/.../test.outputs/payloads/tests/*.json` -- each payload JSON is parseable and contains uploadable test events -- `bazel_target_metadata.json` exists beside each target's payloads -- metadata includes the exact `bazel.target`, `bazel.package`, service, and - `bazel.go.payload_selection` -- doctor succeeds without `full_bundle_no_match` -- uploader dry-run enrichment succeeds -- logs or payloads prove CI Visibility initialized; test success alone is not - sufficient - -If possible, add one build-shape proof in `dd-source` with probes enabled: - -```bash -RULES_GO_ORCHESTRION_PROBE=1 \ -RULES_GO_ORCHESTRION_PROBE_FILE=/tmp/dd-source-topt-probes.log \ -bin/bzl test --config=test-optimization \ - --action_env=RULES_GO_ORCHESTRION_PROBE=1 \ - --action_env=RULES_GO_ORCHESTRION_PROBE_FILE=/tmp/dd-source-topt-probes.log \ - //domains/ci-app/apps/apis/code-workload-runner-pipelines-descriptor/internal/inspector:go_default_test -``` - -The probe or aquery evidence must show: - -- stdlib/`testing` path is Orchestrion-enabled -- ordinary app package compiles are not Orchestrion-enabled -- external `_test` package compiles are not Orchestrion-enabled -- final link/importcfg still has the synthetic manifest/helper support - -## Risks - -- R-1: Removing Orchestrion too broadly can leave synthetic `testmain` without - Datadog helper packagefiles. Mitigation: split compile-toolexec gating from - helper/testmain context. -- R-2: Builds can pass while runtime payloads are missing if link/importcfg - falls back to unwoven stdlib. Mitigation: verify payload files, tracer - initialization, doctor, and uploader dry-run enrichment. -- R-3: `testify/suite` users lose automatic suite instrumentation in - `test_optimization` v1. Mitigation: document this explicitly and add a - negative/unsupported fixture. -- R-4: Fork variants can drift. Mitigation: update base and complete together, - refresh changed-files reports, and run variant verification. -- R-5: `dd-source` has local changes and environment-specific Bazel state. - Mitigation: use `git -c core.fsmonitor=false`, avoid overwriting unrelated - edits, validate in a separate worktree if needed, and run heavy commands - serially. -- R-6: Local Bazel or Orchestrion caches can hide correctness problems. - Mitigation: run at least one clean-ish validation after `bazel shutdown` and - refresh sync data with `FETCH_SALT`. - -## Done Criteria - -The experiment is done only when all of these are true: - -- `test_optimization` mode exists and is documented. -- Customer package compiles and external `_test` package compiles do not receive - Orchestrion in `test_optimization`. -- Stdlib/`testing`, synthetic `testmain`, helper packagefiles, link, and - importcfg remain coherent. -- Standard Go `testing` pilot targets in `dd-source` emit usable payloads. -- Doctor and uploader dry-run enrichment pass for the pilot. -- `testify/suite` automatic instrumentation is either documented as unsupported - or covered by a negative fixture. -- Both vendored fork variants are updated and verified. -- No performance claims are made from this pass. diff --git a/docs/internal_monorepo_go_rollout_guide.md b/docs/internal_monorepo_go_rollout_guide.md index b8710aef..541da1e1 100644 --- a/docs/internal_monorepo_go_rollout_guide.md +++ b/docs/internal_monorepo_go_rollout_guide.md @@ -8,10 +8,11 @@ This product includes software developed at Datadog # Large WORKSPACE Go Rollout Guide -This document is the maintained rollout checklist for large WORKSPACE -monorepos that need Go Test Optimization with Orchestrion. It is intentionally +This document is the rollout checklist for large WORKSPACE monorepos that add +Go Test Optimization. Go tests build with the Go SDK that `ddtest instrument` +instruments and link Mini, ddtest's Test Optimization runtime. The page is generic: it describes the repository shape and validation sequence without -encoding any consumer-specific service, path, or target names. +naming any consumer's services, paths, or targets. Use [`Language_Onboarding.md`](./Language_Onboarding.md#large-workspace-monorepos) for the step-by-step onboarding guide. Use this page as the operator checklist @@ -22,17 +23,23 @@ managed command that expands exact targets should instead use the [automatic managed Go/Python contract](./Language_Onboarding.md#automatic-managed-gopython-monorepos). That path derives services per invocation and does not check in pilot lists, Gazelle policy, or ownership gates. Both paths share the same `rules_go`, -Orchestrion, doctor, and uploader safety requirements. +instrumentation, doctor, and uploader requirements. ## Published Contract -- Consume one complete base `rules_go` Orchestrion tree. Do not copy patch - directories, and do not configure `patches`, `patch_tool`, or `patch_args`. -- Use the versioned base `rules_go` Orchestrion tree for ordinary repositories. -- Keep the repository's existing Bazel name for `rules_go` when other - repository code depends on that name. -- Use the public WORKSPACE helper so the Go companion repo mapping and the - selected `rules_go` upstream support line stay consistent. +- Use upstream `rules_go`, declared as `io_bazel_rules_go`. The SDK + repositories that `go_wrap_sdk` writes for the instrumented SDK load it under + that name. +- Use the public WORKSPACE helpers, in the order shown in + [step 8 of the Installation Reference](./Installation_Reference.md#8-configure-go-support-in-workspace-with-the-public-helper): + `datadog_go_test_optimization_workspace_repositories(...)` for the Go + companion, then, after `go_rules_dependencies()` and + `go_register_toolchains(...)`, `topt_go_ddtest_workspace_repositories(...)` + for the instrumented SDK. +- Pin a commit that is reachable from `origin/main`; never publish + feature-branch SHAs into consumer snippets. In archive mode, keep the commit, + archive URL, archive SHA256, and archive prefix generated from that same + commit; see [Generate published Go pins](./Installation_Reference.md#generate-published-go-pins). ```bzl load( @@ -42,25 +49,25 @@ load( datadog_go_test_optimization_workspace_repositories( rto_commit = "", - rules_go_repo_name = "", - rules_go_upstream = "v0_60_0", - rules_go_variant = "base", ) -``` -Archive mode is also supported when the consuming environment requires a -mirrored or integrity-checked archive. Keep the commit, archive URL, archive -SHA256, and archive prefix generated from the same published commit. +# After go_rules_dependencies() and go_register_toolchains(version = ""): +load( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_repository.bzl", + "topt_go_ddtest_workspace_repositories", +) + +topt_go_ddtest_workspace_repositories( + base_sdk_roots = {"linux_amd64": "@go_sdk//:ROOT"}, + exec_platforms = ["linux_amd64"], +) +``` ## Local Pilot Requirements -- Use a commit that is reachable from `origin/main`; never publish feature-branch - SHAs into consumer snippets. -- Configure `dd_topt_go_orchestrion_tool_repo(...)` with the current supported - Orchestrion version and the current supported `dd-trace-go` Bazel-mode - version, plus the repository's central `@go_sdk//:ROOT` label and exact Go - SDK version. Do not load the underlying `rules_go` repository rule directly - or repeat this wiring per service. +- Instrument the repository's central SDK with `base_sdk_roots`, or set + `go_version` to the repository's Go version. List every exec platform that + runs instrumented tests in `exec_platforms`. - Configure `dd_topt_go_workspace_sync_repositories(...)` with: - `service` - `runtime_version` @@ -70,12 +77,9 @@ SHA256, and archive prefix generated from the same published commit. sync by default. - Keep repository-specific scheduling, Docker, tags, platform constraints, and flaky policy in the repository-local wrapper layer. -- Route the existing central Go wrapper through `dd_topt_go_test` and set - `orchestrion_mode = "test_optimization"` for - standard Go `testing` Test Optimization pilots. The `general` mode remains - available for explicit compatibility validation only. -- Keep BUILD callsites on that same wrapper; the named config controls whether - its expansion is normal or instrumented. +- Route the existing central Go wrapper through `dd_topt_go_test`. Keep BUILD + callsites on that same wrapper; the named config controls whether its + expansion is normal or instrumented. - Select only the agreed runtime-emitting pilot scope in central repository policy. - Do not list `.build_test`, compile-only, or other build-only controls as @@ -97,9 +101,10 @@ SHA256, and archive prefix generated from the same published commit. common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 ``` - Optimized targets enable Orchestrion through their own transition. Removing - the config disables metadata repositories; the public Go helpers enable that - metadata gating by default. + Instrumented tests select the instrumented SDK through their own transition. + Removing the config disables the metadata repositories, and the disabled + export builds a plain `go_test`; the public Go helpers enable that metadata + gating by default. ## Bootstrap Flow @@ -108,17 +113,9 @@ Generate repository-local scaffolding, then review it before committing: ```bash bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --workspace-mode \ - --service "" \ - --runtime-version "" \ - --rules-go-repo-name "" \ - --rules-go-upstream v0_60_0 \ - --rules-go-variant base \ - --dd-trace-go-version v2.9.1 \ --write-bazelrc \ - --write-orchestrion-files \ --write-wrapper-template \ --write-validation-script \ - --check-go-repositories \ --large-monorepo \ --shutdown-bazel-on-exit \ --default-jobs=1 \ @@ -128,24 +125,18 @@ bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --control-target "//path/to/plain/control:go_default_test" ``` -Create the single doctor/uploader pair in -`//tools/test_optimization:BUILD.bazel`; do not use `--write-root-targets` for -this monorepo flow. +`--print-workspace-snippet` with `--service`, `--runtime-version`, and +`--rto-commit` prints the WORKSPACE wiring for review. Create the single +doctor/uploader pair in `//tools/test_optimization:BUILD.bazel`; do not use +`--write-root-targets` for this monorepo flow. ### Updating an existing managed config -When upgrading from the current release, rerun the same bootstrap with -`--write-bazelrc`. It replaces the content between the Datadog-managed markers -with the current single-config contract, preserves all content outside those -markers, and is idempotent. This adds both -`DD_TEST_OPTIMIZATION_ENABLED=1` and the existing `rules_go` Orchestrion flag to -the named config; consumers do not need a separate migration mode or a second -bool flag. - -If the repository owns checked-in `go_repository(...)` declarations, run the -repository-owned refresh command after targeted Go module sync and rerun -bootstrap with `--check-go-repositories`. Bootstrap should verify those pins; it -should not silently edit large generated dependency files on its own. +Rerun the same bootstrap with `--write-bazelrc`. It replaces the content +between the Datadog-managed markers with the current config, preserves all +content outside those markers, and is idempotent. A workspace set up by 1.x +must also replace its 1.x WORKSPACE wiring by hand with the helpers above; see +the [migration note](../CHANGELOG.md#unreleased). ## Validation @@ -175,6 +166,9 @@ Upload-time enrichment validation proves that tags expected in Datadog are present in the final upload body. Raw payload files on disk are intentionally not the final enriched body. +For instrumentation failures, see +[Go tests instrumented by ddtest](./Troubleshooting.md#go-tests-instrumented-by-ddtest). + ## Disk Guardrails - Check `df -h /` before every heavy Bazel phase. diff --git a/docs/rules_go_orchestrion_performance_analysis.md b/docs/rules_go_orchestrion_performance_analysis.md deleted file mode 100644 index 8e4afee4..00000000 --- a/docs/rules_go_orchestrion_performance_analysis.md +++ /dev/null @@ -1,396 +0,0 @@ - - -# rules_go Orchestrion Fork Performance Analysis - -> Scope note: this document analyzes the generic base variant under -> `third_party/rgo/v0_60_0/base`. Consumers that already own a -> `rules_go` patch stack can use a generated public consumer patch profile. - -## Purpose - -This document captures a code-reading analysis of the vendored -`third_party/rgo/v0_60_0/base` fork and explains: - -- what was changed to make Orchestrion work under Bazel and `rules_go` -- which parts of the implementation are likely on the performance hot path -- which costs appear inherent to Orchestrion's design -- which extra costs appear to come from this fork's integration work -- which optimization directions look most promising - -This is a maintainer note for future reference. - -This document is intentionally historical. It reflects the code-reading view -from `2026-03-25`, before the later measured bootstrap changes on this branch. - -For the current state: - -- use [go_orchestrion_maintainer_state.md](./go_orchestrion_maintainer_state.md) - for the maintained summary -- use [rules_go_orchestrion_probe_measurements.md](./rules_go_orchestrion_probe_measurements.md) - for the measured results and later rollbacks - -## Scope And Limits - -This analysis is based on: - -- the checked-in fork metadata and changed-files report -- the vendored fork source under `third_party/rgo/v0_60_0/base/` -- Datadog's public Orchestrion documentation -- the public Orchestrion source entrypoint - -This analysis is not based on runtime benchmarking or profiling. - -Any statements about likely bottlenecks are informed engineering inferences from -the code, not measured timings. - -Analysis date: `2026-03-25` - -## Primary References - -- [third_party/rgo/v0_60_0/base.METADATA.json](../third_party/rgo/v0_60_0/base.METADATA.json) -- [third_party/rgo/v0_60_0/base.CHANGED_FILES.md](../third_party/rgo/v0_60_0/base.CHANGED_FILES.md) -- [third_party/rgo/v0_60_0/base/go/private/orchestrion/extensions.bzl](../third_party/rgo/v0_60_0/base/go/private/orchestrion/extensions.bzl) -- [third_party/rgo/v0_60_0/base/go/private/context.bzl](../third_party/rgo/v0_60_0/base/go/private/context.bzl) -- [third_party/rgo/v0_60_0/base/go/private/actions/compilepkg.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/compilepkg.bzl) -- [third_party/rgo/v0_60_0/base/go/private/actions/link.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/link.bzl) -- [third_party/rgo/v0_60_0/base/go/private/actions/stdlib.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/stdlib.bzl) -- [third_party/rgo/v0_60_0/base/go/tools/builders/orchestrion.go](../third_party/rgo/v0_60_0/base/go/tools/builders/orchestrion.go) -- [third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go) -- [third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go) -- [third_party/rgo/v0_60_0/base/go/tools/builders/link.go](../third_party/rgo/v0_60_0/base/go/tools/builders/link.go) -- [third_party/rgo/v0_60_0/base/go/tools/builders/stdlib.go](../third_party/rgo/v0_60_0/base/go/tools/builders/stdlib.go) -- [Orchestrion docs](https://datadoghq.dev/orchestrion/) -- [Orchestrion contributor guide](https://datadoghq.dev/orchestrion/contributing/) -- [Orchestrion main.go](https://github.com/DataDog/orchestrion/blob/main/main.go) - -## High-Level Conclusion - -The fork is deep, not a thin wrapper. - -At the time of this analysis, the fork was already deep and touched dozens of -paths against upstream `rules_go v0.60.0`, including: - -- module extension setup for Orchestrion -- `rules_go` analysis-time context wiring -- compile, link, and stdlib action definitions -- the Go builder implementation used by those actions -- synthetic `testmain` handling -- stdlib cache handling - -That means performance issues are very likely coming from the normal build hot -path, not from one isolated bootstrap helper. - -## What Changed To Make Orchestrion Work - -### 1. A custom Orchestrion binary is built during module/repository resolution - -The fork adds a dedicated module-extension layer in -[go/private/orchestrion/extensions.bzl](../third_party/rgo/v0_60_0/base/go/private/orchestrion/extensions.bzl). - -At the time of this analysis, that layer: - -- validates the configured `dd-trace-go` versions -- resolves package/module versions up front -- downloads the Orchestrion source -- patches upstream files for Bazel compatibility -- builds the Orchestrion binary used later by the toolchain - -This is a fetch-time cost, not a per-compile cost, but it is part of the total -integration overhead. - -Later work on this branch removed the extension/bootstrap rewrite and tidy of -the downloaded Orchestrion repo's own `go.mod`. See the maintainer-state and -probe measurement docs for that newer design and for the remaining builder-side -synthetic-module work. - -### 2. Orchestrion is threaded through `rules_go` itself - -The fork does not treat Orchestrion as an external post-process. - -Instead, it modifies `rules_go` internals so the toolchain knows about: - -- the Orchestrion binary -- the Orchestrion version file -- when compile, link, and stdlib actions should pass `-orchestrion` -- which extra inputs must be staged for Orchestrion to work in a Bazel sandbox - -This starts in -[go/private/context.bzl](../third_party/rgo/v0_60_0/base/go/private/context.bzl#L705) -where `GoContextInfo` gains Orchestrion-related fields that become available to -common Go actions. - -### 3. Compile, link, and stdlib actions are widened - -When Orchestrion is enabled, the action wrappers add: - -- the Orchestrion binary -- the Orchestrion version file -- the `go` binary -- the Go SDK source tree -- staged rule `data` files such as `go.mod`, `go.sum`, `orchestrion.tool.go`, - and `orchestrion.yml` - -Relevant entry points: - -- [compilepkg.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/compilepkg.bzl#L215) -- [link.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/link.bzl#L204) -- [stdlib.bzl](../third_party/rgo/v0_60_0/base/go/private/actions/stdlib.bzl#L179) - -This is necessary because Orchestrion shells out to `go` and expects a Go -module-shaped environment, even under Bazel sandboxing. - -### 4. The builder wraps real Go tools with `orchestrion toolexec` - -The builder runtime in -[env.go](../third_party/rgo/v0_60_0/base/go/tools/builders/env.go#L144) -switches from direct tool execution to: - -```text -orchestrion toolexec ... -``` - -It also injects: - -- `TOOLEXEC_IMPORTPATH` -- jobserver state -- compatible cache-related environment variables - -This matches Orchestrion's own public model: its CLI documentation says it -instruments code by interfacing with the Go toolchain through `-toolexec`. - -### 5. The fork adds Bazel-specific support for synthetic testmain and stdlib weaving - -Two areas clearly required extra integration work: - -- synthetic Bazel `testmain` archives must include Datadog helper packages and - their transitive link closure -- stdlib weaving must produce cacheable, importcfg-compatible archives that - later compile and link actions can consume consistently - -Those behaviors live mostly in: - -- [compilepkg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go#L596) -- [link.go](../third_party/rgo/v0_60_0/base/go/tools/builders/link.go#L123) -- [stdlib.go](../third_party/rgo/v0_60_0/base/go/tools/builders/stdlib.go#L43) -- [importcfg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go) - -## Likely Performance Hot Paths - -### 1. Per-action jobserver startup and dependency warmup - -The strongest suspected hot path is repeated jobserver and dependency setup in -[orchestrion.go](../third_party/rgo/v0_60_0/base/go/tools/builders/orchestrion.go). - -The relevant behavior is: - -- compile actions start an Orchestrion jobserver -- most link actions do the same -- stdlib weaving does the same -- startup may call `ensureWovenPackagesAvailable(...)` -- that helper may run `go list`, then `go mod download`, then `go list` again - -This suggests real work may happen before the actual compile or link step -starts. - -The code does not obviously show a long-lived shared jobserver across Bazel -actions. If each action pays startup and warmup costs independently, that can -be expensive. - -### 2. Repeated temporary module preparation - -The next likely hotspot is `ensureGoModExists(...)` in -[orchestrion.go](../third_party/rgo/v0_60_0/base/go/tools/builders/orchestrion.go#L268). - -It may: - -- copy `go.mod` and `go.sum` into the current working directory -- copy or synthesize `orchestrion.tool.go` -- copy `orchestrion.yml` -- validate resolved `dd-trace-go` versions -- restore or prepare a synthetic module state -- run module-resolution-related commands on cache miss - -This logic is touched by compile, link, and stdlib paths, so repeated setup -cost here can become multiplicative. - -### 3. Importcfg rewriting and export discovery - -The importcfg path in -[importcfg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go#L565) -looks expensive because it repeatedly resolves export archives through -`go list -export -deps`. - -The code suggests multiple layers of work: - -- resolve exports for Datadog helper/module packages -- rewrite stdlib entries toward Orchestrion-aware caches -- sanitize archives that contain `link.deps` -- use closure-driven cache rewrites that may resolve overlapping package sets - -This area is especially suspicious because repeated `go list -export -deps` -calls can be costly even if compilation itself is cached. - -### 4. Synthetic testmain cold-miss behavior - -Synthetic testmain support in -[compilepkg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go#L596) -looks like a major cold-path cost. - -When cache misses happen, the code may: - -- prepare a synthetic module -- run `go list -deps -json` -- decide which helper packages need source compilation -- resolve external exports -- compile helper packages one by one -- write manifests and sanitized helper archives - -There is persistent caching here, but cold misses are likely expensive enough -to deserve separate attention from ordinary package compilation. - -### 5. Stdlib weaving cache sync and duplicate archive copying - -The stdlib path appears to have the largest I/O footprint. - -After weaving, `persistOrchestrionStdlibExports(...)` and related helpers in -[stdlib.go](../third_party/rgo/v0_60_0/base/go/tools/builders/stdlib.go#L338): - -- walk the stdlib archive tree -- copy archives into a persisted export tree -- write a manifest -- copy them back or sync them into other cache locations - -That is a lot of duplicate file traffic and is a strong candidate for both -wall-clock time and cache churn. - -### 6. Action input expansion and sandbox materialization - -The Starlark action wrappers now add `sdk.srcs` plus staged module files for -Orchestrion-enabled actions. - -That likely increases: - -- sandbox input materialization cost -- action invalidation surface -- remote cache key sensitivity - -This may not be the biggest cost by itself, but it broadens the amount of work -each action has to carry. - -## Costs That Look Inherent Versus Integration-Specific - -### Costs that appear inherent to Orchestrion's model - -- Using `-toolexec` to intercept compile-time tool execution -- AST rewrite work done by Orchestrion itself -- some level of module-aware configuration discovery -- some stdlib weaving cost when instrumentation requires woven stdlib packages - -These are part of how Orchestrion works, based on its public documentation. - -### Costs that appear to come from this fork's integration strategy - -- repeated synthetic module setup inside Bazel actions -- repeated `go list` / `go mod download` warmup before actions -- repeated export discovery through importcfg rewrite helpers -- synthetic testmain helper compilation and manifest plumbing -- duplicate stdlib archive copying across cache layers -- widened action inputs to support sandboxed Orchestrion execution - -These are the first places to look for optimization because they may be -reducible without changing Orchestrion's core functionality. - -## Most Promising Optimization Directions - -### 1. Measure setup costs separately from rewrite costs - -The first profiling target should be the code we add around Orchestrion, not -just Orchestrion itself. - -The most useful timings to collect are: - -- jobserver startup time -- woven-package warmup time -- synthetic module restore/prepare time -- importcfg export-resolution time -- synthetic testmain helper build time -- stdlib cache persistence/sync time - -Without that separation, it will be too easy to blame Orchestrion broadly for -costs that actually come from the fork. - -### 2. Reduce repeated module/export discovery - -The importcfg and synthetic-module helpers appear to repeat similar discovery -work. - -The best medium-term opportunity may be: - -- batch export resolution more aggressively -- reduce repeated `go list -export -deps` calls -- share more prepared module state across actions - -### 3. Treat synthetic testmain as its own optimization problem - -Synthetic testmain support is specialized enough that it should probably be -optimized separately. - -Its cold path is complex and may dominate test builds even if ordinary package -compilation is reasonably cached. - -### 4. Reduce duplicate stdlib archive copying - -If the cache layout can be simplified without breaking correctness, the stdlib -path looks like a good place to remove unnecessary I/O. - -### 5. Narrow Orchestrion action inputs where possible - -If the builder does not actually need the full SDK source tree or all staged -module files for every action, tightening those inputs could reduce sandbox and -cache pressure. - -## Practical Takeaway - -The strongest current hypothesis is: - -1. Orchestrion itself is not the only reason the fork is slow. -2. The fork is paying substantial repeated setup cost outside the actual source - rewriting phase. -3. The highest-probability bottlenecks are jobserver bootstrap, synthetic - module preparation, export discovery, synthetic testmain cold misses, and - stdlib cache synchronization. - -That means the next step should not be a blind rewrite. - -It should be a focused profiling pass that instruments these specific fork-side -helpers so measured time can be assigned to: - -- Orchestrion proper -- our builder/setup code -- synthetic testmain support -- stdlib weaving/cache sync - -## Verification - -This document was produced by: - -- reading the checked-in fork metadata and changed-files report -- reading the Starlark action wiring -- reading the builder/runtime implementation in the vendored fork -- checking Datadog's public Orchestrion docs and public source entrypoint - -This document was not produced by: - -- benchmarking the fork -- capturing CPU profiles -- measuring Bazel action timing -- comparing live timings against upstream `rules_go` - -Use this as a map for profiling and optimization work, not as proof of actual -runtime percentages. diff --git a/docs/rules_go_orchestrion_probe_measurements.md b/docs/rules_go_orchestrion_probe_measurements.md deleted file mode 100644 index e2c954c5..00000000 --- a/docs/rules_go_orchestrion_probe_measurements.md +++ /dev/null @@ -1,839 +0,0 @@ - - -# rules_go Orchestrion Probe Measurements - -> Scope note: measurements in this document describe the generic base variant -> under `third_party/rgo/v0_60_0/base`. Public consumer patch profiles -> are verified separately by `tools/dev/verify_rules_go_profiles.py`. - -## Purpose - -This note captures the first real timing pass for the vendored -`third_party/rgo/v0_60_0/base` fork using the probe instrumentation added -to the builder and Orchestrion extension paths. - -Unlike -[rules_go_orchestrion_performance_analysis.md](./rules_go_orchestrion_performance_analysis.md), -this document is based on measured local runs, not just code reading. - -Measurement date: `2026-03-26` - -Latest baseline refresh: `2026-03-27` - -## Scope And Limits - -These measurements came from a local consumer-style run in the sibling fixture -repository: - -- workspace: - `../rules_test_optimization_tests` -- target: - `//src/go-project:hello_test` -- host Go: - `1.24.0` -- platform: - local macOS arm64 machine - -This is a useful first baseline, but it is still one machine, one target, and -one cold-start-oriented scenario. The timings below should be treated as -representative local data, not universal constants. - -For the current state summary that ties these measurements to the architecture -and the kept or reverted experiments, see -[go_orchestrion_maintainer_state.md](./go_orchestrion_maintainer_state.md). - -## Latest Baseline Refresh - -After the later stdlib experiments and rollbacks, the branch was re-measured -from the current good state using three runs in the sibling fixture repo: - -- cold build: - fresh `--output_base` plus isolated `XDG_CACHE_HOME` -- warm build: - fresh `--output_base` while reusing that same cache root -- runtime validation: - fresh test execution with `DD_TRACE_DEBUG=1` and - `DD_CIVISIBILITY_ENABLED=1` - -Measured result: - -- cold build: - - total elapsed: `249.053s` - - critical path: `75.20s` - - `extensions.orchestrion_build_total`: `153.057s` - - `extensions.download_and_extract`: `32.066s` - - `extensions.go_build`: `113.334s` -- warm build: - - total elapsed: `86.281s` - - critical path: `68.91s` - - `extensions.orchestrion_build_total`: `3.955s` -- runtime validation: - - total elapsed: `96.620s` - - critical path: `81.32s` - - `extensions.orchestrion_build_total`: `3.532s` - -Runtime validation also confirmed that the measured branch behaved correctly: - -- tracer startup logs were present -- CI Visibility initialized -- the test binary reported `Datadog Tracer v2.7.0-dev.1` -- the test wrote a payload file under `test.outputs/payloads/tests` - -This latest baseline keeps the same broad conclusion as the earlier -measurements: - -- on a true cold start, the Orchestrion tool build is still the largest - remaining cost -- once bootstrap reuse kicks in, stdlib becomes the most visible remaining - build step - -## Probe Collection Setup - -Two probe paths were involved: - -- extension probes: - enabled with `log_timing = True` in the local - `orchestrion.from_source(...)` call in the fixture repo -- builder probes: - enabled with `RULES_GO_ORCHESTRION_PROBE=1` - -The builder probe plumbing also needed one fix before the measurements were -useful: - -- the compile, link, and stdlib actions were constructing explicit action - environments and were not forwarding the probe environment variables into the - builder process -- after forwarding those variables, successful actions started surfacing probe - lines in normal Bazel output and mirroring them into a shared probe file - -## Commands Used - -### Baseline cold run - -```bash -GIT_CONFIG_GLOBAL=/dev/null \ -GIT_TERMINAL_PROMPT=0 \ -PATH=$HOME/sdk/go1.24.0/bin:$PATH \ -RULES_GO_ORCHESTRION_PROBE=1 \ -./bazelw \ - --output_base=/tmp/rto_phase12_cold/output_base \ - test //src/go-project:hello_test \ - --test_output=streamed \ - --sandbox_debug \ - --subcommands \ - --verbose_failures \ - --action_env=RULES_GO_ORCHESTRION_PROBE=1 \ - --action_env=RULES_GO_ORCHESTRION_PROBE_FILE=/tmp/rto_phase12_cold/builder-probes.log -``` - -### Phase 1/2 seed run - -This run populated the shared helper-decision cache and the shared -module-export cache using the Phase 1/2 code. - -```bash -GIT_CONFIG_GLOBAL=/dev/null \ -GIT_TERMINAL_PROMPT=0 \ -PATH=$HOME/sdk/go1.24.0/bin:$PATH \ -RULES_GO_ORCHESTRION_PROBE=1 \ -./bazelw \ - --output_base=/tmp/rto_phase12_post_export_seed/output_base \ - test //src/go-project:hello_test \ - --test_output=streamed \ - --sandbox_debug \ - --subcommands \ - --verbose_failures \ - --action_env=RULES_GO_ORCHESTRION_PROBE=1 \ - --action_env=RULES_GO_ORCHESTRION_PROBE_FILE=/tmp/rto_phase12_post_export_seed/builder-probes.log -``` - -### Phase 1/2 verification run - -This is the important verification shape for the first implementation pass: - -- keep the decision cache and shared module-export cache warm -- delete only the whole helper-archive bundle -- rerun with a fresh output base -- confirm that helper selection and export resolution are reused on the forced - helper rebuild path - -```bash -rm -rf \ - /var/folders/42/00hrt9gj3276cy8h0_d9nw480000gq/T/datadog-orchestrion-go-cache/cache/rules-go-orchestrion/synthetic-testmain-helpers/5c8037544d53d7ef - -GIT_CONFIG_GLOBAL=/dev/null \ -GIT_TERMINAL_PROMPT=0 \ -PATH=$HOME/sdk/go1.24.0/bin:$PATH \ -RULES_GO_ORCHESTRION_PROBE=1 \ -./bazelw \ - --output_base=/tmp/rto_phase12_post_export_verify/output_base \ - test //src/go-project:hello_test \ - --test_output=streamed \ - --sandbox_debug \ - --subcommands \ - --verbose_failures \ - --action_env=RULES_GO_ORCHESTRION_PROBE=1 \ - --action_env=RULES_GO_ORCHESTRION_PROBE_FILE=/tmp/rto_phase12_post_export_verify/builder-probes.log -``` - -### Builder probe log location - -The builder now mirrors probe lines into the shared Orchestrion cache root. - -On this machine, the measured run wrote to: - -```text -/var/folders/42/00hrt9gj3276cy8h0_d9nw480000gq/T/datadog-orchestrion-go-cache/probes/builder-probes.log -``` - -That exact base path is machine-specific. The important invariant is that the -file lives under the shared `datadog-orchestrion-go-cache/probes/` directory. - -## End-To-End Result - -The baseline run and the Phase 1/2 verification run both completed -successfully. - -Key top-level numbers from the measured runs: - -- baseline cold run: - `430.571s` elapsed, `219.60s` critical path -- Phase 1/2 seed run: - `334.254s` elapsed, `169.85s` critical path -- Phase 1/2 verification run with helper archive removed: - `341.734s` elapsed, `163.97s` critical path - -The phase timings below are the meaningful comparison point. The Phase 1/2 -verification run intentionally forced a helper-archive rebuild, but it kept the -new lower-layer caches available so their reuse behavior could be measured. - -## Measured Extension Hotspots - -From the cold extension setup pass: - -- `extensions.validate_dd_trace_go_versions`: - about `0.08s` -- `extensions.download_and_extract`: - about `21-22s` -- `extensions.go_mod_edit`: - about `8-9s` -- `extensions.go_mod_tidy`: - about `96-104s` -- `extensions.go_build`: - about `32-41s` -- `extensions.orchestrion_build_total`: - about `160-176s` - -### Extension Conclusion - -The biggest startup cost before normal Bazel Go actions even begin is the -temporary Orchestrion module preparation and binary build. - -In that earlier baseline, the largest single measured phase in that area was -`go mod tidy`. - -## Historical Bootstrap Cache Pass - -This section records the intermediate bootstrap-cache experiment that preceded -the later no-tool-repin design. - -It is not the current cache contract. The maintained extension now reuses Go's -standard host build and module caches for ordinary Go work and reserves the -persistent Datadog cache for final bootstrap artifacts. - -The next optimization pass focused only on the Orchestrion bootstrap path in -`go/private/orchestrion/extensions.bzl`. - -### What changed - -This pass added: - -- a host-side bootstrap artifact cache for the built Orchestrion binary and its - `dd_trace_go_versions.json` -- stable host-side `GOMODCACHE` and `GOCACHE` roots under the shared - `datadog-orchestrion-go-cache` -- a bootstrap cache key that includes: - - Orchestrion version - - normalized `dd_trace_go_versions` - - host Go identity - - cache ABI version - - manual patchset identifier -- a fast path that reuses the cached bootstrap artifact on a cache hit -- a fallback path that still runs `go mod tidy` only when the first build says - the module graph is not ready -- Starlark unit tests for the retry classifier and bootstrap cache-key - stability - -### Commands used for the bootstrap-only pass - -Cold verification: - -```bash -BASE=/tmp/rto_bootstrap_eval7 -rm -rf "$BASE" -mkdir -p "$BASE/cold" "$BASE/warm" "$BASE/cache_home" - -cd ../rules_test_optimization_tests -source "$HOME/ddtrace.sh" >/dev/null 2>&1 || true - -PATH=$HOME/sdk/go1.24.0/bin:$PATH \ -RULES_GO_ORCHESTRION_PROBE=1 \ -XDG_CACHE_HOME="$BASE/cache_home" \ -RULES_GO_ORCHESTRION_PROBE_FILE="$BASE/cold/builder-probes.log" \ -./bazelw \ - --output_base="$BASE/cold/output_base" \ - build //src/go-project:hello_test \ - --subcommands \ - --verbose_failures \ - >"$BASE/cold/run.log" 2>&1 -``` - -Warm verification: - -```bash -cd ../rules_test_optimization_tests -source "$HOME/ddtrace.sh" >/dev/null 2>&1 || true - -PATH=$HOME/sdk/go1.24.0/bin:$PATH \ -RULES_GO_ORCHESTRION_PROBE=1 \ -XDG_CACHE_HOME=/tmp/rto_bootstrap_eval7/cache_home \ -RULES_GO_ORCHESTRION_PROBE_FILE=/tmp/rto_bootstrap_eval7/warm/builder-probes.log \ -./bazelw \ - --output_base=/tmp/rto_bootstrap_eval7/warm/output_base \ - build //src/go-project:hello_test \ - --subcommands \ - --verbose_failures \ - >/tmp/rto_bootstrap_eval7/warm/run.log 2>&1 -``` - -### Historical bootstrap cache result - -The best measured variant at that stage kept three things: - -- the host-side bootstrap artifact cache -- the persistent host-side Go caches -- `go mod tidy` as a fallback instead of as an unconditional step - -Measured result from that intermediate variant: - -- cold bootstrap: - - `extensions.download_and_extract`: `21.907s` - - `extensions.go_mod_edit`: `9.831s` - - `extensions.go_mod_download`: `53.575s` - - `extensions.go_build_initial`: `55.091s` with fallback - - `extensions.go_mod_tidy`: `30.131s` - - `extensions.go_build_retry`: `37.981s` - -## Woven Stdlib Snapshot Rollback - -One later stdlib experiment looked good from the build timings and was pushed, -but it had to be rolled back. - -### What happened - -The host-side woven stdlib snapshot cache made build/test timing look better, -but it silently broke runtime weaving: - -- Bazel builds still succeeded -- Bazel tests still passed -- the test binary still contained `dd-trace-go` -- but CI Visibility never started at runtime -- tracer startup logs disappeared -- payload files were not written -- the uploader then had nothing to upload - -This failure reproduced both in CI and locally. - -### Practical lesson - -For stdlib-path changes, build success is not enough. - -Any future stdlib optimization must also be validated in -`../rules_test_optimization_tests` with: - -- `DD_TRACE_DEBUG=1` -- `DD_CIVISIBILITY_ENABLED=1` -- confirmation that the tracer startup logs appear -- confirmation that payload files are written under `test.outputs/payloads` - -If those runtime signals are missing, the optimization is not safe even if the -build and test steps are green. - - `extensions.orchestrion_build_total`: `216.628s` - - end-to-end build: `305.653s` elapsed -- warm bootstrap: - - `extensions.bootstrap_cache_hit`: yes - - `extensions.orchestrion_build_total`: `3.097s` - - no `download_and_extract`, `go_mod_edit`, `go_mod_download`, - `go_mod_tidy`, or `go_build` phases executed on the hit - - end-to-end build: `91.957s` elapsed - -### Bootstrap pass conclusion - -This pass clearly improved warm bootstrap reuse. The host-side bootstrap cache -removes almost all of the repeated Orchestrion setup cost across fresh Bazel -output bases. - -It did **not** achieve the hoped-for cold-start reduction yet. Several follow-up -experiments were measured locally, including: - -- using `go build -mod=mod` on the first build -- skipping the explicit tracer-module download - -Those experiments made the cold bootstrap slower on this machine, so they were -not kept. - -The practical outcome is: - -- warm bootstrap reuse is now real and large -- cold bootstrap is still expensive -- the next extension-focused pass should look for a better way to prepare the - Orchestrion module graph without paying for both a large module download and a - later rebuild - -## No-Tool-Repin Bootstrap Pass - -The next pass removed the tool-side module rewrite entirely. Bazel still: - -- downloads and patches the Orchestrion source tree -- writes `dd_trace_go_versions.json` -- validates the configured tracer version against the target module later in - the builder/runtime path - -But it no longer runs `go mod edit`, `go mod download`, or `go mod tidy` -against the downloaded Orchestrion repo itself. - -### Commands used for the no-tool-repin pass - -Consumer validation: - -```bash -cd ../rules_test_optimization_tests -eval "$(sed -n '/^export DD_API_KEY=/p;/^export DD_SITE=/p' ~/ddtrace.sh)" - -TEST_TARGET=//src/go-project:hello_test \ -DD_TRACE_DEBUG=1 \ -./runtests - -TEST_TARGET=//src/go-project:hello_test \ -DD_TRACE_DEBUG=1 \ -./runtests-hermetic -``` - -Tracer log validation: - -```bash -cd ../rules_test_optimization_tests -eval "$(sed -n '/^export DD_API_KEY=/p;/^export DD_SITE=/p' ~/ddtrace.sh)" - -TEST_TARGET=//src/go-project:hello_test \ -DD_TRACE_DEBUG=1 \ -DD_CIVISIBILITY_ENABLED=1 \ -./runtests -``` - -### Final bootstrap numbers - -Cold bootstrap with the no-tool-repin flow: - -- `extensions.validate_dd_trace_go_versions`: `87 ms` -- `extensions.download_and_extract`: `22.192 s` -- `extensions.patch_source_tree`: `61 ms` -- `extensions.go_build_initial`: `25.886 s` -- `extensions.bootstrap_cache_write`: `430 ms` -- `extensions.orchestrion_build_total`: `49.742 s` - -Removed phases on that cold run: - -- `extensions.go_mod_edit`: `0` -- `extensions.go_mod_download`: `0` -- `extensions.go_mod_tidy`: `0` -- `extensions.go_mod_tidy_fallback`: `0` - -Comparison to the earlier cold bootstrap baseline: - -- before: about `175.2 s` -- after: about `49.7 s` -- improvement: about `125.5 s` faster, roughly `71.6%` lower - -Target-side verification: - -- `./runtests` passed -- `./runtests-hermetic` passed -- with `DD_CIVISIBILITY_ENABLED=1`, the tracer logs showed - `Datadog Tracer v2.7.0-dev.1`, confirming that the pinned target tracer - version was still the one loaded by the test binary - -## Phase 0 Measurement Gates - -The first implementation pass used these measured gates: - -- `compilepkg.compile_synthetic_testmain_source_packages` -- `importcfg.resolve_module_exports_for_packages.go_list_export_deps` -- `builder.stdlib` -- `extensions.go_mod_tidy` - -The target outcomes for this pass were: - -- synthetic testmain cold path: - major reduction -- export resolution: - major reduction -- stdlib: - moderate improvement if the cache-key cleanup also helped there -- extension bootstrap: - observe, but do not optimize yet - -## Measured Builder Hotspots - -The Phase 1/2 verification builder probe log contained `3550` structured -timing lines. - -### Baseline builder phases - -- `builder.compilepkg` for the synthetic testmain action: - `134179 ms` -- `compilepkg.compile_synthetic_testmain_source_packages`: - `132384 ms` -- `importcfg.resolve_module_exports_for_packages.go_list_export_deps`: - `18848 ms` -- `builder.stdlib`: - `52907 ms` -- `extensions.go_mod_tidy`: - `127946 ms` - -### Phase 1/2 verification builder phases - -- `builder.compilepkg` for the synthetic testmain action: - `81538 ms` -- `compilepkg.compile_synthetic_testmain_source_packages`: - `79321 ms` -- `builder.link`: - `3582 ms` -- `builder.stdlib`: - `45097 ms` -- `extensions.go_mod_tidy`: - `98580 ms` - -### Important synthetic testmain sub-phases after Phase 1/2 - -The synthetic testmain path is not slow because the final compile command is -slow. It is slow because a large amount of helper-package preparation happens -before the final compile is attempted. - -Important measured phases from the testmain action: - -- `compilepkg.synthetic_testmain_helper_cache_miss`: - intentionally forced by deleting only the helper-archive bundle -- `compilepkg.synthetic_testmain_helper_decision_cache_hit`: - confirmed reuse of the persisted helper decision graph -- `importcfg.resolve_module_exports_for_packages.cache_hit`: - confirmed reuse of the shared module-export cache -- `importcfg.resolve_module_exports_for_packages.go_list_export_deps`: - not executed on the verification run -- `compilepkg.compile_synthetic_testmain_source_packages`: - `79321 ms` -- final `compilepkg.compile_go_action.run_command` for the main synthetic - `testmain` package: - `1343 ms` - -### Builder Conclusion - -The builder-side bottleneck is dominated by synthetic testmain helper -preparation, not by the final compile or link command. - -The stdlib path is also expensive, but it is clearly smaller than the synthetic -testmain helper path in this measured scenario. - -The important change from this first optimization pass is that the expensive -export-discovery work is no longer being repeated during a forced helper-bundle -rebuild. The remaining cost is mostly real source compilation for the selected -helper closure. - -## Phase 1 And Phase 2 Implementation Result - -The first implementation pass did three concrete things: - -1. It made the cache keys stable across fresh Bazel output bases. -2. It persisted the synthetic helper decision graph separately from the whole - helper-archive bundle. -3. It persisted the resolved module-export map under a shared request-keyed - cache so equivalent helper rebuilds can skip `go list -export -deps`. - -The measured result is: - -- synthetic helper source-package time dropped from `132.4s` to `79.3s` - on the forced helper rebuild path -- that is about a `40.1%` reduction -- synthetic testmain `builder.compilepkg` time dropped from `134.2s` to - `81.5s` -- that is about a `39.2%` reduction -- `importcfg.resolve_module_exports_for_packages.go_list_export_deps` went - from `18.8s` in the baseline run to not executing at all in the verification - run because the shared export cache hit -- `builder.stdlib` improved from `52.9s` to `45.1s` - in the verification run, which is about a `14.8%` reduction - -These numbers do not mean the whole build is solved. They do show that the -Phase 1/2 cache reuse is real and materially changes the synthetic helper -rebuild path. - -## What The Measurements Changed - -This section is partly historical. - -It records the priority order as it looked immediately after the Phase 1/2 -synthetic-helper cache work, before the later bootstrap redesign removed the -tool-side `go mod tidy` path. For the current maintained summary, prefer -[go_orchestrion_maintainer_state.md](./go_orchestrion_maintainer_state.md). - -Before measuring, the likely hot paths were: - -- Orchestrion bootstrap/setup -- stdlib weaving -- synthetic testmain handling -- repeated importcfg rewrites and export discovery - -After the baseline pass, the priority order was: - -1. synthetic testmain helper preparation -2. Orchestrion extension `go mod tidy` -3. stdlib install plus export persistence/sync -4. repeated export discovery through `go list -export -deps` - -After the first implementation pass, the priority order was: - -1. remaining synthetic helper source compilation -2. Orchestrion extension `go mod tidy` -3. stdlib install plus export persistence/sync -4. repeated woven dependency probing - -That means the next pass should continue on the synthetic helper source -compilation logic itself, not go back to export resolution for this path. - -That priority list is only accurate for the pre-no-tool-repin phase of the -investigation. Later work removed the extension-side `go mod tidy` path, so it -is no longer a current hotspot on this branch. - -## First-Pass Answers - -### Why are so many helper packages compiled on a cold miss? - -Because the synthetic testmain path does not stop at the small fixed Datadog -root helper set. - -The flow in -[compilepkg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go) -starts from: - -- `syntheticTestmainRootPackages` -- `orchestrionLinkClosurePackages` - -Then `compileSyntheticTestmainSourcePackages(...)` expands that into a larger -source-compiled closure by: - -- loading dependency metadata with `go list -deps -json` -- recursively classifying packages in - `packageNeedsSyntheticSourceCompile(...)` -- recursively compiling selected packages in - `compileSyntheticTestmainSourcePackage(...)` - -The recursive selection rule is the key detail. A package is pulled into source -compilation when it: - -- is already in the root set -- depends on another package that needs source compilation -- or imports certain stdlib packages such as `flag`, `log`, `log/slog`, - `net/http`, `os`, `os/exec`, or `testing` - -So the cold miss is not “compile a few helper packages”. It becomes “compile a -large transitive non-cgo helper ecosystem that must remain source-compatible in -the synthetic module”. - -That matches the measured run: expensive work included the Datadog roots and -deeper transitive packages such as: - -- `github.com/DataDog/dd-trace-go/v2/ddtrace/mocktracer` -- `github.com/DataDog/datadog-agent/pkg/trace/stats` -- `google.golang.org/protobuf/runtime/protoimpl` - -### Which helper-package results can be reused more aggressively? - -Some reuse already exists, but it is coarse. - -The current persistent helper cache in -`compileSyntheticTestmainSourcePackages(...)` already stores: - -- compiled helper interface archives -- compiled helper link archives -- helper link closures - -That cache is shared across similar actions because its key depends on things -like: - -- configured `dd-trace-go` versions -- SDK path -- install suffix -- woven stdlib key -- Orchestrion version - -What is reused more aggressively after Phase 1/2: - -- package metadata from `loadModulePackageMetadataBatch(...)` -- recursive source-compile decisions from - `packageNeedsSyntheticSourceCompile(...)` -- external export resolution done during a helper-cache miss -- the whole helper bundle when its top-level cache key matches - -What is still not reused aggressively enough today: - -- individual helper package compile results outside the whole-bundle helper - cache -- the remaining real source compilation work inside a forced helper-bundle - rebuild - -So the strongest reuse opportunities are: - -- consider finer-grained per-helper-package reuse if the coarse whole-bundle - cache is still too expensive on misses -- reduce how many packages need to be source-compiled in the first place - -### Can `resolve_module_exports_for_packages` be cached across similar actions? - -Yes. - -The code already has most of the right key material through -`moduleExportRequestKey(...)` in -[importcfg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go). - -That key already includes: - -- helper export cache ABI version -- Orchestrion version identity -- SDK path -- install suffix -- whether the module root is synthetic or real -- digests of `go.mod`, `go.sum`, `orchestrion.tool.go`, and `orchestrion.yml` -- a woven stdlib cache key - -Before this pass, the synthetic cold-miss path did not reuse that aggressively -because it called `resolveModuleExportsForPackagesWithRoot(...)` with a forced -temporary export root under the helper-cache temp directory. That bypassed the -stable shared `module-exports/` cache root used by the normal path. - -Yes, and this pass now proves it in the measured fixture run. - -The verification run intentionally removed only the whole helper-archive bundle -and kept the lower-layer caches warm. The resulting probe lines showed: - -- helper archive cache miss -- helper decision cache hit -- module export cache hit -- no `go list -export -deps` execution for - `resolve_module_exports_for_packages` - -So the direct answer is no longer theoretical. The result now survives across -similar synthetic testmain actions when the shared cache key matches. - -## Historical Next Ranked Optimization Plan - -This was the next ranked plan at that point in the investigation. Later work on -this branch changed the bootstrap design substantially, so the current next -steps are different from the list below. - -### 1. Keep attacking synthetic testmain helper preparation - -This is the clearest builder hotspot. - -Specific focus areas now: - -- reduce the remaining real source compilation inside - `compilepkg.compile_synthetic_testmain_source_packages` -- identify whether some helper packages can be precompiled or reused more - granularly without changing behavior -- investigate whether the selected helper closure can be narrowed safely for - common testmain shapes - -### 2. Reduce extension-side `go mod tidy` cost - -At that time, this was the largest measured startup phase before the Go actions -even began. - -Specific focus areas: - -- understand why `go mod tidy` does so much Git/network work on cold setup -- determine whether the temporary module can be prepared with less work than a - full tidy -- investigate whether a more reusable cache layout can avoid paying this cost - repeatedly in local runs - -This item is historical. The later no-tool-repin change removed that -extension-side module rewrite and tidy path from the current design. - -### 3. Reduce stdlib export persistence and cache sync work - -The stdlib action is not just expensive because of installation. A large amount -of time is spent persisting and copying export data after the install. - -Specific focus areas: - -- `stdlib.persist_orchestrion_stdlib_exports` -- `stdlib.sync_persisted_exports_to_cache` -- `importcfg.resolve_cache_stdlib_exports_at.go_list_export_deps` - -### 4. Reduce repeated woven dependency probing - -Multiple phases repeatedly run `ensure_woven_packages_available` and related -`go list` probes. - -The per-call cost is smaller than the biggest hotspots, but it appears in many -places and may add up significantly across larger builds. - -## Verification Performed - -The first implementation pass was verified with: - -- the baseline fixture run in `../rules_test_optimization_tests` -- a seed run that populated the new caches -- a verification run that deleted only the helper-archive bundle and confirmed - lower-layer cache reuse -- focused builder tests in this repo: - `./bazelw test @rules_go//go/tools/builders:{compilepkg_test,importcfg_test,probe_test,orchestrion_test}` - -All of those checks passed locally. - -## Historical Practical Next Steps - -These were the practical next steps before the later bootstrap redesign. - -The next optimization pass should start with the synthetic testmain path in: - -- `go/tools/builders/compilepkg.go` -- `go/tools/builders/importcfg.go` - -The first concrete questions to answer are: - -- why are so many helper packages being compiled on a cold miss? -- which of those helper package results can be reused more aggressively? -- can the `resolve_module_exports_for_packages` result be cached in a way that - survives across similar testmain actions? - -The second optimization pass should then focus on: - -- `go/private/orchestrion/extensions.bzl` -- `go/tools/builders/stdlib.go` -- `go/tools/builders/orchestrion.go` - -## Related References - -- [docs/rules_go_orchestrion_performance_analysis.md](./rules_go_orchestrion_performance_analysis.md) -- [third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/compilepkg.go) -- [third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go](../third_party/rgo/v0_60_0/base/go/tools/builders/importcfg.go) -- [third_party/rgo/v0_60_0/base/go/tools/builders/stdlib.go](../third_party/rgo/v0_60_0/base/go/tools/builders/stdlib.go) -- [third_party/rgo/v0_60_0/base/go/tools/builders/orchestrion.go](../third_party/rgo/v0_60_0/base/go/tools/builders/orchestrion.go) -- [third_party/rgo/v0_60_0/base/go/private/orchestrion/extensions.bzl](../third_party/rgo/v0_60_0/base/go/private/orchestrion/extensions.bzl) diff --git a/docs/rules_go_orchestrion_support_selection.md b/docs/rules_go_orchestrion_support_selection.md deleted file mode 100644 index 5ecd6b97..00000000 --- a/docs/rules_go_orchestrion_support_selection.md +++ /dev/null @@ -1,81 +0,0 @@ - - -# rules_go Orchestrion support selection - -This repository exposes one supported public `rules_go` tree variant, `base`. -Consumers that already own a private `rules_go` patch stack can also consume a -generated sparse patch profile as an input to a local rebase or merge inside -their own repository. - -## Published variant - -- `base`: upstream `rules_go` plus the generic Orchestrion integration and - correctness fixes maintained by this repository. - -The base variant is a complete repository root. A consumer should point -`rules_go`, `io_bazel_rules_go`, or its equivalent repository name directly at -this subtree when it does not already maintain its own `rules_go` patch stack. - -The default `rules_go_upstream` is currently `v0_60_0`, which preserves the -existing `third_party/rgo/v0_60_0/base` path. When multiple upstream -`rules_go` versions are supported, use `rules_go_upstream` to choose the upstream -support line. Omitting `rules_go_upstream` preserves the repository default. -The registry currently supports `v0_60_0`, `v0_61_1`, `v0_62_0`, and -`v0_63_0`. - -## Selection rule - -- Use `base` for normal WORKSPACE and Bzlmod consumers. -- Use a generated consumer patch profile only when the consuming repository - already applies its own private `rules_go` patch stack and needs to preserve - that ownership model. - -Public consumer patch profiles live under -`third_party/rules_go_orchestrion/profiles/` and are verified by: - -```bash -python3 tools/dev/verify_rules_go_profiles.py --public-denylist tools/dev/private_leak_public_denylist.txt -``` - -## Maintainer workflow - -Maintainers track each supported upstream version with both: - -- patch series under `third_party/rules_go_orchestrion/patches//` -- materialized base trees under the registry-selected `tree_path` - -The patch series is the maintainer source for rebasing. The materialized tree is -the consumer artifact. CI verifies that they match. - -When the generic Orchestrion integration changes, update the base tree for the -target upstream, regenerate the maintainer patch series, and verify that the -consumer patch profiles still round-trip against clean upstream `rules_go`. - -After any variant change, regenerate the upstream delta reports: - -```bash -python3 tools/dev/diff_rules_go_fork.py --all --write-report -python3 tools/dev/materialize_rules_go_fork.py check --all -python3 tools/dev/verify_rules_go_profiles.py --public-denylist tools/dev/private_leak_public_denylist.txt -``` - -The verifier runs two isolated ordinary and Test Optimization builds for every -registered upstream. It compares `GoStdlib`, synthetic helper, synthetic -`~testmain.a`, and `GoLink` action keys and bytes, and rejects an instrumented -stdlib cache in ordinary mode. Local compact execution-log inspection requires -`zstd`. - -Run the smoke lane before publishing: - -```bash -RULES_GO_UPSTREAM=v0_60_0 RULES_GO_VARIANT=base tools/dev/run_rules_go_variant_smoke.sh -``` - -When adding a new upstream, run the same smoke commands with -`RULES_GO_UPSTREAM=`. diff --git a/docs/rules_go_stdlib_cache_determinism_plan.md b/docs/rules_go_stdlib_cache_determinism_plan.md deleted file mode 100644 index 241c5134..00000000 --- a/docs/rules_go_stdlib_cache_determinism_plan.md +++ /dev/null @@ -1,814 +0,0 @@ - - -# Deterministic rules_go build-output tracker - -## Status - -Implementation is in progress on PR #251. The stdlib ownership fix and the -isolated action-output verifier are implemented locally; publication and -downstream adoption remain open until the PR is merged and consumers are -repinned. This document now tracks that work instead of describing an -unimplemented proposal. - -The work starts in `rules_test_optimization`, is validated through -`rules_test_optimization_tests`, and is adopted by `dd-source` only after a -published rules commit is available. The source changes must cover every -maintained `rules_go` profile: - -- `v0_60_0` -- `v0_61_1` -- `v0_62_0` -- `v0_63_0` - -It must preserve both supported Go execution modes: - -- ordinary Go builds and tests without Test Optimization; -- Go builds and tests with Orchestrion in `test_optimization` mode. - -Generic Orchestrion behavior must remain compatible as well, even though the -cross-repository end-to-end acceptance matrix is focused on the two consumer -modes above. - -## Objective - -Make the `GoStdlib` action output deterministic without losing the woven -standard-library archives required by Test Optimization. - -The implementation must separate two concepts that currently overlap: - -1. a writable, action-private Go build cache used by live `go install` and - `go list -export -deps` subprocesses; and -2. the Bazel-declared `-cacheout` TreeArtifact consumed by later Orchestrion - compile and link actions. - -No Go subprocess may use the declared TreeArtifact as its live `GOCACHE`. -Instead, Test Optimization must explicitly publish only the deterministic -archive data required by downstream actions. Ordinary Go builds must still -create the declared TreeArtifact, but leave it empty. - -## Problem statement - -The Go build cache is not a reproducible Bazel action output. In addition to -archive data entries (`*-d`), the Go tool writes action index entries (`*-a`) -whose bytes include a wall-clock timestamp. Re-running the same command with -identical sources can therefore produce different cache bytes. - -The current `GoStdlib` integration allows that mutable cache format to become a -Bazel-declared output in two places: - -1. `stdlib.go` selects `-cacheout` as `GOCACHE` when the flag is present. -2. `env_orchestrion.go:newBufferedCommand` overwrites a subprocess's `GOCACHE` - with `env.stdlibCache`, which is the declared `-cacheout` directory. - -There is a third indirect writer. During publication, -`syncPersistedOrchestrionExportsToCache` calls -`resolveCacheStdlibExportsAt` for both the declared cache and the current -environment cache. That resolver executes `go list -export -deps` against the -selected cache root. Running it against the declared root creates the same -non-deterministic Go cache metadata even if the original stdlib build used a -private cache. - -The downstream `dd-source` patch -`third_party/rules_go/0019-Keep-stdlib-GOCACHE-off-the-declared-cacheout-output.patch` -correctly identifies the first writer and moves the primary stdlib build to a -private cache. Porting that patch literally is insufficient for this repository -because the two Orchestrion-specific writers would remain. - -## Terminology and data flow - -This plan uses these names consistently: - -- **scratch cache**: the private, writable `GOCACHE` rooted at - `/.gocache`. It may contain any files produced by Go and is - deleted when the `GoStdlib` action finishes. -- **declared cache**: the directory supplied by Bazel through `-cacheout` and - recorded as `env.stdlibCache`. It is a TreeArtifact and must contain only - deterministic published files. -- **persisted stdlib exports**: the woven archives already copied under the - replicated GOROOT output and recorded by the existing persisted-export - manifest. -- **published cache exports**: the selected woven archives projected into the - declared cache using their Go cache data-entry paths, plus - `.orchestrion_stdlib_cache_manifest`. -- **consumer cache**: a later action's writable cache. It can be seeded from the - declared cache, but it is never the declared cache itself. - -The intended flow is: - -```text -GoStdlib action - | - +-- live go install / go list - | GOCACHE=/.gocache - | produces mutable *-a and data *-d entries - | - +-- Orchestrion persistence - | produces woven archives under the replicated GOROOT - | - +-- deterministic publisher - reads archive locations from the scratch cache - copies selected woven bytes to equivalent *-d paths in -cacheout - writes a sorted .orchestrion_stdlib_cache_manifest - -Later compile/link action - | - +-- RULES_GO_ORCHESTRION_STDLIB_CACHE= - +-- GOCACHE= - +-- seedWovenStdlibCache copies/hard-links declared archives into GOCACHE -``` - -## Required invariants - -The implementation is complete only if all of the following remain true. - -### Cache ownership - -- `GOCACHE` and `env.stdlibCache` never refer to the same directory. -- Every `go install`, `go list`, or Orchestrion subprocess receives a writable - scratch or consumer cache through `GOCACHE`. -- `RULES_GO_ORCHESTRION_STDLIB_CACHE` may point to `env.stdlibCache`, but treats - it as a read-only source of published archives. -- The scratch cache is removed at the end of the stdlib action on success and - failure. -- Bazel's declared cache directory exists whenever `-cacheout` is provided, - including ordinary non-Orchestrion builds. - -### Declared output contents - -- Without Orchestrion, the declared cache is empty. -- With Test Optimization, the declared cache contains only: - - selected woven archive data entries at stable relative `*-d` paths; and - - `.orchestrion_stdlib_cache_manifest`. -- The declared cache contains no `*-a` action index entries, `trim.txt`, Go - cache README files, temporary files, lock files, or unrelated cache-prefix - siblings. -- Manifest records are sorted deterministically and contain relative paths. -- Two isolated executions with identical inputs produce the same set of paths - and identical bytes for every declared output file. - -### Runtime correctness - -- Ordinary Go targets compile, link, and test without Test Optimization. -- Test Optimization still weaves the required stdlib closure. -- Synthetic test-main and final link actions can find the published woven - archives. -- A real Go test emits Test Optimization test and telemetry payloads. A green - build without payloads is a failure, not successful validation. -- Existing partial-result, doctor, enrichment-validation, and upload behavior - remains unchanged; this fix is confined to stdlib cache production and - consumption. - -## Scope - -### In scope - -- The base `rules_go` trees for all four registered upstreams. -- Builder unit tests covering cache ownership, publication, manifest contents, - path safety, and command environments. -- Generated public patch profiles, metadata, and changed-files reports. -- The generated-profile functional smoke in - `tools/dev/verify_rules_go_profiles.py`. -- Consumer validation in `rules_test_optimization_tests` for every maintained - upstream in both disabled/plain and Test Optimization modes. -- Adoption in `dd-source`, including removal of its temporary `0019` overlay, - after the rules change has been published and pinned. - -### Out of scope - -- Replacing the Go build cache implementation. -- Persisting or remotely sharing the entire Go `GOCACHE`. -- Changing the public Test Optimization configuration or onboarding API. -- Adding another user-facing Orchestrion mode. -- Parallelizing doctor or payload upload work. -- Changing which stdlib packages are woven, except where required to preserve - the existing closure while changing cache storage. -- Refactoring unrelated Orchestrion cache, jobserver, resolver, uploader, or - payload code. - -## Design decisions - -### D-1: Fix the base trees, then regenerate - -The three directories under `third_party/rgo/*/base` are the editable source of -truth. Generated files under `third_party/rules_go_orchestrion/patches` must not -be hand-edited. After the base implementations and tests agree, use the -repository generators once to refresh all derived artifacts. - -This is especially important on the current PR branch because generated patch -files already contain pending review fixes. Regenerating once at the end avoids -overlapping generated churn and preserves those changes. - -### D-2: Keep the current Go-cache-shaped archive layout - -Published archives will retain their cache-relative `*-d` paths. Existing -manifest readers and cache-seeding logic already understand this format, so it -is the smallest compatible change. A new custom flat archive layout would -increase the diff and require more consumer rewrites without improving the -determinism guarantee. - -### D-3: Resolve layout only against writable scratch - -`go list -export -deps` remains the authority for locating Go cache archive -paths, but it runs only against the scratch cache during stdlib publication. -The resulting path for each package is made relative to the scratch root and -projected into the declared root. The resolver must never run with the declared -root as `GOCACHE`. - -### D-4: Publish an allowlist, not a copied cache tree - -The publisher copies the existing woven archive bytes for the selected package -set. It does not recursively copy a Go cache directory or a two-character cache -prefix. This prevents an adjacent `*-a`, metadata file, or unrelated data entry -from entering the declared TreeArtifact. - -### D-5: Treat path containment as a correctness boundary - -For every projected destination: - -1. compute the source archive's path relative to the absolute scratch root; -2. reject absolute relative results; -3. reject `..` and any path whose cleaned form escapes the root; -4. join the validated relative path to the declared root; -5. verify the resulting destination is still contained by the declared root. - -The implementation must use `filepath` semantics so the same validation works -on Linux, macOS, and Windows. - -### D-6: Prove determinism from bytes, not filesystem timestamps - -The verifier compares a canonical inventory of relative path, file type, size, -and SHA-256 content digest. Filesystem mtimes are not part of the TreeArtifact's -semantic content and must not be used to create a false failure. Symlinks, if -any unexpectedly appear, must be reported rather than silently followed. - -## Implementation plan - -### S-1: Introduce explicit cache setup in `stdlib.go` - -Apply the same logical change in: - -- `third_party/rgo/v0_60_0/base/go/tools/builders/stdlib.go` -- `third_party/rgo/v0_61_1/base/go/tools/builders/stdlib.go` -- `third_party/rgo/v0_62_0/base/go/tools/builders/stdlib.go` - -In `stdlib`, immediately after the replicated GOROOT is selected: - -1. Always define `cachePath` as `filepath.Join(output, ".gocache")`. -2. If `-cacheout` is non-empty, store `abs(*cacheOut)` in - `goenv.stdlibCache`; do not assign it to `cachePath`. -3. Set process `GOCACHE` to the scratch `cachePath`. -4. Create the scratch directory and register unconditional deferred removal. -5. If `goenv.stdlibCache` is non-empty, create that directory separately so - the declared TreeArtifact exists even when the plain path publishes no - files. -6. Remove `shouldRemoveStdlibCache`; cache ownership no longer depends on - Orchestrion or on the presence of `-cacheout`. - -Errors must identify whether scratch preparation or declared-output -preparation failed and include the affected path. - -Expected behavior after S-1 alone: - -- plain stdlib builds no longer write live cache data into `-cacheout`; -- Orchestrion has access to both roots through separate fields/environment; -- publication is not yet safe until S-2 and S-3 are complete. - -### S-2: Stop Orchestrion commands from overriding `GOCACHE` - -Apply the same logical change to each version's -`go/tools/builders/env_orchestrion.go`. - -Change `env.newBufferedCommand` so it: - -- preserves the `GOCACHE` already present in `os.Environ()`; -- sets `RULES_GO_ORCHESTRION_STDLIB_CACHE` when `env.stdlibCache` exists and is - a directory; -- never assigns `env.stdlibCache` to `GOCACHE`. - -Update the function comment to describe the declared cache as an archive -source, not a cache override. Do not duplicate scratch-cache creation here: -stdlib setup and the existing command-environment helpers already own writable -cache selection. - -Audit all direct uses of `env.stdlibCache` in the builder package. Classify each -one as either: - -- a read of the declared manifest/archive source; or -- an accidental attempt to use it as a writable Go cache. - -Only the latter are changed. In particular, preserve manifest readers and -`seedWovenStdlibCache`, which intentionally copy declared archives into a -different writable cache. - -### S-3: Make stdlib publication one-way and deterministic - -Refactor `syncPersistedOrchestrionExportsToCache` in each version's -`stdlib.go`. - -The new algorithm is: - -1. Return early when there is no environment, no persisted export, no selected - root, or no declared cache. -2. Read the current absolute scratch cache from `GOCACHE`. Reject an empty - value or an alias with the declared cache. -3. Create the declared cache directory without changing process `GOCACHE`. -4. Call `resolveCacheStdlibExportsAt(goenv, roots, scratchCache)` exactly once. -5. Sort the resolved package names. -6. For each selected package: - - require a persisted woven source archive; - - require a resolved scratch data-entry path; - - derive and validate its cache-relative destination; - - require the destination to be a data entry rather than an action index; - - copy the persisted woven archive bytes to that destination under the - declared cache; - - append `package=relative/path` to the manifest. -7. Write `.orchestrion_stdlib_cache_manifest` atomically after all archives - have been copied successfully. - -Prefer a small helper for safe path projection, for example a function that -takes `(scratchRoot, declaredRoot, scratchArchive)` and returns a validated -destination and relative manifest path. Keep it private to the builder package. - -The publisher must not: - -- build a `candidateCaches` list; -- temporarily set `GOCACHE` to the declared cache; -- call `resolveCacheStdlibExportsAt` against the declared cache; -- copy a whole Go cache prefix directory into the declared cache; -- include a manifest record for a package whose archive was not published. - -If later code in the same stdlib action needs the woven archive in scratch, it -may replace the corresponding scratch `*-d` entry with the woven bytes as a -separate operation. That mutable update remains inside scratch and is not a -substitute for explicit publication. - -### S-4: Preserve downstream manifest and seeding behavior - -Review, and change only if a test proves it necessary: - -- `readStdlibCacheManifest` -- `readAllStdlibCacheManifest` -- `currentWovenStdlibCacheKey` -- `seedWovenStdlibCache` -- `resolveCacheStdlibExportsAt` -- importcfg rewrite helpers that consume `goenv.stdlibCache` - -The desired downstream contract is: - -- manifest reads never invoke Go or mutate the declared cache; -- `seedWovenStdlibCache` resolves destinations against its writable - `cacheRoot`, then copies or hard-links source bytes from the declared cache; -- a consumer that receives `RULES_GO_ORCHESTRION_STDLIB_CACHE` can populate its - own `GOCACHE` without modifying the TreeArtifact; -- generic Orchestrion and Test Optimization continue to use their existing - package selection logic. - -Do not make `resolveCacheStdlibExportsAt` globally read-only. It is still valid -for ephemeral writable caches used by consumers. The caller, not the resolver, -must enforce that the declared cache is never passed as its writable root. - -### S-5: Add builder unit tests - -The existing builder test target includes `stdlib_test.go` in -`@rules_go//go/tools/builders:orchestrion_test`. Add the tests to the base trees -and keep the test sources identical across all four versions where the -production sources are identical. - -#### Cache setup and ownership tests - -- Replace `TestShouldRemoveStdlibCache` with tests for the new cache-selection - helper or directly test the smallest extracted setup seam. -- With no `-cacheout`, verify scratch is `/.gocache` and scheduled for - removal. -- With `-cacheout`, verify scratch remains `/.gocache`, declared cache - is stored separately, both directories exist, and the paths do not alias. -- Cover relative inputs normalized to absolute paths. - -Avoid testing deferred filesystem cleanup through the complete `stdlib` -command if a small ownership helper can prove the same contract more reliably. - -#### Command environment tests - -Add a focused test for `newBufferedCommand`: - -- set a known private `GOCACHE` in the process environment; -- set `env.stdlibCache` to a distinct existing directory; -- construct the command without executing it; -- assert its `GOCACHE` is still the private value; -- assert `RULES_GO_ORCHESTRION_STDLIB_CACHE` is the declared value; -- verify an absent or invalid declared directory does not replace `GOCACHE`. - -#### Publisher determinism tests - -Construct two fake scratch cache roots with: - -- identical selected `*-d` relative paths; -- identical persisted woven archive bytes; -- different `*-a` contents that model Go's timestamped index records; -- different unrelated metadata or prefix siblings. - -Publish each into a fresh declared directory and assert: - -- the canonical path-and-content inventories are identical; -- the expected `*-d` archives contain the woven bytes; -- no `*-a`, metadata, temporary, or unrelated sibling file is present; -- the manifest contains only successfully published packages; -- records and relative paths are identical and sorted. - -Where invoking the real resolver would make the unit test depend on a host Go -cache, factor the copying/projection portion into a deterministic helper and -test it with an explicit package-to-scratch-path map. Existing integration -tests will cover the real resolver. - -#### Negative tests - -- Reject a resolved path outside the scratch root. -- Reject an absolute or `..`-escaping manifest projection. -- Reject an action-index (`*-a`) source where a data entry is required. -- Return a useful error when a selected package has no persisted woven archive. -- Do not leave a complete-looking manifest after a failed publication. - -#### Existing regression tests - -Keep existing tests for: - -- persisted stdlib export installation; -- manifest reads; -- seeding a writable cache; -- importcfg rewrites; -- the standard-library package closure in `general` and - `test_optimization` modes. - -### S-6: Apply the implementation to every maintained upstream - -At the start of implementation, compare the relevant source files across the -four bases. They are currently byte-identical for `stdlib.go`, -`stdlib_test.go`, `importcfg.go`, and `env_orchestrion.go`, so the preferred -workflow is: - -1. implement and review the change in `v0_60_0`; -2. copy the exact logical change to `v0_61_1`, `v0_62_0`, and `v0_63_0`; -3. compare the resulting files or focused diffs across versions; -4. retain a version-specific difference only when the upstream base requires - it and document the reason in the diff. - -Do not assume future equality: re-run the comparison immediately before -propagating the change. - -### S-7: Extend generated-profile functional verification - -Extend the existing functional smoke in -`tools/dev/verify_rules_go_profiles.py`; do not create a parallel profile -verifier. - -For each registered upstream, the verifier materializes a pristine upstream -tree, applies the generated public patch, creates a temporary WORKSPACE -consumer, executes a Test Optimization test, and inspects structured Bazel -output. Its determinism phase runs twice with isolated output roots and reads -the compact execution logs. Local runs require `zstd`. - -The comparison covers the full Reprise-sensitive chain: `GoStdlib`, -`GoSyntheticTestmainHelpers`, the `GoCompilePkg` action that produces -`~testmain.a`, and `GoLink`. The verifier compares both action keys and output -bytes. Different bytes fail the check; equal bytes with a different key -identify an unnecessary rebuild. - -#### Plain mode - -1. Build or test the Go target without the Orchestrion flags. -2. Identify the `GoStdlib` action and its declared `gocache` TreeArtifact. -3. Record a canonical inventory of the output. -4. Force a second execution with identical source inputs but without reusing - the first `GoStdlib` action result. -5. Record the second inventory and compare it with the first. -6. Assert both declared cache inventories are empty. - -#### Test Optimization mode - -1. Run with: - - ```text - --@io_bazel_rules_go//go/private/orchestrion:enabled=true - --@io_bazel_rules_go//go/private/orchestrion:mode=test_optimization - ``` - -2. Capture the `GoStdlib` declared cache inventory. -3. Force the same action to execute again in an isolated output state. -4. Compare every relative path and file digest. -5. Require a non-empty sorted manifest and its referenced archives. -6. Reject `*-a`, `trim.txt`, cache metadata, symlinks, and unmanifested files. -7. Preserve the existing `aquery` assertions proving that the generated patch - actually enabled Test Optimization. -8. Preserve the real test execution proving that the woven stdlib can be - consumed. -9. Exercise CGO so the woven stdlib, compiler random seed, relative input - paths, synthetic source trim paths, and copied helper build IDs are covered. - -The implementation may use two isolated Bazel output roots or an explicit -action-cache invalidation mechanism. It must not compare one execution with a -cache hit from that same execution. Disable remote and disk cache reuse for this -specific determinism check so both inventories come from executed actions. - -Use `aquery --output=jsonproto` or another structured Bazel output to locate -the action outputs. Do not depend on a hard-coded configuration hash in a -`bazel-out` path. - -#### Functional payload assertion - -The generated-profile smoke currently proves the test and action shape. If it -cannot observe Test Optimization payloads directly, add the smallest fixture -assertion that the test created the expected test and telemetry output files. -Keep full doctor/uploader validation in the consumer repository, where those -targets already exist. - -### S-8: Regenerate public profiles and metadata - -After all base files and focused tests are stable, regenerate and inspect the -derived outputs: - -```bash -python3 tools/dev/diff_rules_go_fork.py --all --write-report -python3 tools/dev/generate_rules_go_fork_maps.py --check -python3 tools/dev/materialize_rules_go_fork.py check --all -python3 tools/dev/verify_rules_go_profiles.py \ - --public-denylist tools/dev/private_leak_public_denylist.txt -python3 tools/dev/check_release_archive_contents.py -``` - -Expected generated changes include the matching -`third_party/rules_go_orchestrion/patches//base/0001-full-delta.patch` -files and any changed-files or metadata output owned by the generator. Inspect -every generated diff to ensure it contains the cache fix and the already -intended PR changes, without private paths or unrelated churn. - -If a generator modifies a base source file, stop and understand why before -continuing; the base source implementation must remain the reviewed source of -truth. - -## Validation matrix - -The minimum acceptance matrix is: - -| rules_go | Plain Go | Test Optimization | Declared-cache bytes | Payload proof | -| --- | --- | --- | --- | --- | -| v0.60.0 | required | required | two isolated runs | required | -| v0.61.1 | required | required | two isolated runs | required | -| v0.62.0 | required | required | two isolated runs | required | -| v0.63.0 | required | required | two isolated runs | required | - -For every row: - -- plain Go must compile and test without enabling Orchestrion; -- Test Optimization must compile, link, execute, and emit payloads; -- the declared cache must satisfy the file allowlist; -- two independently executed `GoStdlib` actions must produce identical - canonical inventories; -- a subsequent unchanged consumer run must be eligible for a Bazel cache hit. - -### Focused repository validation - -Before the full repository suite, run the smallest relevant builder and Python -tooling tests. Resolve exact labels with `bazel query` if necessary rather than -guessing a target that is not visible from the root module. - -Expected focused coverage includes: - -```bash -./bazelw test @rules_go//go/tools/builders:orchestrion_test \ - --noexperimental_split_xml_generation -./bazelw test @rules_go//go/tools/builders:importcfg_test \ - --noexperimental_split_xml_generation -./bazelw test //tools/tests/python:python_tools_test \ - --noexperimental_split_xml_generation -``` - -Then run: - -```bash -./bazelw test //... --noexperimental_split_xml_generation -``` - -On macOS, use the repository's documented Bazel wrapper and macOS validation -procedure. Do not introduce a source-level workaround for a local Bazel test -runner issue. - -### Consumer validation in `rules_test_optimization_tests` - -Before publishing, validate the local rule checkout through the sibling -consumer repository. Enable the existing local overrides for: - -- `datadog-rules-test-optimization`; -- `datadog-rules-test-optimization-go`; -- the selected `rules_go` base tree. - -Exercise all four upstreams using the existing fixture support: - -```bash -for rules_go_upstream in v0_60_0 v0_61_1 v0_62_0 v0_63_0; do - RULES_GO_UPSTREAM="$rules_go_upstream" RTO_LOCAL_ARCHIVE=1 \ - ./fixtures/bzlmod-go/runtests -done -``` - -Use the repository's documented WORKSPACE and hermetic variants as required by -the canonical matrix. For each upstream, validate both phases explicitly: - -1. disabled/plain run without `--config=test-optimization`; -2. enabled run with `--config=test-optimization`; -3. doctor; -4. uploader enrichment validation using the current one-pass uploader - contract; -5. actual upload only where the CI workflow is already authorized to upload; -6. a second unchanged run to observe Bazel cache reuse. - -The local run must use Go 1.25.0, reset long-lived Bazel state when diagnosing a -discrepancy, and clear the stable Orchestrion cache when the test specifically -needs a cold Orchestrion execution. Do not clear caches between the first and -second runs whose purpose is to prove cache reuse. - -## Cross-repository rollout - -### R-1: Publish `rules_test_optimization` - -Only after the complete rules and consumer matrices pass: - -1. inspect the final diff and generated artifacts; -2. commit only the intended PR #214 files; -3. push the current PR branch; -4. record the exact remote commit SHA; -5. allow PR CI to verify Linux, macOS, and Windows behavior. - -Publishing is a separate authorized phase. Implementation and local validation -do not themselves authorize a commit or push. - -### R-2: Repin `rules_test_optimization_tests` - -Update PR #108 to the exact published rules SHA using its maintained refresh -tooling, including every generated fixture pin and hash. Run the full -multi-version consumer matrix from the pinned commit, not from a local override, -before considering the repin complete. - -### R-3: Adopt in `dd-source` - -After PR #108 proves the published artifact: - -1. update dd-source's rules/Test Optimization pin to the exact rules SHA; -2. regenerate dd-source's composed `rules_go` patch so the deterministic cache - fix is already present in its generated rules patch; -3. remove - `third_party/rules_go/0019-Keep-stdlib-GOCACHE-off-the-declared-cacheout-output.patch`; -4. remove the corresponding patch-list entry from `WORKSPACE`; -5. remove the `0019` section from `third_party/rules_go/README.md`; -6. verify there is no second application of the same source hunk. - -Validate dd-source in both modes: - -- a representative ordinary Go target without Test Optimization; -- the Test Optimization pilot target with - `--config=test-optimization`; -- the existing Reprise determinism check for `GoStdlib` outputs; -- the Test Optimization load job, including a fresh run and an unchanged - cached run; -- the existing Bazel cache hydration flow after the generated patch and pin are - final. - -For the Test Optimization run, require test and telemetry payload counts and -successful doctor/enrichment/upload stages. A Reprise success from a plain Go -target alone does not prove the Orchestrion path. - -## Failure handling and rollback - -### During Rule implementation - -- If plain mode is deterministic but Test Optimization is not, inspect the - declared tree for a remaining writer before changing the output comparison. -- If the declared tree is deterministic but payloads disappear, revert the - consumer-side cache-read change and inspect manifest/seeding behavior. Do not - accept build-only success. -- If only one upstream fails, compare its pristine upstream source and - generated patch with the other bases before adding a version-specific branch. -- If Windows path tests fail, fix path normalization and containment logic; do - not disable the negative test on Windows. - -### During downstream adoption - -- Keep dd-source's `0019` until the new rules commit has been successfully - pinned and its composed patch is verified to contain the replacement fix. -- If the repinned dd-source patch cannot apply, restore the previous exact rules - pin and retain `0019`; do not partially combine old and new hunks. -- If CI exposes a regression after publication, roll back by exact pin rather - than adding another overlay before determining whether the failure is in - Rule generation, consumer integration, or dd-source composition. - -## Risks and mitigations - -### Silent loss of instrumentation - -**Risk:** compile and link succeed while downstream actions read plain stdlib -archives. - -**Mitigation:** require real payload emission plus doctor/enrichment validation; -builder unit tests and `aquery` output alone are insufficient. - -### A hidden declared-cache writer remains - -**Risk:** another helper passes `env.stdlibCache` as `GOCACHE` and reintroduces -timestamped index files. - -**Mitigation:** audit every `env.stdlibCache`, `GOCACHE`, and -`resolveCacheStdlibExportsAt` call site; assert the output allowlist in both unit -and generated-profile tests. - -### Accidental cache-prefix copying - -**Risk:** copying siblings of a selected data archive includes an action index -or unrelated package entry. - -**Mitigation:** publish only explicit selected archive paths and verify that -every output except the manifest is referenced by the manifest. - -### Path escape or platform-specific path handling - -**Risk:** relative projection produces a destination outside the declared root, -especially with Windows volume or separator semantics. - -**Mitigation:** centralized `filepath`-based containment validation with Linux, -macOS, and Windows CI coverage and explicit negative unit tests. - -### False determinism from Bazel cache reuse - -**Risk:** the verifier compares a newly executed action with a Bazel cache hit -of the same output. - -**Mitigation:** use isolated output/action-cache state for both runs and record -evidence that `GoStdlib` executed twice. - -### Excessive stdlib preparation time - -**Risk:** resolving exports once per package or per destination cache increases -the already expensive preparation phase. - -**Mitigation:** resolve the complete selected package set once against scratch, -sort once, and copy once. Add no new `go list` invocation per package. - -### Generated patch drift on the active PR - -**Risk:** regenerating while review fixes are pending loses or duplicates -changes already present in generated profiles. - -**Mitigation:** modify base sources first, keep existing user changes intact, -then run the canonical generators once and inspect the combined generated diff. - -## Completion criteria - -The work is complete only when: - -- all four base profiles implement the same cache ownership contract; -- no live Go or Orchestrion subprocess receives the declared cache as - `GOCACHE`; -- plain Go declared cache outputs are empty and reproducible; -- Test Optimization declared cache outputs contain only deterministic - manifested archive data; -- two isolated executions per mode and upstream have identical path-and-byte - inventories; -- builder unit tests, profile materialization, profile verification, release - archive checks, and the full rules suite pass; -- local and published consumer fixtures pass for `v0.60.0`, `v0.61.1`, - `v0.62.0`, and `v0.63.0`; -- real Test Optimization tests emit valid test and telemetry payloads; -- an unchanged second consumer run demonstrates Bazel cache reuse; -- PR #108 is pinned to the final rules SHA and passes its CI matrix; -- dd-source is pinned to that same rules version, passes both plain and Test - Optimization validation, and no longer carries the standalone `0019` patch; -- no temporary overrides, scratch artifacts, or unrelated generated changes - remain in any of the three repositories. - -## Execution checklist - -- [x] Confirm the active Rule branch and preserve all existing review changes. -- [x] Recompare relevant builder sources across all four bases. -- [x] Implement private scratch and separate declared-cache setup. -- [x] Remove the Orchestrion `GOCACHE=env.stdlibCache` override. -- [x] Implement safe deterministic archive publication. -- [x] Audit every declared-cache read and write call site. -- [x] Add cache ownership and command-environment tests. -- [x] Add deterministic publisher and path-safety tests. -- [x] Run focused builder tests. -- [x] Propagate and compare the implementation across all upstreams. -- [x] Extend generated-profile verification to the four Reprise action families. -- [x] Regenerate profiles, metadata, and changed-files reports. -- [x] Run materialization, verifier, release, and full-suite checks. -- [x] Validate local consumer fixtures across all versions and both modes. -- [ ] Publish the Rule commit when explicitly authorized. -- [ ] Repin and validate PR #108 from the published commit. -- [ ] Repin dd-source and regenerate its composed rules_go patch. -- [ ] Remove dd-source `0019` and its WORKSPACE/README references. -- [ ] Validate dd-source plain, Test Optimization, Reprise, load, and cache - hydration flows. -- [ ] Inspect final diffs and confirm all temporary local wiring is removed. diff --git a/docs/rules_go_variant_maintenance_guide.md b/docs/rules_go_variant_maintenance_guide.md deleted file mode 100644 index c93605f0..00000000 --- a/docs/rules_go_variant_maintenance_guide.md +++ /dev/null @@ -1,287 +0,0 @@ - - -# rules_go Orchestrion support folder guide - -This guide describes the maintained folder layout for the vendored -Orchestrion-enabled `rules_go` support lines and public consumer patch profiles. - -## Folders - -### `third_party/rgo//base/` - -The public base tree for one supported upstream. For example, the current -default upstream uses `third_party/rgo/v0_60_0/base`, and the additional -support lines use `third_party/rgo/v0_61_1/base`, -`third_party/rgo/v0_62_0/base`, and `third_party/rgo/v0_63_0/base`. Each tree -contains clean upstream `rules_go` plus the generic Orchestrion support -maintained by this repository. Bugs in our integration are fixed in the -affected materialized tree, then the matching patch series is regenerated from -that tree. - -### `third_party/rules_go_orchestrion/` - -The registry-driven support area for multi-version maintenance: - -- `registry.json` selects supported upstream lines and base tree paths. -- `patches//` stores the maintainer patch series for rebasing. -- `profiles/.json` declares public sparse-patch profiles for - consumers that need to apply this repository's `rules_go` changes on top of - a repository-owned patch stack. -- `patches//base.series` lists the patch files that recreate the - public base tree for that upstream. - -The default `rules_go_upstream` is currently `v0_60_0`, which preserves the -existing `third_party/rgo/v0_60_0/base` path. When multiple upstream -`rules_go` versions are supported, use `rules_go_upstream` to choose the upstream -support line. Omitting `rules_go_upstream` preserves the repository default. - -Materialized base trees always live at the registry-selected `tree_path`. The -current convention is `third_party/rgo//base`; do not add new -`third_party/rules_go_orchestrion/versions/...` trees. - -### `tools/tests/rules_go_variant_regressions/` - -Maintainer-only proof fixtures copied into temporary variant trees by the smoke -and extended scripts. These files are not part of the published base tree or -generated public consumer patch profiles. - -## Metadata - -- `third_party/rgo//base.METADATA.json` -- `third_party/rgo//base.CHANGED_FILES.md` -- `third_party/rules_go_orchestrion/registry.json` -- `third_party/rules_go_orchestrion/profiles/.json` -- `third_party/rules_go_orchestrion/patches//` - -Use `tools/dev/diff_rules_go_fork.py` to regenerate each changed-files report. -Use `tools/dev/materialize_rules_go_fork.py check --all` to verify that patch -series recreate the checked-in trees. Use -`tools/dev/verify_rules_go_profiles.py --public-denylist tools/dev/private_leak_public_denylist.txt` -to verify that public consumer patch profiles round-trip against clean upstream -`rules_go` without leaking private-only strings. The verifier also rebuilds -ordinary and Test Optimization consumers in isolated output roots and compares -action keys and bytes for `GoStdlib`, synthetic helpers, synthetic -`~testmain.a`, and `GoLink`. Install `zstd` before running this check locally. - -## Adding a rules_go upstream release - -Use this sequence when adding support for a new upstream `rules_go` release. -Replace the example `v0_62_0` and `v0.62.0` values with the requested release. - -1. Resolve the exact upstream tag, commit, and archive checksum: - - ```bash - NEW_UPSTREAM=v0_62_0 - NEW_RULES_GO_VERSION=0.62.0 - NEW_TAG=v0.62.0 - NEW_COMMIT="$(git ls-remote --tags https://github.com/bazel-contrib/rules_go.git "refs/tags/${NEW_TAG}^{}" | awk '{print $1}')" - if [ -z "$NEW_COMMIT" ]; then - NEW_COMMIT="$(git ls-remote --tags https://github.com/bazel-contrib/rules_go.git "refs/tags/${NEW_TAG}" | awk '{print $1}')" - fi - test -n "$NEW_COMMIT" - curl -L "https://github.com/bazel-contrib/rules_go/archive/${NEW_COMMIT}.tar.gz" \ - -o "/tmp/rules_go-${NEW_UPSTREAM}.tar.gz" - NEW_ARCHIVE_SHA256="$(shasum -a 256 "/tmp/rules_go-${NEW_UPSTREAM}.tar.gz" | awk '{print $1}')" - echo "$NEW_ARCHIVE_SHA256" - ``` - -2. Add a `third_party/rules_go_orchestrion/registry.json` entry using: - - ```json - { - "rules_go_version": "0.62.0", - "upstream": { - "repository": "https://github.com/bazel-contrib/rules_go.git", - "commit": "", - "tag": "v0.62.0", - "archive_sha256": "" - }, - "patch_root": "third_party/rules_go_orchestrion/patches/v0_62_0", - "variants": { - "base": { - "tree_path": "third_party/rgo/v0_62_0/base", - "metadata_path": "third_party/rgo/v0_62_0/base.METADATA.json", - "changed_files_report": "third_party/rgo/v0_62_0/base.CHANGED_FILES.md", - "series": "third_party/rules_go_orchestrion/patches/v0_62_0/base.series" - } - } - } - ``` - -3. Create the metadata sidecar named by the registry entry: - - ```bash - mkdir -p "third_party/rgo/${NEW_UPSTREAM}" - cat > "third_party/rgo/${NEW_UPSTREAM}/base.METADATA.json" <.tar.gz"], - sha256 = "", - strip_prefix = "rules_test_optimization-/third_party/rgo/v0_60_0/base", -) -``` - -For non-default support lines, use the registry-resolved base tree path, for -example `third_party/rgo/v0_61_1/base`. - -Consumers should not apply additional Datadog-managed Bazel patch files when -they consume a complete base tree. Repositories that already own their -`rules_go` patch stack should instead use a generated public consumer patch -profile as a local rebase or merge input, then verify the regenerated private -patch in their private patch order. - -## Test Optimization Alias Contract - -The public base trees keep the existing -`//go/private/orchestrion:enabled` setting and stable aliases. The Datadog Go -wrapper transitions that setting only for optimized targets. Consumers expose -one metadata config: - -```bazelrc -common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 -``` - -The public Go extension reads the metadata environment by default; low-level -repositories do so when explicitly configured with `enabled_by_env = True`. -Removing the config is the metadata opt-out and must not require a -consumer-owned duplicate bool flag or stub repository. diff --git a/examples/README.md b/examples/README.md index 267f14f7..e33322fc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -32,7 +32,7 @@ repository, use `./bazelw` for local development convenience. - `bazel_dep(name = "datadog-rules-test-optimization-nodejs", version = "1.4.0")` for NodeJS macro usage - `bazel_dep(name = "datadog-rules-test-optimization-dotnet", version = "1.4.0")` for .NET macro usage - `bazel_dep(name = "datadog-rules-test-optimization-ruby", version = "1.4.0")` for Ruby macro usage - - `bazel_dep(name = "rules_go", ...)` for Go examples shown below + - `bazel_dep(name = "rules_go", ...)` (upstream, `0.60.0` or newer) for Go examples shown below - For NodeJS/.NET/Ruby examples shown below, also pin reference rulesets and register toolchains: - `bazel_dep(name = "aspect_rules_js", version = "3.0.0-rc5")` - `bazel_dep(name = "rules_nodejs", version = "6.7.3")` @@ -41,7 +41,9 @@ repository, use `./bazelw` for local development convenience. - `node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node"); node.toolchain(node_version = "22.22.0"); use_repo(node, "nodejs", "nodejs_host", "nodejs_toolchains"); register_toolchains("@nodejs_toolchains//:all")` - `dotnet = use_extension("@rules_dotnet//dotnet:extensions.bzl", "dotnet"); dotnet.toolchain(name = "dotnet", dotnet_version = "8.0.100"); use_repo(dotnet, "dotnet_toolchains"); register_toolchains("@dotnet_toolchains//:all")` - `ruby = use_extension("@rules_ruby//ruby:extensions.bzl", "ruby"); ruby.toolchain(name = "ruby", version = "3.3.9"); use_repo(ruby, "ruby", "ruby_toolchains"); register_toolchains("@ruby_toolchains//:all")` -- Configure Go toolchains/SDK in your repo if you build Go targets. +- Configure Go toolchains/SDK in your repo if you build Go targets, and the Go + SDK that `ddtest instrument` instruments for `dd_topt_go_test` with + `topt_go_ddtest.sdk(...)`. - Provide sync credentials via environment and forward them to repository rules: - shell/CI secret: `DD_API_KEY` - `.bazelrc`: `common --repo_env=DD_API_KEY` (and optionally `common --repo_env=DD_SITE`) @@ -53,9 +55,9 @@ repository, use `./bazelw` for local development convenience. ``` Removing `--config=test-optimization` disables metadata resolution and the - matching Go/Python runtime wiring. Optimized Go targets enable Orchestrion - through their own transition. Other companions retain their existing - enablement contract in this release. + matching Go/Python runtime wiring. Instrumented Go tests select the + instrumented Go SDK through their own transition. Other companions retain + their existing enablement contract in this release. ## Single-service (classic) @@ -70,7 +72,7 @@ bazel_dep(name = "datadog-rules-test-optimization-nodejs", version = "1.4.0") bazel_dep(name = "datadog-rules-test-optimization-dotnet", version = "1.4.0") bazel_dep(name = "datadog-rules-test-optimization-ruby", version = "1.4.0") -bazel_dep(name = "rules_go", version = "0.60.0") # or your repo-selected version +bazel_dep(name = "rules_go", version = "0.60.0") # or newer bazel_dep(name = "aspect_rules_js", version = "3.0.0-rc5") bazel_dep(name = "rules_nodejs", version = "6.7.3") bazel_dep(name = "rules_dotnet", version = "0.21.5") @@ -151,6 +153,14 @@ go_topt.test_optimization_go( ) use_repo(go_topt, "test_optimization_data_go") +# dd_topt_go_test builds tests with the Go SDK that ddtest instruments, at the +# Go version of the module's toolchain (or go) directive. +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", +) +topt_go_ddtest.sdk(go_mod = "//src/go-project:go.mod") + runtime_topt = use_extension( "@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", "test_optimization_sync_extension", @@ -209,29 +219,29 @@ runtime metadata and context label. If your team owns only Python, Java, NodeJS, .NET, or Ruby, the simpler runtime-specific setup is in [`docs/Language_Onboarding.md`](../docs/Language_Onboarding.md). -The generated Go wrapper uses `orchestrion_mode = "test_optimization"` for -standard Go `testing`. Manual `dd_topt_go_test` snippets below set the same mode -explicitly. The default Go Orchestrion mode is `general`; these examples choose -`test_optimization` because they target standard Go `testing` payloads rather -than broader generic Orchestrion instrumentation. +The `topt_go_ddtest.sdk(...)` block configures the Go SDK that +[`ddtest instrument`](https://github.com/DataDog/ddtest/blob/main/ddto/docs/bazel.md) +instruments; the Go companion registers the toolchains. `go_mod` needs a +`BUILD` file in the `go.mod` package. -Bootstrap once after adding the module prerequisites: +The guided bootstrap supports only fresh single-service Go workspaces, and it +stops when `MODULE.bazel` already uses `test_optimization_sync_extension`, as +this mixed-runtime workspace does. Write the central Go wrapper by hand: -```bash -bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ - --guided \ - --service go-service \ - --sync-repo-name test_optimization_data_go \ - --runtime-version 1.25.0 \ - --dd-trace-go-version v2.9.1 -``` +```bzl +# tools/build/dd_go_test.bzl +load("@datadog-rules-test-optimization-go//:topt_go_test.bzl", "dd_topt_go_test") +load("@test_optimization_data_go//:export.bzl", "topt_data") -`--dd-trace-go-version` is optional. If omitted, bootstrap uses the default -`v2.9.1`. It accepts a tag, -pseudo-version, branch, or commit SHA. Bootstrap resolves that input to exact -versions and repins the local Go module to match what Bazel will use. +def dd_go_test(name, **kwargs): + dd_topt_go_test( + name = name, + topt_data = topt_data, + **kwargs + ) +``` -BUILD.bazel (generated wrapper path, inference via embed): +BUILD.bazel (central wrapper, inference via embed): ```bzl load("@rules_go//go:def.bzl", "go_library") @@ -239,23 +249,24 @@ load("//tools/build:dd_go_test.bzl", "dd_go_test") go_library( name = "pkg_lib", - srcs = ["*.go"], + srcs = ["pkg.go"], + importpath = "example.com/single-service-go-project/pkg", ) dd_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], # importpath inferred via rules_go provider ) ``` -Enable runtime source staging only when the tracer needs to open the source -file for AST-derived metadata: +Enable runtime source staging only when the test runtime needs to open the +source file for metadata such as `test.source.end`: ```bzl dd_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], stage_sources = True, ) @@ -520,7 +531,7 @@ MODULE.bazel: ```bzl bazel_dep(name = "datadog-rules-test-optimization", version = "1.4.0") bazel_dep(name = "datadog-rules-test-optimization-go", version = "1.4.0") -bazel_dep(name = "rules_go", version = "0.60.0") # or your repo-selected version +bazel_dep(name = "rules_go", version = "0.60.0") # or newer git_override( module_name = "datadog-rules-test-optimization", @@ -553,18 +564,25 @@ use_repo( ) ``` -Bootstrap once after adding the Go module files: +Configure the Go SDK that `ddtest instrument` instruments, as the +`examples/multi_service` workspace does: + +```bzl +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", +) +topt_go_ddtest.sdk(go_mod = "//src/go-project:go.mod") +``` + +Instead of adding these lines by hand, you can let the bootstrap write them in +its managed block. Do not do both: the root module accepts one `sdk` tag. ```bash bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ - --go-module-dir src/go-project \ - --dd-trace-go-version v2.9.1 + --go-module-dir src/go-project ``` -As in the single-service flow, `--dd-trace-go-version` is optional and defaults -to `v2.9.1`. It may resolve to one shared tracer version or to separate exact -versions for the traced Go modules when you pass a branch or commit SHA. - This multi-service path stays on the lower-level/manual API. Guided bootstrap is only for fresh single-service Go workspaces. @@ -591,14 +609,14 @@ load("@test_optimization_data//:export.bzl", "topt_data_by_service") go_library( name = "pkg_lib", - srcs = ["*.go"], + srcs = ["pkg.go"], + importpath = "example.com/multi-service-go-project/pkg", ) dd_topt_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], - orchestrion_mode = "test_optimization", topt_data = topt_data_by_service["go_service_a"], # sanitized key ) ``` @@ -607,7 +625,7 @@ Mixed-runtime monorepo pattern: ```bzl # Keep runtime-specific sync repos separate. This remains the recommended path -# when not every service is Go and not every service needs Orchestrion. +# when not every service is Go. topt_go = use_extension( "@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", "test_optimization_sync_extension", @@ -646,14 +664,14 @@ load("@test_optimization_data//:export.bzl", "topt_data_by_service") go_library( name = "pkg_lib", - srcs = ["*.go"], + srcs = ["pkg.go"], + importpath = "example.com/multi-service-go-project/pkg", ) dd_topt_go_test( name = "pkg_go_test", - srcs = ["*_test.go"], + srcs = ["pkg_test.go"], embed = [":pkg_lib"], - orchestrion_mode = "test_optimization", topt_data = topt_data_by_service, topt_service = "go_service_a", # or raw "go-service-a" # If two services sanitize to the same key, use the deduped key (e.g. go_service_a_2). diff --git a/examples/multi_service/.bazelrc b/examples/multi_service/.bazelrc index 3203f0ef..7f4d448a 100644 --- a/examples/multi_service/.bazelrc +++ b/examples/multi_service/.bazelrc @@ -1,9 +1,8 @@ # Required on Windows for dd_topt_java_test: -javaagent paths only resolve under symlinked runfiles. build --enable_runfiles -# One user-facing switch for Go/Python metadata bootstrap and Go Orchestrion analysis. +# One user-facing switch for Go/Python metadata bootstrap. common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 -build:test-optimization --@rules_go//go/private/orchestrion:enabled=true # Hermetic config (sandbox, stable env, network blocking) build:hermetic --incompatible_strict_action_env diff --git a/examples/multi_service/MODULE.bazel b/examples/multi_service/MODULE.bazel index 9a3bbbf3..a5551ef1 100644 --- a/examples/multi_service/MODULE.bazel +++ b/examples/multi_service/MODULE.bazel @@ -6,15 +6,18 @@ module(name = "example-multi-service") -bazel_dep(name = "rules_go", version = "0.60.0") +bazel_dep(name = "rules_go", version = "0.60.0", repo_name = "io_bazel_rules_go") bazel_dep(name = "gazelle", version = "0.39.0") bazel_dep(name = "datadog-rules-test-optimization", version = "1.4.0") bazel_dep(name = "datadog-rules-test-optimization-go", version = "1.4.0") -# Run: -# bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- --go-module-dir src/go-project -# after adding your Go module files to let Datadog pin Orchestrion and patch -# the managed rules_go override block required by dd_topt_go_test. +# dd_topt_go_test builds tests with the Go SDK that ddtest instruments, at the +# Go version of the module's toolchain (or go) directive. +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", +) +topt_go_ddtest.sdk(go_mod = "//src/go-project:go.mod") # For local development before BCR publication, pin both modules from git: # git_override( diff --git a/examples/multi_service/src/go-project/BUILD.bazel b/examples/multi_service/src/go-project/BUILD.bazel index 28eaaaf9..5339893f 100644 --- a/examples/multi_service/src/go-project/BUILD.bazel +++ b/examples/multi_service/src/go-project/BUILD.bazel @@ -4,7 +4,7 @@ # This product includes software developed at Datadog # (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. -load("@rules_go//go:def.bzl", "go_binary", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") load("@datadog-rules-test-optimization-go//:topt_go_test.bzl", "dd_topt_go_test") load("@test_optimization_data//:export.bzl", "topt_data_by_service") @@ -25,5 +25,4 @@ dd_topt_go_test( topt_service = "go_service_a", # select one service (sanitized key) srcs = ["main_test.go"], embed = [":hello_lib"], # lets the macro infer importpath via rules_go provider - orchestrion_mode = "test_optimization", ) diff --git a/examples/single_service/.bazelrc b/examples/single_service/.bazelrc index 3203f0ef..7f4d448a 100644 --- a/examples/single_service/.bazelrc +++ b/examples/single_service/.bazelrc @@ -1,9 +1,8 @@ # Required on Windows for dd_topt_java_test: -javaagent paths only resolve under symlinked runfiles. build --enable_runfiles -# One user-facing switch for Go/Python metadata bootstrap and Go Orchestrion analysis. +# One user-facing switch for Go/Python metadata bootstrap. common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 -build:test-optimization --@rules_go//go/private/orchestrion:enabled=true # Hermetic config (sandbox, stable env, network blocking) build:hermetic --incompatible_strict_action_env diff --git a/examples/single_service/MODULE.bazel b/examples/single_service/MODULE.bazel index e2cd2e6e..97186edc 100644 --- a/examples/single_service/MODULE.bazel +++ b/examples/single_service/MODULE.bazel @@ -7,7 +7,7 @@ module(name = "example-single-service") bazel_dep(name = "aspect_rules_js", version = "3.0.0-rc5") -bazel_dep(name = "rules_go", version = "0.60.0") +bazel_dep(name = "rules_go", version = "0.60.0", repo_name = "io_bazel_rules_go") bazel_dep(name = "rules_nodejs", version = "6.7.3") bazel_dep(name = "rules_dotnet", version = "0.21.5") bazel_dep(name = "rules_ruby", version = "0.21.1") @@ -20,14 +20,13 @@ bazel_dep(name = "datadog-rules-test-optimization-nodejs", version = "1.4.0") bazel_dep(name = "datadog-rules-test-optimization-dotnet", version = "1.4.0") bazel_dep(name = "datadog-rules-test-optimization-ruby", version = "1.4.0") -# Run: -# bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ -# --guided \ -# --service go-service \ -# --runtime-version 1.25.0 \ -# --sync-repo-name test_optimization_data_go -# after adding your Go module files to let Datadog pin Orchestrion and patch -# the managed rules_go override block required by dd_topt_go_test. +# dd_topt_go_test builds tests with the Go SDK that ddtest instruments, at the +# Go version of the module's toolchain (or go) directive. +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", +) +topt_go_ddtest.sdk(go_mod = "//src/go-project:go.mod") # For local development before BCR publication, pin both modules from git: # git_override( diff --git a/examples/single_service/src/go-project/BUILD.bazel b/examples/single_service/src/go-project/BUILD.bazel index eae57b70..3a8e8361 100644 --- a/examples/single_service/src/go-project/BUILD.bazel +++ b/examples/single_service/src/go-project/BUILD.bazel @@ -4,7 +4,7 @@ # This product includes software developed at Datadog # (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. -load("@rules_go//go:def.bzl", "go_binary", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") load("@datadog-rules-test-optimization-go//:topt_go_test.bzl", "dd_topt_go_test") load("@test_optimization_data_go//:export.bzl", "topt_data") @@ -24,5 +24,4 @@ dd_topt_go_test( topt_data = topt_data, srcs = ["main_test.go"], embed = [":hello_lib"], # lets the macro infer importpath via rules_go provider - orchestrion_mode = "test_optimization", ) diff --git a/examples/single_service/src/go-project/go.mod b/examples/single_service/src/go-project/go.mod index 7e3eaa53..e9d70d77 100644 --- a/examples/single_service/src/go-project/go.mod +++ b/examples/single_service/src/go-project/go.mod @@ -7,123 +7,3 @@ module example.com/single-service-go-project go 1.26.0 - -require ( - github.com/DataDog/dd-trace-go/contrib/log/slog/v2 v2.11.2 - github.com/DataDog/dd-trace-go/contrib/net/http/v2 v2.11.2 - github.com/DataDog/dd-trace-go/v2 v2.11.2 - github.com/DataDog/orchestrion v1.12.0 -) - -require ( - github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.82.0 // indirect - github.com/DataDog/datadog-agent/pkg/obfuscate v0.82.0 // indirect - github.com/DataDog/datadog-agent/pkg/opentelemetry-mapping-go/otlp/attributes v0.82.0 // indirect - github.com/DataDog/datadog-agent/pkg/proto v0.82.0 // indirect - github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.82.0 // indirect - github.com/DataDog/datadog-agent/pkg/trace v0.82.0 // indirect - github.com/DataDog/datadog-agent/pkg/trace/log v0.82.0 // indirect - github.com/DataDog/datadog-agent/pkg/trace/stats v0.82.0 // indirect - github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.82.0 // indirect - github.com/DataDog/datadog-go/v5 v5.9.0 // indirect - github.com/DataDog/go-libddwaf/v5 v5.0.0 // indirect - github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20260217080614-b0f4edc38a6d // indirect - github.com/DataDog/go-sqllexer v0.2.4 // indirect - github.com/DataDog/go-tuf v1.1.1-0.5.2 // indirect - github.com/DataDog/sketches-go v1.4.8 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/antithesishq/antithesis-sdk-go v0.7.2 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect - github.com/cenkalti/backoff/v5 v5.0.3 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/charmbracelet/colorprofile v0.4.3 // indirect - github.com/charmbracelet/lipgloss v1.1.0 // indirect - github.com/charmbracelet/x/ansi v0.11.7 // indirect - github.com/charmbracelet/x/cellbuf v0.0.15 // indirect - github.com/charmbracelet/x/term v0.2.2 // indirect - github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect - github.com/clipperhouse/displaywidth v0.11.0 // indirect - github.com/clipperhouse/uax29/v2 v2.7.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect - github.com/dave/dst v0.27.4 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/ebitengine/purego v0.10.2 // indirect - github.com/fsnotify/fsnotify v1.10.1 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/goccy/go-yaml v1.19.2 // indirect - github.com/google/go-tpm v0.9.8 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/go-version v1.9.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.19.1 // indirect - github.com/klauspost/cpuid/v2 v2.4.0 // indirect - github.com/linkdata/deadlock v0.5.5 // indirect - github.com/lucasb-eyer/go-colorful v1.4.0 // indirect - github.com/lufia/plan9stats v0.0.0-20260627054121-477a66015f15 // indirect - github.com/mattn/go-colorable v0.1.15 // indirect - github.com/mattn/go-isatty v0.0.24 // indirect - github.com/mattn/go-runewidth v0.0.27 // indirect - github.com/minio/highwayhash v1.0.4 // indirect - github.com/minio/simdjson-go v0.4.5 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect - github.com/muesli/termenv v0.16.0 // indirect - github.com/nats-io/jwt/v2 v2.8.2 // indirect - github.com/nats-io/nats-server/v2 v2.14.3 // indirect - github.com/nats-io/nats.go v1.52.0 // indirect - github.com/nats-io/nkeys v0.4.16 // indirect - github.com/nats-io/nuid v1.0.1 // indirect - github.com/outcaste-io/ristretto v0.2.3 // indirect - github.com/petermattis/goid v0.0.0-20260725062400-500c67a39b75 // indirect - github.com/philhofer/fwd v1.2.0 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/polyfloyd/go-errorlint v1.8.1-0.20250906200200-9b25878c4dea // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/puzpuzpuz/xsync/v4 v4.5.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/rs/zerolog v1.35.1 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect - github.com/shirou/gopsutil/v4 v4.26.6 // indirect - github.com/stretchr/testify v1.11.1 // indirect - github.com/tinylib/msgp v1.6.4 // indirect - github.com/tklauser/go-sysconf v0.4.0 // indirect - github.com/tklauser/numcpus v0.12.0 // indirect - github.com/trailofbits/go-mutexasserts v0.0.0-20250514102930-c1f3d2e37561 // indirect - github.com/urfave/cli/v2 v2.27.7 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.opentelemetry.io/collector/component v1.63.0 // indirect - go.opentelemetry.io/collector/featuregate v1.63.0 // indirect - go.opentelemetry.io/collector/pdata v1.63.0 // indirect - go.opentelemetry.io/collector/pdata/pprofile v0.157.0 // indirect - go.opentelemetry.io/otel v1.45.0 // indirect - go.opentelemetry.io/otel/metric v1.45.0 // indirect - go.opentelemetry.io/otel/trace v1.45.0 // indirect - go.opentelemetry.io/proto/otlp v1.11.0 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.uber.org/mock v0.6.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.28.0 // indirect - go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/crypto v0.58.0 // indirect - golang.org/x/exp v0.0.0-20260718201538-764159d718ef // indirect - golang.org/x/mod v0.41.0 // indirect - golang.org/x/sync v0.23.0 // indirect - golang.org/x/sys v0.49.0 // indirect - golang.org/x/term v0.47.0 // indirect - golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.51.0 // indirect - golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect - google.golang.org/protobuf v1.36.12-0.20260116114154-8c4c4ae446ca // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/examples/single_service/src/go-project/go.sum b/examples/single_service/src/go-project/go.sum deleted file mode 100644 index f5e6675f..00000000 --- a/examples/single_service/src/go-project/go.sum +++ /dev/null @@ -1,351 +0,0 @@ -github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.82.0 h1:4cTEzpBezsfbHkn90BDPSYAWCahLqe/cpR5DSvRPrLU= -github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.82.0/go.mod h1:6LC1ryDn2VNqF0iNapwcLLdsfoFUMnT4p+JPu6sEkHg= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.82.0 h1:d0qTccmgcy/cCFlX7uz6m++2qtdHjG0cfj+Tj/w+TEo= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.82.0/go.mod h1:pW+H9lCruFxEq4JrxTKvatD+7hIsEXNd8InZyBmVYco= -github.com/DataDog/datadog-agent/pkg/opentelemetry-mapping-go/otlp/attributes v0.82.0 h1:Ha7Paf9fvT7MaetK60K0JliVIPMHYtXsEoB8vhWSCwU= -github.com/DataDog/datadog-agent/pkg/opentelemetry-mapping-go/otlp/attributes v0.82.0/go.mod h1:r3lb6X8YsmcfsLV9uM6apiqpfRACnbbyConMjsN1Q4Y= -github.com/DataDog/datadog-agent/pkg/proto v0.82.0 h1:3IniGYr5au2P1/EFFl1WvqnE37NT5OJEe22alQ8o2Jw= -github.com/DataDog/datadog-agent/pkg/proto v0.82.0/go.mod h1:o59ZSwSu/xgqEo7w0+Fpp2TGZzkWeTsQij7bnEh55dE= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.82.0 h1:NdNElWE9+XmN8dAWSA6C2nlcW1BRALAGH1YBVERAKwA= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.82.0/go.mod h1:OMlz3Bu/jlUXBVNrDP1ZyYtxwa5KMCm1OGa40Z3M9hI= -github.com/DataDog/datadog-agent/pkg/trace v0.82.0 h1:WnRt36GQMiS3eUuzgQwZHE/ujn2+yboxMKeCu7yzRAA= -github.com/DataDog/datadog-agent/pkg/trace v0.82.0/go.mod h1:996SW3E0BGByfmHHMZ8oyiEbySp2LhpWwsfPXdYS31o= -github.com/DataDog/datadog-agent/pkg/trace/log v0.82.0 h1:pddABOKDqh7J5zPblVa9pgPLGewAbSXIOVSHIOzrfC4= -github.com/DataDog/datadog-agent/pkg/trace/log v0.82.0/go.mod h1:Vj76uL63Yu1mh+ZlcxInL6YiG7xP2i1NcrIGSP6mXzI= -github.com/DataDog/datadog-agent/pkg/trace/stats v0.82.0 h1:rh/E60HMZzzuSNkYhKKILDjnxWYyV/dmNmsM/IYdhxY= -github.com/DataDog/datadog-agent/pkg/trace/stats v0.82.0/go.mod h1:ewMjfw1fAMTSplgGzl4X7jr/K+0UtVQbp67UFbP8Gso= -github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.82.0 h1:7qQAboJOj+eAj9caXwRgpuQiALYcL+spsU/gawTBevo= -github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.82.0/go.mod h1:yZZL4wCytoVXUayJuNXznHduZ0INjSA8rw9E/98hhVM= -github.com/DataDog/datadog-go/v5 v5.9.0 h1:0rhs5wBov9Iz+xLXLk4maaReHvOANM1ijSm2IKWtKFs= -github.com/DataDog/datadog-go/v5 v5.9.0/go.mod h1:2SBt8zJu6r7sRQHZFMQ8oCukWTKj0ymwulmNgQzJ1JM= -github.com/DataDog/dd-trace-go/contrib/log/slog/v2 v2.11.2 h1:hYhU6jY/ev7KDsCvGDfAHiAZ1P2017xGHQPFK+IRMus= -github.com/DataDog/dd-trace-go/contrib/log/slog/v2 v2.11.2/go.mod h1:f0C6HNXKn+Tqja65Lzws7KUF00chIzK1wqPcsfSvkVY= -github.com/DataDog/dd-trace-go/contrib/net/http/v2 v2.11.2 h1:g2n0fyzmhn6MRJ5WEe8IdNnJtVLY0dP2X3aPRMIfU18= -github.com/DataDog/dd-trace-go/contrib/net/http/v2 v2.11.2/go.mod h1:qwRccmR3+QntlLNAtqA2UuxIoqS0n9rfTJiChHecDbA= -github.com/DataDog/dd-trace-go/v2 v2.11.2 h1:HbANttl9UJGen/tRb+pX3nPNa4IQy2vXvUDlhZaQHeI= -github.com/DataDog/dd-trace-go/v2 v2.11.2/go.mod h1:V/CeQWG3+2fzAgZReAYfDRNjUXjHKOOW4KtEnV/yk2g= -github.com/DataDog/go-libddwaf/v5 v5.0.0 h1:uPQfIVPH9T8jLSplAdfETAHwL3iRLabP6JxRTH8NL7Y= -github.com/DataDog/go-libddwaf/v5 v5.0.0/go.mod h1:/UzdsIsO8qk8fmbqNnk6Na7MsaNvU2R7SlBRHEgSIOQ= -github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20260217080614-b0f4edc38a6d h1:cH9Bm0tJ8FEQbA4FRi0iRm7Zr/5Lata/Or31c+Dth0E= -github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20260217080614-b0f4edc38a6d/go.mod h1:yDuvU+Ak1TKwgd4K8DNcpJmUrrK8ONLkBMGNAppmBRk= -github.com/DataDog/go-sqllexer v0.2.4 h1:Ah9v8g5KX8vNV7q3t6YoA3fW4F/1OvZh/JbyfPcaySA= -github.com/DataDog/go-sqllexer v0.2.4/go.mod h1:3xTFXBU69vUikYpESggScvC0RKYA7ZIdVrIkLwUOWdE= -github.com/DataDog/go-tuf v1.1.1-0.5.2 h1:YWvghV4ZvrQsPcUw8IOUMSDpqc3W5ruOIC+KJxPknv0= -github.com/DataDog/go-tuf v1.1.1-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= -github.com/DataDog/orchestrion v1.12.0 h1:GMdYBXkn3QbaU1mla2b7FN0uF9vJ2UJzrUH1X254Hc0= -github.com/DataDog/orchestrion v1.12.0/go.mod h1:Xg4S/8UP7LpmmeSR/pqhO7vKUpiohSJcU8VCDPkpBpA= -github.com/DataDog/sketches-go v1.4.8 h1:pFk9BNn+Rzv8IMIoPUttoOpOr3bJOqU3P6EP5wK+Lv8= -github.com/DataDog/sketches-go v1.4.8/go.mod h1:a/wjRUqzqtGS8qRHRPDCs4EAQfmvPDZGDlMIF5mxXOE= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/antithesishq/antithesis-sdk-go v0.7.2 h1:oEEedg1Xgi8drRjqB0f9tfjhLoInE0IYZfZ6zAhQUbY= -github.com/antithesishq/antithesis-sdk-go v0.7.2/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= -github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= -github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= -github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= -github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= -github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= -github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= -github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= -github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= -github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs= -github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= -github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= -github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= -github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= -github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= -github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= -github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/dave/dst v0.27.4 h1:d+EVnOZmphH+lUEXq9rit4GjsFSKJ3AhfRWf7eobTps= -github.com/dave/dst v0.27.4/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= -github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= -github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= -github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE= -github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= -github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= -github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= -github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= -github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= -github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= -github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= -github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/liamg/memoryfs v1.6.0 h1:jAFec2HI1PgMTem5gR7UT8zi9u4BfG5jorCRlLH06W8= -github.com/liamg/memoryfs v1.6.0/go.mod h1:z7mfqXFQS8eSeBBsFjYLlxYRMRyiPktytvYCYTb3BSk= -github.com/linkdata/deadlock v0.5.5 h1:d6O+rzEqasSfamGDA8u7bjtaq7hOX8Ha4Zn36Wxrkvo= -github.com/linkdata/deadlock v0.5.5/go.mod h1:tXb28stzAD3trzEEK0UJWC+rZKuobCoPktPYzebb1u0= -github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= -github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lufia/plan9stats v0.0.0-20260627054121-477a66015f15 h1:YkjVPl/YH5XlJ+/NiwzJtPYXXKRcyjmEUhsDci6YK3c= -github.com/lufia/plan9stats v0.0.0-20260627054121-477a66015f15/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= -github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= -github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= -github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= -github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0= -github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= -github.com/minio/highwayhash v1.0.4 h1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4= -github.com/minio/highwayhash v1.0.4/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= -github.com/minio/simdjson-go v0.4.5 h1:r4IQwjRGmWCQ2VeMc7fGiilu1z5du0gJ/I/FsKwgo5A= -github.com/minio/simdjson-go v0.4.5/go.mod h1:eoNz0DcLQRyEDeaPr4Ru6JpjlZPzbA0IodxVJk8lO8E= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/nats-io/jwt/v2 v2.8.2 h1:XXRgB60MSTnqsRwejQurVDs/hcv2dkt+86GjI+I/bMc= -github.com/nats-io/jwt/v2 v2.8.2/go.mod h1:Ag/56sq9OblL4JgdYufDd16Egb17Kr/8WwwuO/forVc= -github.com/nats-io/nats-server/v2 v2.14.3 h1:+xjydPt7rkit67G+04TN0mcO2n+8nveZE7tK/PPV53A= -github.com/nats-io/nats-server/v2 v2.14.3/go.mod h1:5IlCtBzfwyzQzPMjmoJ9W2/LKmnJRtNyuOs/OT+NHDY= -github.com/nats-io/nats.go v1.52.0 h1:n3avV4VBsCgsdwh71TppsTwtv+QdPs7ntSKM8qJLGsc= -github.com/nats-io/nats.go v1.52.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno= -github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg= -github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= -github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= -github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= -github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= -github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= -github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= -github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/petermattis/goid v0.0.0-20260725062400-500c67a39b75 h1:VmZ6mKVkxavKEhEy4ZYyV7BwBYBFBP0TwIqmLk84fpU= -github.com/petermattis/goid v0.0.0-20260725062400-500c67a39b75/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= -github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.8.1-0.20250906200200-9b25878c4dea h1:0hdZfdf74rU/bEndy2uZJyNeY21jmH751KmdAjOmEiA= -github.com/polyfloyd/go-errorlint v1.8.1-0.20250906200200-9b25878c4dea/go.mod h1:msT1JMnFNM1gqj7rtZYaA0EtpIYNeLQSsKJChZNA+5A= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/puzpuzpuz/xsync/v4 v4.5.0 h1:vOSWu6b57/emh+L/Cw0BeQfvxa/cogFywXHeGUxQxAg= -github.com/puzpuzpuz/xsync/v4 v4.5.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= -github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= -github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= -github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= -github.com/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs= -github.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs= -github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= -github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ= -github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= -github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= -github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI= -github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4= -github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= -github.com/trailofbits/go-mutexasserts v0.0.0-20250514102930-c1f3d2e37561 h1:qqa3P9AtNn6RMe90l/lxd3eJWnIRxjI4eb5Rx8xqCLA= -github.com/trailofbits/go-mutexasserts v0.0.0-20250514102930-c1f3d2e37561/go.mod h1:GA3+Mq3kt3tYAfM0WZCu7ofy+GW9PuGysHfhr+6JX7s= -github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= -github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= -github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= -github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= -github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/collector/component v1.63.0 h1:l98ZCxfCTt/O6dYB0JVKKtewaFLe/a6N2qQe61Tbf2o= -go.opentelemetry.io/collector/component v1.63.0/go.mod h1:yLGMmT7jUiqvuGvkqlfR1CBi0dRkSV67tq22I08ZMPk= -go.opentelemetry.io/collector/component/componenttest v0.155.0 h1:FfQQpYJnkNhNW5EPSD+vBiUL7Mwgkudrkr0LRYpi7HA= -go.opentelemetry.io/collector/component/componenttest v0.155.0/go.mod h1:MkXnGN4QH6El1GGTTOrDUqY8/p8Vkbfi0Non2Pmi0m4= -go.opentelemetry.io/collector/featuregate v1.63.0 h1:6EWX1C5AtmIh8hFH97DwK6R7R8Jk3fTLxAUfZPXGutY= -go.opentelemetry.io/collector/featuregate v1.63.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU= -go.opentelemetry.io/collector/internal/testutil v0.157.0 h1:plojUQwFC5l1ex9KUDaLmCFY/mTxEmf3zrlP7M23IEw= -go.opentelemetry.io/collector/internal/testutil v0.157.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= -go.opentelemetry.io/collector/pdata v1.63.0 h1:fY2xSG2MnyoBwA4GUhzoogGZMuNS0qHpCoODqaKwiVQ= -go.opentelemetry.io/collector/pdata v1.63.0/go.mod h1:jzozYYhQEkTQ/CCbCBNC+hYUeju9S2J8HIqIDHdxZWk= -go.opentelemetry.io/collector/pdata/pprofile v0.157.0 h1:YRTPhwWzdG0pfJmb8p/qpQm1EdX+JfV20qzwG3ypDqI= -go.opentelemetry.io/collector/pdata/pprofile v0.157.0/go.mod h1:kwy/ufNUBkw8PsFPQnAqCvD12OpGU8h9A1cz5S7xS6g= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= -go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= -go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= -go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM= -go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk= -go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ= -go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U= -go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0= -go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= -go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= -go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= -go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= -go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54= -golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w= -golang.org/x/exp v0.0.0-20260718201538-764159d718ef h1:LkZ48HFgy/TvhTI0bcWkjgFkgLyKUwcTbDjS0DUjw+A= -golang.org/x/exp v0.0.0-20260718201538-764159d718ef/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= -golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= -golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= -golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= -golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM= -golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc= -golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s= -golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= -golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI= -golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= -golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/protobuf v1.36.12-0.20260116114154-8c4c4ae446ca h1:/ro7D0tSP+jEnQPzy9e1r5L6mAcEShGlE5kFsShX5O8= -google.golang.org/protobuf v1.36.12-0.20260116114154-8c4c4ae446ca/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= -gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= diff --git a/examples/single_service/src/go-project/orchestrion.tool.go b/examples/single_service/src/go-project/orchestrion.tool.go deleted file mode 100644 index 1236d6f8..00000000 --- a/examples/single_service/src/go-project/orchestrion.tool.go +++ /dev/null @@ -1,30 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under -// the Apache 2.0 License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. - -// This file was created by `orchestrion pin`, and is used to ensure the -// `go.mod` file contains the necessary entries to ensure repeatable builds when -// using `orchestrion`. It is also used to set up which integrations are enabled. - -//go:build tools - -//go:generate go run github.com/DataDog/orchestrion pin -generate - -package tools - -// Imports in this file determine which tracer integrations are enabled in -// orchestrion. New integrations can be automatically discovered by running -// `orchestrion pin` again. You can also manually add new imports here to -// enable additional integrations. When doing so, you can run `orchestrion pin` -// to make sure manually added integrations are valid (i.e, the imported package -// includes a valid `orchestrion.yml` file). -import ( - // Ensures `orchestrion` is present in `go.mod` so that builds are repeatable. - // Do not remove. - _ "github.com/DataDog/dd-trace-go/contrib/log/slog/v2" // integration - _ "github.com/DataDog/dd-trace-go/contrib/net/http/v2" // integration - _ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration - _ "github.com/DataDog/orchestrion" // integration -) diff --git a/modules/go/BUILD.bazel b/modules/go/BUILD.bazel index a6776139..6ee56913 100644 --- a/modules/go/BUILD.bazel +++ b/modules/go/BUILD.bazel @@ -4,14 +4,16 @@ # This product includes software developed at Datadog # (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. -load("@rules_go//go:def.bzl", "go_binary") +load("@io_bazel_rules_go//go:def.bzl", "go_binary") exports_files( [ "topt_go_test.bzl", "topt_go_infer.bzl", + "topt_go_ddtest.bzl", + "topt_go_ddtest_extension.bzl", + "topt_go_ddtest_repository.bzl", "topt_go_extension.bzl", - "topt_go_orchestrion_repository.bzl", "topt_go_stdlib.bzl", "topt_go_workspace.bzl", ], diff --git a/modules/go/MODULE.bazel b/modules/go/MODULE.bazel index 0d937b87..828ea4e0 100644 --- a/modules/go/MODULE.bazel +++ b/modules/go/MODULE.bazel @@ -11,12 +11,34 @@ module( bazel_dep(name = "datadog-rules-test-optimization", version = "1.4.0") bazel_dep(name = "platforms", version = "1.0.0") -bazel_dep(name = "rules_go", version = "0.60.0") + +# The SDK repositories that go_wrap_sdk writes load rules_go as +# @io_bazel_rules_go, so this module sees it under that name. +bazel_dep(name = "rules_go", version = "0.60.0", repo_name = "io_bazel_rules_go") bazel_dep(name = "bazel_skylib", version = "1.8.2") -local_path_override( - module_name = "rules_go", - path = "../../third_party/rgo/v0_60_0/base", +# The ddtest-instrumented Go SDKs and Mini. The root module configures the SDK +# with topt_go_ddtest.sdk(...); these toolchains only match tests that the +# instrumentation transition selects, so other builds fetch nothing. +topt_go_ddtest = use_extension("//:topt_go_ddtest_extension.bzl", "topt_go_ddtest") +use_repo(topt_go_ddtest, "topt_go_ddtest_mini", "topt_go_ddtest_toolchains") + +register_toolchains("@topt_go_ddtest_toolchains//:all") + +# The companion's own tests instrument the official Go SDK. +topt_go_ddtest_dev = use_extension( + "//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", + dev_dependency = True, +) +topt_go_ddtest_dev.sdk(go_version = "1.26.3") + +# A FIPS-like SDK variant on another Go version, which tests/ddtest's +# go_cross_binary selects below an instrumented test. +topt_go_ddtest_dev.sdk( + experiments = ["boringcrypto"], + go_version = "1.25.9", + sdk_version = "1.25.9fips", ) # Dev-only stub repository so companion-module tests can resolve diff --git a/modules/go/MODULE.bazel.lock b/modules/go/MODULE.bazel.lock index d11c7953..c7a239ed 100644 --- a/modules/go/MODULE.bazel.lock +++ b/modules/go/MODULE.bazel.lock @@ -10,6 +10,7 @@ "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", @@ -37,6 +38,9 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", "https://bcr.bazel.build/modules/gazelle/0.36.0/source.json": "0823f097b127e0201ae55d85647c94095edfe27db0431a7ae880dcab08dfaa04", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", @@ -63,6 +67,7 @@ "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", @@ -86,6 +91,11 @@ "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", + "https://bcr.bazel.build/modules/rules_go/0.60.0/MODULE.bazel": "4a57ff2ffc2a3570e3c5646575c5a4b07287e91bcdac5d1f72383d51502b48cb", + "https://bcr.bazel.build/modules/rules_go/0.60.0/source.json": "1e21368c5e0c3013a110bd79a8fcff8ca46b5bcb2b561713a7273cbfcff7c464", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", @@ -146,6 +156,948 @@ }, "selectedYankedVersions": {}, "moduleExtensions": { + "//:topt_go_ddtest_extension.bzl%topt_go_ddtest": { + "general": { + "bzlTransitiveDigest": "n5O4xLLdqGDM4y5XWH0B51n2RGUkaiC/vF4JrsZMTx0=", + "usagesDigest": "tgjgtFWtKN0BqQfQrh/mwAh7ZOA/g0vsgIO9+xMDZOs=", + "recordedInputs": [ + "REPO_MAPPING:,bazel_skylib bazel_skylib+", + "REPO_MAPPING:,io_bazel_rules_go rules_go+", + "REPO_MAPPING:rules_go+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_go+,io_bazel_rules_go rules_go+" + ], + "generatedRepoSpecs": { + "topt_go_ddtest_host": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_host", + "attributes": {} + }, + "topt_go_ddtest_mini": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_mini", + "attributes": { + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_base_darwin_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.26.3", + "goos": "darwin", + "goarch": "amd64", + "experiments": [] + } + }, + "topt_go_ddtest_base_darwin_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "darwin" + ], + "goarchs": [ + "amd64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_darwin_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_sdk_files_darwin_amd64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_darwin_amd64//:ROOT", + "exec_platform": "darwin_amd64", + "go_version": "1.26.3", + "sdk_version": "", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_darwin_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_darwin_amd64//:sdk/DDTEST_INSTRUMENT", + "experiments": [], + "version": "1.26.3" + } + }, + "topt_go_ddtest_sdk_darwin_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_darwin_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_base_darwin_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.26.3", + "goos": "darwin", + "goarch": "arm64", + "experiments": [] + } + }, + "topt_go_ddtest_base_darwin_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "darwin" + ], + "goarchs": [ + "arm64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_darwin_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_sdk_files_darwin_arm64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_darwin_arm64//:ROOT", + "exec_platform": "darwin_arm64", + "go_version": "1.26.3", + "sdk_version": "", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_darwin_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_darwin_arm64//:sdk/DDTEST_INSTRUMENT", + "experiments": [], + "version": "1.26.3" + } + }, + "topt_go_ddtest_sdk_darwin_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_darwin_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_base_linux_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.26.3", + "goos": "linux", + "goarch": "amd64", + "experiments": [] + } + }, + "topt_go_ddtest_base_linux_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "linux" + ], + "goarchs": [ + "amd64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_linux_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_sdk_files_linux_amd64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_linux_amd64//:ROOT", + "exec_platform": "linux_amd64", + "go_version": "1.26.3", + "sdk_version": "", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_linux_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_linux_amd64//:sdk/DDTEST_INSTRUMENT", + "experiments": [], + "version": "1.26.3" + } + }, + "topt_go_ddtest_sdk_linux_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_linux_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_base_linux_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.26.3", + "goos": "linux", + "goarch": "arm64", + "experiments": [] + } + }, + "topt_go_ddtest_base_linux_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "linux" + ], + "goarchs": [ + "arm64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_linux_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_sdk_files_linux_arm64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_linux_arm64//:ROOT", + "exec_platform": "linux_arm64", + "go_version": "1.26.3", + "sdk_version": "", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_linux_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_linux_arm64//:sdk/DDTEST_INSTRUMENT", + "experiments": [], + "version": "1.26.3" + } + }, + "topt_go_ddtest_sdk_linux_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_linux_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_base_windows_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.26.3", + "goos": "windows", + "goarch": "amd64", + "experiments": [] + } + }, + "topt_go_ddtest_base_windows_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "windows" + ], + "goarchs": [ + "amd64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_windows_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_sdk_files_windows_amd64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_windows_amd64//:ROOT", + "exec_platform": "windows_amd64", + "go_version": "1.26.3", + "sdk_version": "", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_windows_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_windows_amd64//:sdk/DDTEST_INSTRUMENT", + "experiments": [], + "version": "1.26.3" + } + }, + "topt_go_ddtest_sdk_windows_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_windows_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_base_windows_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.26.3", + "goos": "windows", + "goarch": "arm64", + "experiments": [] + } + }, + "topt_go_ddtest_base_windows_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "windows" + ], + "goarchs": [ + "arm64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_windows_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_sdk_files_windows_arm64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_windows_arm64//:ROOT", + "exec_platform": "windows_arm64", + "go_version": "1.26.3", + "sdk_version": "", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_windows_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_windows_arm64//:sdk/DDTEST_INSTRUMENT", + "experiments": [], + "version": "1.26.3" + } + }, + "topt_go_ddtest_sdk_windows_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_windows_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.26.3" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_darwin_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.25.9", + "goos": "darwin", + "goarch": "amd64", + "experiments": [ + "boringcrypto" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_darwin_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "darwin" + ], + "goarchs": [ + "amd64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_v1_25_9fips_darwin_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_sdk_files_v1_25_9fips_darwin_amd64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_v1_25_9fips_darwin_amd64//:ROOT", + "exec_platform": "darwin_amd64", + "go_version": "1.25.9", + "sdk_version": "1.25.9fips", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_darwin_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_v1_25_9fips_darwin_amd64//:sdk/DDTEST_INSTRUMENT", + "experiments": [ + "boringcrypto" + ], + "version": "1.25.9" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_darwin_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_v1_25_9fips_darwin_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_darwin_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.25.9", + "goos": "darwin", + "goarch": "arm64", + "experiments": [ + "boringcrypto" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_darwin_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "darwin" + ], + "goarchs": [ + "arm64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_v1_25_9fips_darwin_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_sdk_files_v1_25_9fips_darwin_arm64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_v1_25_9fips_darwin_arm64//:ROOT", + "exec_platform": "darwin_arm64", + "go_version": "1.25.9", + "sdk_version": "1.25.9fips", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_darwin_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_v1_25_9fips_darwin_arm64//:sdk/DDTEST_INSTRUMENT", + "experiments": [ + "boringcrypto" + ], + "version": "1.25.9" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_darwin_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_v1_25_9fips_darwin_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_linux_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.25.9", + "goos": "linux", + "goarch": "amd64", + "experiments": [ + "boringcrypto" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_linux_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "linux" + ], + "goarchs": [ + "amd64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_v1_25_9fips_linux_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_sdk_files_v1_25_9fips_linux_amd64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_v1_25_9fips_linux_amd64//:ROOT", + "exec_platform": "linux_amd64", + "go_version": "1.25.9", + "sdk_version": "1.25.9fips", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_linux_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_v1_25_9fips_linux_amd64//:sdk/DDTEST_INSTRUMENT", + "experiments": [ + "boringcrypto" + ], + "version": "1.25.9" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_linux_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_v1_25_9fips_linux_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_linux_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.25.9", + "goos": "linux", + "goarch": "arm64", + "experiments": [ + "boringcrypto" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_linux_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "linux" + ], + "goarchs": [ + "arm64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_v1_25_9fips_linux_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_sdk_files_v1_25_9fips_linux_arm64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_v1_25_9fips_linux_arm64//:ROOT", + "exec_platform": "linux_arm64", + "go_version": "1.25.9", + "sdk_version": "1.25.9fips", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_linux_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_v1_25_9fips_linux_arm64//:sdk/DDTEST_INSTRUMENT", + "experiments": [ + "boringcrypto" + ], + "version": "1.25.9" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_linux_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_v1_25_9fips_linux_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_windows_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.25.9", + "goos": "windows", + "goarch": "amd64", + "experiments": [ + "boringcrypto" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_windows_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "windows" + ], + "goarchs": [ + "amd64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_v1_25_9fips_windows_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_sdk_files_v1_25_9fips_windows_amd64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_v1_25_9fips_windows_amd64//:ROOT", + "exec_platform": "windows_amd64", + "go_version": "1.25.9", + "sdk_version": "1.25.9fips", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_windows_amd64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_v1_25_9fips_windows_amd64//:sdk/DDTEST_INSTRUMENT", + "experiments": [ + "boringcrypto" + ], + "version": "1.25.9" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_windows_amd64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_v1_25_9fips_windows_amd64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_windows_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "version": "1.25.9", + "goos": "windows", + "goarch": "arm64", + "experiments": [ + "boringcrypto" + ] + } + }, + "topt_go_ddtest_base_v1_25_9fips_windows_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "windows" + ], + "goarchs": [ + "arm64" + ], + "sdk_repos": [ + "topt_go_ddtest_base_v1_25_9fips_windows_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_sdk_files_v1_25_9fips_windows_arm64": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_sdk_files", + "attributes": { + "base_sdk_root": "@@+topt_go_ddtest+topt_go_ddtest_base_v1_25_9fips_windows_arm64//:ROOT", + "exec_platform": "windows_arm64", + "go_version": "1.25.9", + "sdk_version": "1.25.9fips", + "panic_hook": false, + "host": "@@+topt_go_ddtest+topt_go_ddtest_host//:PLATFORM" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_windows_arm64": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_wrap_sdk_rule", + "attributes": { + "root_file": "@@+topt_go_ddtest+topt_go_ddtest_sdk_files_v1_25_9fips_windows_arm64//:sdk/DDTEST_INSTRUMENT", + "experiments": [ + "boringcrypto" + ], + "version": "1.25.9" + } + }, + "topt_go_ddtest_sdk_v1_25_9fips_windows_arm64_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "topt_go_ddtest_sdk_v1_25_9fips_windows_arm64" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.25.9" + ] + } + }, + "topt_go_ddtest_toolchains": { + "repoRuleId": "@@//:topt_go_ddtest_repository.bzl%_toolchains", + "attributes": { + "sdks": "[{\"exec_platforms\":[\"darwin_amd64\",\"darwin_arm64\",\"linux_amd64\",\"linux_arm64\",\"windows_amd64\",\"windows_arm64\"],\"id\":\"\",\"matches\":[\"\",\"1\",\"1.26\",\"1.26.3\"]},{\"exec_platforms\":[\"darwin_amd64\",\"darwin_arm64\",\"linux_amd64\",\"linux_arm64\",\"windows_amd64\",\"windows_arm64\"],\"id\":\"v1_25_9fips\",\"matches\":[\"1.25.9fips\"]}]" + } + } + } + } + }, "//tests:example_stub_repo.bzl%example_stub_repo_extension": { "general": { "bzlTransitiveDigest": "qE9S3HNMfKVJtD4XMT5WNvc3sgOOqqdVoBns0egnuPE=", @@ -328,25 +1280,6 @@ } } }, - "@@rules_go+//go:extensions.bzl%orchestrion": { - "general": { - "bzlTransitiveDigest": "D5LBsjRKekTF19YV7Jqf9N14osuZYJjSaa+XKj00RlI=", - "usagesDigest": "9kKEG/hjK4fnk42l4jAKDLhx9xExyCYAS8r8NlbPLMw=", - "recordedInputs": [ - "REPO_MAPPING:bazel_features+,bazel_features_globals bazel_features++version_extension+bazel_features_globals", - "REPO_MAPPING:bazel_features+,bazel_features_version bazel_features++version_extension+bazel_features_version", - "REPO_MAPPING:rules_go+,bazel_tools bazel_tools", - "REPO_MAPPING:rules_go+,io_bazel_rules_go rules_go+", - "REPO_MAPPING:rules_go+,io_bazel_rules_go_bazel_features bazel_features+" - ], - "generatedRepoSpecs": { - "rules_go_orchestrion_tool": { - "repoRuleId": "@@rules_go+//go/private/orchestrion:extensions.bzl%_orchestrion_empty", - "attributes": {} - } - } - } - }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { "bzlTransitiveDigest": "03Qju4tW0vE+0RBuZGuV2A4Hx6AiSkdNahYvworx2aM=", diff --git a/modules/go/tests/BUILD.bazel b/modules/go/tests/BUILD.bazel index ef8f2327..47e1aae3 100644 --- a/modules/go/tests/BUILD.bazel +++ b/modules/go/tests/BUILD.bazel @@ -8,6 +8,16 @@ load( "@datadog-rules-test-optimization-go//:topt_go_stdlib.bzl", "dd_topt_go_stdlib_warmup", ) +load( + ":test_ddtest_repository.bzl", + "base_sdk_version_test", + "contract_test", + "go_version_from_go_mod_test", + "platforms_test", + "sdk_config_test", + "sdk_versions_test", + "toolchains_build_content_test", +) load( ":test_extension.bzl", "go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test", @@ -16,10 +26,12 @@ load( ) load( ":test_macro.bzl", + "ddtest_wrapper_materialized_actual_non_windows_target", + "ddtest_wrapper_materialized_actual_non_windows_test", + "ddtest_wrapper_materialized_actual_windows_target", + "ddtest_wrapper_materialized_actual_windows_test", "go_macro_ci_visibility_opt_out_target", "go_macro_ci_visibility_opt_out_wiring_test", - "go_macro_default_general_public_wrapper_mode_target", - "go_macro_default_general_public_wrapper_mode_test", "go_macro_disabled_raw_target", "go_macro_disabled_raw_wiring_test", "go_macro_dynamic_manifest_payloads_test", @@ -29,15 +41,11 @@ load( "go_macro_env_none_wiring_test", "go_macro_explicit_service_target", "go_macro_explicit_service_wiring_test", - "go_macro_general_mode_linker_flags_target", - "go_macro_general_mode_linker_flags_wiring_test", "go_macro_inferred_importpath_metadata_test", + "go_macro_instrumentation_metadata_test", "go_macro_internal_helper_tags_loading_tests", "go_macro_multi_service_target", "go_macro_multi_service_wiring_test", - "go_macro_orchestrion_pin_files_provider_test", - "go_macro_orchestrion_pin_files_target", - "go_macro_orchestrion_pin_files_wiring_test", "go_macro_public_wrapper_test", "go_macro_rundir_mismatch_target", "go_macro_rundir_mismatch_wiring_test", @@ -75,31 +83,18 @@ load( "go_macro_test_optimization_linker_metadata_strip_never_test", "go_macro_test_optimization_linker_opt_out_target", "go_macro_test_optimization_linker_opt_out_wiring_test", - "go_macro_test_optimization_mode_target", - "go_macro_test_optimization_mode_wiring_test", - "go_macro_test_optimization_public_wrapper_mode_target", - "go_macro_test_optimization_public_wrapper_mode_test", - "has_go_mod_pin_test", - "has_package_local_go_mod_test", "hidden_raw_tags_test", - "orch_transition_forwards_mode_test", - "orch_wrapper_materialized_actual_non_windows_target", - "orch_wrapper_materialized_actual_non_windows_test", - "orch_wrapper_materialized_actual_windows_target", - "orch_wrapper_materialized_actual_windows_test", + "reject_removed_orchestrion_attrs_failure_test", + "reject_removed_orchestrion_attrs_target_rule", + "reject_removed_orchestrion_attrs_test", + "reject_removed_orchestrion_mode_failure_test", "resolve_topt_service_key_missing_failure_test", "resolve_topt_service_key_missing_target_rule", "resolve_topt_service_key_unknown_failure_test", "resolve_topt_service_key_unknown_target_rule", "stdlib_warmup_tags_test", "stdlib_warmup_target_has_outputs_test", - "stdlib_warmup_transition_selects_test_optimization_test", "test_execution_requirements_test", - "validate_orchestrion_mode_invalid_failure_test", - "validate_orchestrion_mode_invalid_target_rule", - "validate_orchestrion_mode_test", - "validate_test_optimization_pin_files_missing_go_mod_failure_test", - "validate_test_optimization_pin_files_missing_go_mod_target_rule", "windows_wrapper_uses_file_payload_mode_test", "wrapper_output_name_non_windows_test", "wrapper_output_name_target_rule", @@ -158,7 +153,6 @@ load( "normalize_user_data_handles_none_test", "normalize_user_data_invalid_type_failure_test", "normalize_user_data_invalid_type_target_rule", - "orchestrion_metadata_enabled_test", "resolve_topt_service_key_prefers_exact_then_sanitized_test", "select_module_group_name_test", "service_mapping_entries_filters_non_service_test", @@ -168,8 +162,6 @@ load( "go_workspace_multi_specs_test", "go_workspace_single_specs_test", "go_workspace_specs_default_to_config_gated_test", - "orchestrion_call_spec_test", - "orchestrion_pin_file_call_spec_test", ) # Go-specific Starlark tests for orchestration selection and macro wiring. @@ -191,6 +183,20 @@ go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test( timeout = "short", ) +contract_test(name = "ddtest_contract_test") + +go_version_from_go_mod_test(name = "ddtest_go_version_from_go_mod_test") + +platforms_test(name = "ddtest_platforms_test") + +sdk_config_test(name = "ddtest_sdk_config_test") + +sdk_versions_test(name = "ddtest_sdk_versions_test") + +base_sdk_version_test(name = "ddtest_base_sdk_version_test") + +toolchains_build_content_test(name = "ddtest_toolchains_build_content_test") + go_specs_default_to_config_gated_test( name = "go_specs_default_to_config_gated_test", size = "small", @@ -215,26 +221,12 @@ go_workspace_specs_default_to_config_gated_test( timeout = "short", ) -orchestrion_call_spec_test( - name = "orchestrion_call_spec_test", - size = "small", - timeout = "short", -) - -orchestrion_pin_file_call_spec_test( - name = "orchestrion_pin_file_call_spec_test", - size = "small", - timeout = "short", -) - test_suite( name = "workspace_helpers_tests", tests = [ ":go_workspace_multi_specs_test", ":go_workspace_single_specs_test", ":go_workspace_specs_default_to_config_gated_test", - ":orchestrion_call_spec_test", - ":orchestrion_pin_file_call_spec_test", ], ) @@ -284,12 +276,6 @@ go_stub_includes_manifest_in_files_test( timeout = "short", ) -orchestrion_metadata_enabled_test( - name = "orchestrion_metadata_enabled_test", - size = "small", - timeout = "short", -) - selector_payload_fixture_targets() selector_omits_flaky_tests_target( @@ -495,6 +481,11 @@ go_macro_inferred_importpath_metadata_test( target_under_test = ":go_macro_single_service_target_topt_bazel_metadata", ) +go_macro_instrumentation_metadata_test( + name = "go_macro_instrumentation_metadata_test", + target_under_test = ":go_macro_single_service_target_topt_bazel_metadata", +) + go_macro_public_wrapper_test( name = "go_macro_public_wrapper_test", target_under_test = ":go_macro_single_service_target", @@ -605,51 +596,6 @@ go_macro_stage_sources_select_wiring_test( target_under_test = ":go_macro_stage_sources_select_target__raw_go_test", ) -go_macro_orchestrion_pin_files_target( - name = "go_macro_orchestrion_pin_files_target", - tags = ["manual"], -) - -go_macro_orchestrion_pin_files_wiring_test( - name = "go_macro_orchestrion_pin_files_wiring_test", - target_under_test = ":go_macro_orchestrion_pin_files_target__raw_go_test", -) - -go_macro_orchestrion_pin_files_provider_test( - name = "go_macro_orchestrion_pin_files_provider_test", - target_under_test = ":go_macro_orchestrion_pin_files_target_orchestrion_pin_files", -) - -go_macro_test_optimization_mode_target( - name = "go_macro_test_optimization_mode_target", - tags = ["manual"], -) - -go_macro_test_optimization_mode_wiring_test( - name = "go_macro_test_optimization_mode_wiring_test", - target_under_test = ":go_macro_test_optimization_mode_target__raw_go_test", -) - -go_macro_test_optimization_public_wrapper_mode_target( - name = "go_macro_test_optimization_public_wrapper_mode_target", - tags = ["manual"], -) - -go_macro_test_optimization_public_wrapper_mode_test( - name = "go_macro_test_optimization_public_wrapper_mode_test", - target_under_test = ":go_macro_test_optimization_public_wrapper_mode_target", -) - -go_macro_default_general_public_wrapper_mode_target( - name = "go_macro_default_general_public_wrapper_mode_target", - tags = ["manual"], -) - -go_macro_default_general_public_wrapper_mode_test( - name = "go_macro_default_general_public_wrapper_mode_test", - target_under_test = ":go_macro_default_general_public_wrapper_mode_target", -) - go_macro_test_optimization_linker_default_target( name = "go_macro_test_optimization_linker_default_target", tags = ["manual"], @@ -695,16 +641,6 @@ go_macro_test_optimization_linker_metadata_opt_out_test( target_under_test = ":go_macro_test_optimization_linker_opt_out_target_topt_bazel_metadata", ) -go_macro_general_mode_linker_flags_target( - name = "go_macro_general_mode_linker_flags_target", - tags = ["manual"], -) - -go_macro_general_mode_linker_flags_wiring_test( - name = "go_macro_general_mode_linker_flags_wiring_test", - target_under_test = ":go_macro_general_mode_linker_flags_target__raw_go_test", -) - build_module_labels_invalid_shape_target_rule( name = "build_module_labels_invalid_shape_target", tags = ["manual"], @@ -765,24 +701,40 @@ resolve_topt_service_key_unknown_failure_test( target_under_test = ":resolve_topt_service_key_unknown_target", ) -validate_orchestrion_mode_invalid_target_rule( - name = "validate_orchestrion_mode_invalid_target", +# The Orchestrion attributes that 2.0.0 removed fail instead of reaching +# go_test. +reject_removed_orchestrion_attrs_target_rule( + name = "reject_removed_orchestrion_mode_target", + macro_attrs = [ + "orchestrion_mode", + "srcs", + ], tags = ["manual"], ) -validate_orchestrion_mode_invalid_failure_test( - name = "validate_orchestrion_mode_invalid_failure_test", - target_under_test = ":validate_orchestrion_mode_invalid_target", +reject_removed_orchestrion_mode_failure_test( + name = "reject_removed_orchestrion_mode_failure_test", + target_under_test = ":reject_removed_orchestrion_mode_target", ) -validate_test_optimization_pin_files_missing_go_mod_target_rule( - name = "validate_test_optimization_pin_files_missing_go_mod_target", +reject_removed_orchestrion_attrs_target_rule( + name = "reject_removed_orchestrion_attrs_target", + macro_attrs = [ + "orchestrion_mode", + "orchestrion_pin_files", + ], tags = ["manual"], ) -validate_test_optimization_pin_files_missing_go_mod_failure_test( - name = "validate_test_optimization_pin_files_missing_go_mod_failure_test", - target_under_test = ":validate_test_optimization_pin_files_missing_go_mod_target", +reject_removed_orchestrion_attrs_failure_test( + name = "reject_removed_orchestrion_attrs_failure_test", + target_under_test = ":reject_removed_orchestrion_attrs_target", +) + +reject_removed_orchestrion_attrs_test( + name = "reject_removed_orchestrion_attrs_test", + size = "small", + timeout = "short", ) go_macro_static_target( @@ -874,7 +826,6 @@ go_macro_static_identity_mismatch_failure_test( wrapper_output_name_target_rule( name = "wrapper_output_name_non_windows_target", - executable_basename = "hello_test__raw_go_test", is_windows = False, label_name = "hello_test", tags = ["manual"], @@ -887,7 +838,6 @@ wrapper_output_name_windows_test( wrapper_output_name_target_rule( name = "wrapper_output_name_windows_target", - executable_basename = "hello_test__raw_go_test.exe", is_windows = True, label_name = "hello_test", tags = ["manual"], @@ -898,60 +848,30 @@ wrapper_output_name_non_windows_test( target_under_test = ":wrapper_output_name_non_windows_target", ) -orch_wrapper_materialized_actual_non_windows_target( - name = "orch_wrapper_materialized_actual_non_windows_target", +ddtest_wrapper_materialized_actual_non_windows_target( + name = "ddtest_wrapper_materialized_actual_non_windows_target", tags = ["manual"], ) -orch_wrapper_materialized_actual_non_windows_test( - name = "orch_wrapper_materialized_actual_non_windows_test", - target_under_test = ":orch_wrapper_materialized_actual_non_windows_target", +ddtest_wrapper_materialized_actual_non_windows_test( + name = "ddtest_wrapper_materialized_actual_non_windows_test", + target_under_test = ":ddtest_wrapper_materialized_actual_non_windows_target", ) -orch_wrapper_materialized_actual_windows_target( - name = "orch_wrapper_materialized_actual_windows_target", +ddtest_wrapper_materialized_actual_windows_target( + name = "ddtest_wrapper_materialized_actual_windows_target", tags = ["manual"], ) -orch_wrapper_materialized_actual_windows_test( - name = "orch_wrapper_materialized_actual_windows_test", - target_under_test = ":orch_wrapper_materialized_actual_windows_target", +ddtest_wrapper_materialized_actual_windows_test( + name = "ddtest_wrapper_materialized_actual_windows_test", + target_under_test = ":ddtest_wrapper_materialized_actual_windows_target", ) windows_wrapper_uses_file_payload_mode_test( name = "windows_wrapper_uses_file_payload_mode_test", ) -has_package_local_go_mod_test( - name = "has_package_local_go_mod_test", - size = "small", - timeout = "short", -) - -has_go_mod_pin_test( - name = "has_go_mod_pin_test", - size = "small", - timeout = "short", -) - -validate_orchestrion_mode_test( - name = "validate_orchestrion_mode_test", - size = "small", - timeout = "short", -) - -orch_transition_forwards_mode_test( - name = "orch_transition_forwards_mode_test", - size = "small", - timeout = "short", -) - -stdlib_warmup_transition_selects_test_optimization_test( - name = "stdlib_warmup_transition_selects_test_optimization_test", - size = "small", - timeout = "short", -) - stdlib_warmup_tags_test( name = "stdlib_warmup_tags_test", size = "small", @@ -987,18 +907,25 @@ test_suite( ":build_module_labels_invalid_shape_failure_test", ":build_module_labels_unsanitized_entry_failure_test", ":build_module_labels_valid_test", + ":ddtest_base_sdk_version_test", + ":ddtest_contract_test", + ":ddtest_go_version_from_go_mod_test", + ":ddtest_platforms_test", + ":ddtest_sdk_config_test", + ":ddtest_sdk_versions_test", + ":ddtest_toolchains_build_content_test", + ":ddtest_wrapper_materialized_actual_non_windows_test", + ":ddtest_wrapper_materialized_actual_windows_test", ":go_macro_ci_visibility_opt_out_wiring_test", - ":go_macro_default_general_public_wrapper_mode_test", ":go_macro_disabled_raw_wiring_test", ":go_macro_dynamic_manifest_payloads_test", ":go_macro_dynamic_manifest_wiring_test", ":go_macro_env_none_wiring_test", ":go_macro_explicit_service_wiring_test", ":go_macro_inferred_importpath_metadata_test", + ":go_macro_instrumentation_metadata_test", ":go_macro_internal_helper_tags_tests", ":go_macro_multi_service_wiring_test", - ":go_macro_orchestrion_pin_files_provider_test", - ":go_macro_orchestrion_pin_files_wiring_test", ":go_macro_public_wrapper_test", ":go_macro_rundir_mismatch_wiring_test", ":go_macro_select_inputs_wiring_test", @@ -1022,8 +949,6 @@ test_suite( ":go_macro_test_optimization_linker_metadata_opt_test", ":go_macro_test_optimization_linker_metadata_strip_never_test", ":go_macro_test_optimization_linker_opt_out_wiring_test", - ":go_macro_test_optimization_mode_wiring_test", - ":go_macro_test_optimization_public_wrapper_mode_test", ":go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test", ":go_runtime_module_identifiers_test", ":go_single_spec_propagates_non_default_enablement_and_flaky_tests_test", @@ -1031,16 +956,12 @@ test_suite( ":go_stub_includes_manifest_in_files_test", ":go_workspace_multi_specs_test", ":go_workspace_single_specs_test", - ":has_go_mod_pin_test", - ":has_package_local_go_mod_test", ":hidden_raw_tags_test", ":normalize_user_data_handles_none_test", ":normalize_user_data_invalid_type_failure_test", - ":orch_transition_forwards_mode_test", - ":orch_wrapper_materialized_actual_non_windows_test", - ":orch_wrapper_materialized_actual_windows_test", - ":orchestrion_call_spec_test", - ":orchestrion_metadata_enabled_test", + ":reject_removed_orchestrion_attrs_failure_test", + ":reject_removed_orchestrion_attrs_test", + ":reject_removed_orchestrion_mode_failure_test", ":resolve_topt_service_key_missing_failure_test", ":resolve_topt_service_key_prefers_exact_then_sanitized_test", ":resolve_topt_service_key_unknown_failure_test", @@ -1065,11 +986,7 @@ test_suite( ":service_mapping_entries_filters_non_service_test", ":stdlib_warmup_tags_test", ":stdlib_warmup_target_has_outputs_test", - ":stdlib_warmup_transition_selects_test_optimization_test", ":test_execution_requirements_test", - ":validate_orchestrion_mode_invalid_failure_test", - ":validate_orchestrion_mode_test", - ":validate_test_optimization_pin_files_missing_go_mod_failure_test", ":windows_wrapper_uses_file_payload_mode_test", ":workspace_helpers_tests", ":wrapper_output_name_non_windows_test", diff --git a/modules/go/tests/compatibility/go.mod b/modules/go/tests/compatibility/go.mod deleted file mode 100644 index 9db8c9c9..00000000 --- a/modules/go/tests/compatibility/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module example.com/stub - -go 1.25.0 diff --git a/modules/go/tests/ddtest/BUILD.bazel b/modules/go/tests/ddtest/BUILD.bazel new file mode 100644 index 00000000..5d140e05 --- /dev/null +++ b/modules/go/tests/ddtest/BUILD.bazel @@ -0,0 +1,166 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under +# the Apache 2.0 License. +# +# This product includes software developed at Datadog +# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_cross_binary", "go_library") +load("//:topt_go_stdlib.bzl", "dd_topt_go_stdlib_warmup") +load("//:topt_go_test.bzl", "dd_topt_go_test") +load("//tests:test_macro.bzl", "stdlib_warmup_target_has_outputs_test") +load( + ":fixtures.bzl", + "TOPT_DATA", + "ddtest_transition_test", + "foreign_mini_failure_test", +) + +# Runs instrumented: it fails unless Mini set its context's trace. Its deps +# name testopt, which the macro then adds only once; its external test +# package receives the registration. +dd_topt_go_test( + name = "instrumented_test", + srcs = [ + "external_test.go", + "instrumented_test.go", + ], + importpath = "example.com/ddtestfixture", + topt_data = TOPT_DATA, + deps = [ + "@topt_go_ddtest_mini//propagation", + "@topt_go_ddtest_mini//testopt", + ], +) + +# Its configurable deps already name testopt, which the macro adds through an +# alias. +dd_topt_go_test( + name = "select_deps_test", + srcs = [ + "external_test.go", + "instrumented_test.go", + ], + importpath = "example.com/selectfixture", + topt_data = TOPT_DATA, + deps = select({ + "//conditions:default": [ + "@topt_go_ddtest_mini//propagation", + "@topt_go_ddtest_mini//testopt", + ], + }), +) + +# Its only source comes through embed, which the registration also reads. +go_library( + name = "widget", + srcs = ["widget.go"], + importpath = "example.com/widget", +) + +dd_topt_go_test( + name = "embed_only_test", + embed = [":widget"], + topt_data = TOPT_DATA, +) + +# Every source has a negated build constraint, which holds on this platform. +dd_topt_go_test( + name = "constrained_test", + srcs = ["constrained_test.go"], + importpath = "example.com/constrained", + topt_data = TOPT_DATA, +) + +# A copy of Mini's packages from another repository stops analysis. +dd_topt_go_test( + name = "foreign_mini_test", + srcs = ["foreign_test.go"], + importpath = "example.com/foreign", + tags = ["manual"], + topt_data = TOPT_DATA, + deps = ["//tests/ddtest/fake_mini"], +) + +foreign_mini_failure_test( + name = "foreign_mini_failure_test", + target_under_test = ":foreign_mini_test", +) + +ddtest_transition_test(name = "ddtest_transition_test") + +# Go binaries below an instrumented test build with the SDK that their +# sdk_version selects: unset or a prefix of the default SDK's version, or an +# SDK variant that the companion's MODULE.bazel configures. Each case has its +# own go_binary, whose runfiles path the other configurations would share. +# The cross binaries are manual because outside the test no regular SDK +# serves their sdk_version. +go_binary( + name = "go_version_tool", + srcs = ["go_version_tool.go"], +) + +go_binary( + name = "go_version_tool_for_1_26", + srcs = ["go_version_tool.go"], + tags = ["manual"], +) + +go_cross_binary( + name = "go_version_tool_1_26", + sdk_version = "1.26", + tags = ["manual"], + target = ":go_version_tool_for_1_26", +) + +go_binary( + name = "go_version_tool_for_fips", + srcs = ["go_version_tool.go"], + tags = ["manual"], +) + +go_cross_binary( + name = "go_version_tool_fips", + sdk_version = "1.25.9fips", + tags = ["manual"], + target = ":go_version_tool_for_fips", +) + +# An exec tool that imports testing builds with the instrumented SDK too. +go_binary( + name = "testing_tool", + srcs = ["testing_tool.go"], +) + +genrule( + name = "testing_tool_output", + outs = ["testing_tool_output.txt"], + cmd = "$(execpath :testing_tool) > $@", + tools = [":testing_tool"], +) + +dd_topt_go_test( + name = "sdk_variants_test", + srcs = ["sdk_variants_test.go"], + data = [ + ":go_version_tool", + ":go_version_tool_1_26", + ":go_version_tool_fips", + ":testing_tool_output", + ], + env = { + "GO_VERSION_TOOL": "$(rlocationpath :go_version_tool)", + "GO_VERSION_TOOL_1_26": "$(rlocationpath :go_version_tool_1_26)", + "GO_VERSION_TOOL_FIPS": "$(rlocationpath :go_version_tool_fips)", + "TESTING_TOOL_OUTPUT": "$(rlocationpath :testing_tool_output)", + }, + topt_data = TOPT_DATA, + deps = ["@io_bazel_rules_go//go/runfiles"], +) + +# The standard library that instrumented tests build, warmed on its own. +dd_topt_go_stdlib_warmup(name = "stdlib_warmup") + +stdlib_warmup_target_has_outputs_test( + name = "stdlib_warmup_has_outputs_test", + target_under_test = ":stdlib_warmup", +) diff --git a/tools/tests/rules_go_variant_regressions/tests/core/cgo/asm_cflags/asm_cflags_test.go b/modules/go/tests/ddtest/constrained_test.go similarity index 63% rename from tools/tests/rules_go_variant_regressions/tests/core/cgo/asm_cflags/asm_cflags_test.go rename to modules/go/tests/ddtest/constrained_test.go index 332d628b..e20aa60f 100644 --- a/tools/tests/rules_go_variant_regressions/tests/core/cgo/asm_cflags/asm_cflags_test.go +++ b/modules/go/tests/ddtest/constrained_test.go @@ -4,12 +4,11 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. -package asmcflags +//go:build !plan9 + +package constrained_test import "testing" -func TestAsmCflags(t *testing.T) { - if got, want := Value(), 123; got != want { - t.Fatalf("Value() = %d, want %d", got, want) - } -} +// Every source of constrained_test has a negated build constraint. +func TestConstrained(t *testing.T) {} diff --git a/tools/tests/rules_go_variant_regressions/tests/core/cgo/asm_cflags/asm_cflags.go b/modules/go/tests/ddtest/external_test.go similarity index 55% rename from tools/tests/rules_go_variant_regressions/tests/core/cgo/asm_cflags/asm_cflags.go rename to modules/go/tests/ddtest/external_test.go index 72bba881..bc552e7e 100644 --- a/tools/tests/rules_go_variant_regressions/tests/core/cgo/asm_cflags/asm_cflags.go +++ b/modules/go/tests/ddtest/external_test.go @@ -4,14 +4,9 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. -package asmcflags +package ddtestfixture_test -/* -extern int asm_cflags_value(void); -*/ -import "C" +import "testing" -// Value returns the sentinel integer emitted by the assembly helper. -func Value() int { - return int(C.asm_cflags_value()) -} +// The external test package makes the registration join it. +func TestExternal(t *testing.T) {} diff --git a/examples/single_service/src/go-project/orchestrion.yml b/modules/go/tests/ddtest/fake_mini/BUILD.bazel similarity index 52% rename from examples/single_service/src/go-project/orchestrion.yml rename to modules/go/tests/ddtest/fake_mini/BUILD.bazel index c223fcc6..a25d8588 100644 --- a/examples/single_service/src/go-project/orchestrion.yml +++ b/modules/go/tests/ddtest/fake_mini/BUILD.bazel @@ -4,10 +4,11 @@ # This product includes software developed at Datadog # (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. ---- -# yaml-language-server: $schema=https://datadoghq.dev/orchestrion/schema.json -meta: - name: datadog/go-bootstrap - description: Datadog starter configuration for Orchestrion. +load("@io_bazel_rules_go//go:def.bzl", "go_library") -aspects: [] +go_library( + name = "fake_mini", + srcs = ["fake.go"], + importpath = "github.com/DataDog/ddtest/ddto/fake", + visibility = ["//tests/ddtest:__pkg__"], +) diff --git a/modules/go/tests/ddtest/fake_mini/fake.go b/modules/go/tests/ddtest/fake_mini/fake.go new file mode 100644 index 00000000..61a63ab6 --- /dev/null +++ b/modules/go/tests/ddtest/fake_mini/fake.go @@ -0,0 +1,8 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under +// the Apache 2.0 License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +// Package fake stands for a copy of Mini's packages that go_deps provides. +package fake diff --git a/modules/go/tests/ddtest/fixtures.bzl b/modules/go/tests/ddtest/fixtures.bzl new file mode 100644 index 00000000..62bc3587 --- /dev/null +++ b/modules/go/tests/ddtest/fixtures.bzl @@ -0,0 +1,50 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under +# the Apache 2.0 License. +# +# This product includes software developed at Datadog +# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +"""Fixtures and analysis tests of the ddtest instrumentation.""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts", "unittest") +load("//:topt_go_ddtest.bzl", "ddtest_transition_impl_for_tests") + +# The stub sync repository's export, as the macro tests use it. +TOPT_DATA = { + "enabled": True, + "repo_name": "test_optimization_data", + "service_name": "go-service", + "manifest_path": ".testoptimization/manifest.txt", + "labels": [], + "set": {}, + "runtimes": { + "go": { + "module_path": "example.com/stub", + "sanitized_module_path": "example_com_stub", + "module_included": False, + }, + }, +} + +def _foreign_mini_failure_test_impl(ctx): + env = analysistest.begin(ctx) + asserts.expect_failure(env, "would link Mini's packages twice") + asserts.expect_failure(env, "override_repo(go_deps, com_github_datadog_ddtest_ddto") + asserts.expect_failure(env, "In WORKSPACE, depend on @topt_go_ddtest_mini labels") + return analysistest.end(env) + +foreign_mini_failure_test = analysistest.make( + _foreign_mini_failure_test_impl, + expect_failure = True, +) + +def _ddtest_transition_test_impl(ctx): + env = unittest.begin(ctx) + asserts.equals( + env, + {"@io_bazel_rules_go//go/toolchain:sdk_name": "ddto"}, + ddtest_transition_impl_for_tests(None, None), + ) + return unittest.end(env) + +ddtest_transition_test = unittest.make(_ddtest_transition_test_impl) diff --git a/modules/go/tests/ddtest/foreign_test.go b/modules/go/tests/ddtest/foreign_test.go new file mode 100644 index 00000000..c9314d00 --- /dev/null +++ b/modules/go/tests/ddtest/foreign_test.go @@ -0,0 +1,15 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under +// the Apache 2.0 License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +package foreign + +import ( + "testing" + + _ "github.com/DataDog/ddtest/ddto/fake" +) + +func TestForeign(t *testing.T) {} diff --git a/modules/go/tests/ddtest/go_version_tool.go b/modules/go/tests/ddtest/go_version_tool.go new file mode 100644 index 00000000..908e6894 --- /dev/null +++ b/modules/go/tests/ddtest/go_version_tool.go @@ -0,0 +1,18 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under +// the Apache 2.0 License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +// Command go_version_tool prints the Go version it was built with, with its +// GOEXPERIMENT values after X:. +package main + +import ( + "fmt" + "runtime" +) + +func main() { + fmt.Println(runtime.Version()) +} diff --git a/modules/go/tests/ddtest/instrumented_test.go b/modules/go/tests/ddtest/instrumented_test.go new file mode 100644 index 00000000..b504a5ca --- /dev/null +++ b/modules/go/tests/ddtest/instrumented_test.go @@ -0,0 +1,22 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under +// the Apache 2.0 License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +package ddtestfixture + +import ( + "testing" + + "github.com/DataDog/ddtest/ddto/propagation" + "github.com/DataDog/ddtest/ddto/testopt" +) + +// Mini gives a test's context its trace only when ddtest instrumented the +// test's testing package and linked Mini. +func TestInstrumented(t *testing.T) { + if _, ok := propagation.FromContext(testopt.Context(t)); !ok { + t.Fatal("the test's context has no trace: the test is not instrumented") + } +} diff --git a/modules/go/tests/ddtest/sdk_variants_test.go b/modules/go/tests/ddtest/sdk_variants_test.go new file mode 100644 index 00000000..36a512ff --- /dev/null +++ b/modules/go/tests/ddtest/sdk_variants_test.go @@ -0,0 +1,53 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under +// the Apache 2.0 License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +package sdkvariants_test + +import ( + "os" + "os/exec" + "strings" + "testing" + + "github.com/bazelbuild/rules_go/go/runfiles" +) + +func runfile(t *testing.T, env string) string { + t.Helper() + path, err := runfiles.Rlocation(os.Getenv(env)) + if err != nil { + t.Fatalf("%s: %v", env, err) + } + return path +} + +// The companion's MODULE.bazel instruments Go 1.26.3 by default and Go +// 1.25.9 with boringcrypto for sdk_version "1.25.9fips". +func TestDataBinariesBuildWithTheRequestedSDK(t *testing.T) { + for env, want := range map[string]string{ + "GO_VERSION_TOOL": "go1.26.3", + "GO_VERSION_TOOL_1_26": "go1.26.3", + "GO_VERSION_TOOL_FIPS": "go1.25.9 X:boringcrypto", + } { + out, err := exec.Command(runfile(t, env)).Output() + if err != nil { + t.Fatalf("%s: %v", env, err) + } + if got := strings.TrimSpace(string(out)); got != want { + t.Errorf("%s printed %q, want %q", env, got, want) + } + } +} + +func TestExecToolImportingTestingRuns(t *testing.T) { + out, err := os.ReadFile(runfile(t, "TESTING_TOOL_OUTPUT")) + if err != nil { + t.Fatal(err) + } + if got := strings.TrimSpace(string(out)); got != "short=false" { + t.Errorf("testing_tool printed %q, want short=false", got) + } +} diff --git a/modules/go/tests/ddtest/testing_tool.go b/modules/go/tests/ddtest/testing_tool.go new file mode 100644 index 00000000..17c7ced1 --- /dev/null +++ b/modules/go/tests/ddtest/testing_tool.go @@ -0,0 +1,21 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under +// the Apache 2.0 License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +// Command testing_tool imports testing, as some generators do, and runs as an +// exec tool below an instrumented test. +package main + +import ( + "flag" + "fmt" + "testing" +) + +func main() { + testing.Init() + flag.Parse() + fmt.Printf("short=%t\n", testing.Short()) +} diff --git a/modules/go/tests/ddtest/widget.go b/modules/go/tests/ddtest/widget.go new file mode 100644 index 00000000..476c1da2 --- /dev/null +++ b/modules/go/tests/ddtest/widget.go @@ -0,0 +1,10 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under +// the Apache 2.0 License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +package widget + +// Name is the library that embed_only_test embeds. +func Name() string { return "widget" } diff --git a/modules/go/tests/go.mod b/modules/go/tests/go.mod deleted file mode 100644 index c710a2bd..00000000 --- a/modules/go/tests/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module example.com/orchestrion-pin-fixture - -go 1.21 diff --git a/modules/go/tests/orchestrion_stub_repo.bzl b/modules/go/tests/orchestrion_stub_repo.bzl deleted file mode 100644 index 1c5b85da..00000000 --- a/modules/go/tests/orchestrion_stub_repo.bzl +++ /dev/null @@ -1,16 +0,0 @@ -# Unless explicitly stated otherwise all files in this repository are licensed under -# the Apache 2.0 License. -# -# This product includes software developed at Datadog -# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. - -"""Thin aliases to the root dev orchestrion stub extension.""" - -load( - "@datadog-rules-test-optimization//tools/dev:orchestrion_stub.bzl", - _orchestrion_stub_extension = "orchestrion_stub_extension", - _orchestrion_stub_repo = "orchestrion_stub_repo", -) - -orchestrion_stub_extension = _orchestrion_stub_extension -orchestrion_stub_repo = _orchestrion_stub_repo diff --git a/modules/go/tests/test_ddtest_repository.bzl b/modules/go/tests/test_ddtest_repository.bzl new file mode 100644 index 00000000..829fe371 --- /dev/null +++ b/modules/go/tests/test_ddtest_repository.bzl @@ -0,0 +1,176 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under +# the Apache 2.0 License. +# +# This product includes software developed at Datadog +# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +"""Unit tests for the ddtest repositories' pure helpers.""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load("//:topt_go_ddtest_extension.bzl", "sdk_config") +load( + "//:topt_go_ddtest_repository.bzl", + "DDTEST_RELEASE", + "PLATFORMS", + "base_sdk_version_error", + "contract_error", + "ddtest_asset", + "go_version_from_go_mod", + "host_platform", + "parse_contract", + "sdk_variant_id", + "sdk_variants_error", + "sdk_version_matches", + "toolchains_build_content", +) + +def _go_version_from_go_mod_test(ctx): + env = unittest.begin(ctx) + asserts.equals(env, "1.26.3", go_version_from_go_mod("module x\n\ngo 1.25.0\n\ntoolchain go1.26.3\n")) + asserts.equals(env, "1.25.4", go_version_from_go_mod("module x\ngo 1.25.4\n")) + asserts.equals(env, "1.25.0", go_version_from_go_mod("module x\ngo 1.25\n")) + asserts.equals(env, "1.20", go_version_from_go_mod("module x\ngo 1.20\n")) + asserts.equals(env, None, go_version_from_go_mod("module x\n")) + return unittest.end(env) + +go_version_from_go_mod_test = unittest.make(_go_version_from_go_mod_test) + +def _contract_test(ctx): + env = unittest.begin(ctx) + good = "version=%s\ncontract=%s\nsdk_name=%s\n" % (DDTEST_RELEASE.runtime_version, DDTEST_RELEASE.contract, DDTEST_RELEASE.sdk_name) + asserts.equals(env, None, contract_error(parse_contract(good), "test")) + + # The release tag is not what the runtime records. + tagged = good.replace(DDTEST_RELEASE.runtime_version, DDTEST_RELEASE.tag) + asserts.true(env, "version" in contract_error(parse_contract(tagged), "test")) + asserts.true(env, "contract" in contract_error(parse_contract(good.replace("contract=1", "contract=2")), "test")) + asserts.true(env, "sdk_name" in contract_error(parse_contract(good.replace("sdk_name=ddto", "sdk_name=go")), "test")) + asserts.true(env, "contract" in contract_error(parse_contract(""), "test")) + return unittest.end(env) + +contract_test = unittest.make(_contract_test) + +def _platforms_test(ctx): + env = unittest.begin(ctx) + asserts.equals(env, "ddtest-windows-arm64.exe", ddtest_asset("windows_arm64")) + asserts.equals(env, "ddtest-linux-amd64", ddtest_asset("linux_amd64")) + asserts.equals(env, "darwin_arm64", host_platform("Mac OS X", "aarch64")) + asserts.equals(env, "linux_amd64", host_platform("linux", "amd64")) + asserts.equals(env, "windows_amd64", host_platform("Windows 11", "amd64")) + asserts.equals(env, None, host_platform("freebsd", "amd64")) + asserts.equals(env, None, host_platform("linux", "s390x")) + for platform in PLATFORMS: + asserts.true(env, platform in DDTEST_RELEASE.sha256, "no checksum for %s" % platform) + return unittest.end(env) + +platforms_test = unittest.make(_platforms_test) + +def _toolchains_build_content_test(ctx): + env = unittest.begin(ctx) + content = toolchains_build_content([ + {"id": "", "matches": sdk_version_matches("1.26.3"), "exec_platforms": ["linux_amd64"]}, + {"id": "v1_26_3fips", "matches": ["1.26.3fips"], "exec_platforms": ["linux_amd64", "linux_arm64"]}, + ]) + + # One toolchain per SDK, exec and target platform, and the catch-all. + asserts.equals(env, 3 * len(PLATFORMS) + 1, content.count("toolchain(")) + asserts.true(env, "toolchain = \"@topt_go_ddtest_sdk_linux_amd64//:go_darwin_arm64-impl\"" in content) + asserts.true(env, "toolchain = \"@topt_go_ddtest_sdk_v1_26_3fips_linux_arm64//:go_darwin_arm64-impl\"" in content) + asserts.true(env, "exec_compatible_with = [\"@platforms//os:linux\", \"@platforms//cpu:x86_64\"]" in content) + asserts.equals(env, len(PLATFORMS), content.count("target_settings = [\":sdk_name_ddto\", \":sdk_version\"]")) + asserts.equals(env, 2 * len(PLATFORMS), content.count("target_settings = [\":sdk_name_ddto\", \":v1_26_3fips_sdk_version\"]")) + asserts.true(env, "\"@io_bazel_rules_go//go/toolchain:sdk_name\": \"ddto\"" in content) + for value in ["", "1", "1.26", "1.26.3", "1.26.3fips"]: + asserts.true(env, "\"@io_bazel_rules_go//go/toolchain:sdk_version\": \"%s\"" % value in content, "no setting for %r" % value) + + # The catch-all matches only a value that no SDK serves, whatever the + # order of registration and of exec platforms. + asserts.true(env, "target_settings = [\":sdk_name_ddto\", \":sdk_version_unserved\"]" in content) + asserts.true(env, "\":sdk_version_served\": \":sdk_name_unset\"" in content) + served = content.split("name = \"sdk_version_served\",")[1].split(")")[0] + asserts.equals(env, 5, served.count(":")) + asserts.true(env, "No instrumented Go SDK matches sdk_version \\\"{sdk_version}\\\"" in content) + asserts.true(env, "sdk_version \\\"1.26.3fips\\\" on linux_amd64, linux_arm64" in content) + + unconfigured = toolchains_build_content([]) + asserts.equals(env, 1, unconfigured.count("toolchain(")) + asserts.true(env, "topt_go_ddtest_unconfigured(" in unconfigured) + asserts.true(env, "target_settings = [\":sdk_name_ddto\"]" in unconfigured) + asserts.true(env, "selects" not in unconfigured) + return unittest.end(env) + +toolchains_build_content_test = unittest.make(_toolchains_build_content_test) + +def _sdk_versions_test(ctx): + env = unittest.begin(ctx) + asserts.equals(env, ["", "1", "1.26", "1.26.3"], sdk_version_matches("1.26.3")) + asserts.equals(env, ["", "1", "1.26", "1.26.0", "1.26.0rc2", "1.26rc2"], sdk_version_matches("1.26rc2")) + asserts.equals(env, [""], sdk_version_matches(None)) + asserts.equals(env, "v1_26_3fips", sdk_variant_id("1.26.3fips")) + asserts.equals(env, None, sdk_variants_error("1.26.3", [{"sdk_version": "1.25.9"}, {"sdk_version": "1.26.3fips", "experiments": ["boringcrypto"]}])) + + # A value that the default SDK serves, two variants with one repository + # name, a variant without sdk_version and an unknown field. + asserts.true(env, "already selects the default SDK" in sdk_variants_error("1.26.3", [{"sdk_version": "1.26"}])) + asserts.true(env, "distinct repository names" in sdk_variants_error("1.26.3", [{"sdk_version": "1.25.9"}, {"sdk_version": "1_25_9"}])) + asserts.true(env, "needs the sdk_version" in sdk_variants_error("1.26.3", [{"go_version": "1.25.9"}])) + asserts.true(env, "unknown SDK variant field \"version\"" in sdk_variants_error("1.26.3", [{"version": "1.25.9"}])) + return unittest.end(env) + +sdk_versions_test = unittest.make(_sdk_versions_test) + +def _base_sdk_version_test(ctx): + env = unittest.begin(ctx) + asserts.equals(env, None, base_sdk_version_error("go1.26.3\ntime 2026-01-01\n", "1.26.3", "linux_amd64", "")) + asserts.equals(env, None, base_sdk_version_error("go1.26.3 X:boringcrypto\n", "1.26.3", "linux_amd64", "1.26.3fips")) + + # Without go_version, or a VERSION file to read, there is nothing to check. + asserts.equals(env, None, base_sdk_version_error("go1.25.9\n", "", "linux_amd64", "")) + asserts.equals(env, None, base_sdk_version_error("", "1.26.3", "linux_amd64", "")) + error = base_sdk_version_error("go1.25.9\n", "1.26.3", "linux_amd64", "") + asserts.true(env, "default SDK's base SDK for linux_amd64 is Go 1.25.9, but its go_version is 1.26.3" in error) + asserts.true(env, "SDK variant \"1.26.3fips\"" in base_sdk_version_error("go1.25.9\n", "1.26.3", "linux_amd64", "1.26.3fips")) + return unittest.end(env) + +base_sdk_version_test = unittest.make(_base_sdk_version_test) + +def _sdk_tag(go_version = "", go_mod = None, sdk_version = ""): + return struct( + sdk_version = sdk_version, + go_version = go_version, + go_mod = go_mod, + base_sdk_roots = {}, + exec_platforms = [], + experiments = ["nocoverageredesign"], + panic_hook = True, + ) + +def _sdk_config_test(ctx): + env = unittest.begin(ctx) + asserts.equals(env, None, sdk_config([], None)) + config = sdk_config([_sdk_tag(go_version = "1.26.3")], None) + asserts.equals(env, "1.26.3", config.go_version) + asserts.equals(env, None, config.exec_platforms) + asserts.equals(env, ["nocoverageredesign"], config.experiments) + asserts.true(env, config.panic_hook) + config = sdk_config([_sdk_tag(go_mod = "//:go.mod")], lambda label: "module x\ngo 1.25\n") + asserts.equals(env, "1.25.0", config.go_version) + + # go_version overrides go_mod. + config = sdk_config([_sdk_tag(go_version = "1.27.1", go_mod = "//:go.mod")], lambda label: "module x\ngo 1.25\n") + asserts.equals(env, "1.27.1", config.go_version) + asserts.equals(env, [], config.sdk_variants) + + # The tag without sdk_version is the default SDK, wherever it is. + config = sdk_config([ + _sdk_tag(sdk_version = "1.26.3fips", go_version = "1.26.3"), + _sdk_tag(go_version = "1.27.1"), + ], None) + asserts.equals(env, "1.27.1", config.go_version) + asserts.equals(env, 1, len(config.sdk_variants)) + asserts.equals(env, "1.26.3fips", config.sdk_variants[0]["sdk_version"]) + asserts.equals(env, "1.26.3", config.sdk_variants[0]["go_version"]) + asserts.equals(env, ["nocoverageredesign"], config.sdk_variants[0]["experiments"]) + return unittest.end(env) + +sdk_config_test = unittest.make(_sdk_config_test) diff --git a/modules/go/tests/test_macro.bzl b/modules/go/tests/test_macro.bzl index 28b8b6bd..6160f621 100644 --- a/modules/go/tests/test_macro.bzl +++ b/modules/go/tests/test_macro.bzl @@ -19,51 +19,51 @@ forwards at analysis time without compiling Go code. """ load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts", "loadingtest", "unittest") -load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") +load( + "@datadog-rules-test-optimization-go//:topt_go_ddtest.bzl", + "MINI_TESTOPT", + "ddtest_go_test", + "test_execution_requirements_for_tests", + "windows_wrapper_content_for_tests", + "wrapper_output_name_for_tests", +) +load( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_repository.bzl", + "DDTEST_RELEASE", +) load( "@datadog-rules-test-optimization-go//:topt_go_infer.bzl", "ToptGoBazelMetadataInfo", "topt_go_payloads_selector", ) -load( - "@datadog-rules-test-optimization-go//:topt_go_orchestrion.bzl", - "orch_go_test", - "orch_transition_impl_for_tests", - "select_wrapper_output_name_for_tests", - "test_execution_requirements_for_tests", - "windows_wrapper_content_for_tests", -) load( "@datadog-rules-test-optimization-go//:topt_go_stdlib.bzl", "stdlib_warmup_tags_for_tests", - "stdlib_warmup_transition_impl_for_tests", ) load( "@datadog-rules-test-optimization-go//:topt_go_test.bzl", "dd_topt_go_test", - "has_go_mod_pin_for_tests", - "has_package_local_go_mod_for_tests", "hidden_raw_tags_for_tests", + "reject_removed_orchestrion_attrs_for_tests", "resolve_topt_service_key_for_tests", - "validate_orchestrion_mode_for_tests", - "validate_test_optimization_pin_files_for_tests", ) -load("@rules_go//go/private/orchestrion:pin_files.bzl", "OrchestrionPinFilesInfo") ToptGoMacroCaptureInfo = provider( doc = "Captured arguments forwarded by dd_topt_go_test to the underlying go_test rule.", fields = { "data_labels": "Forwarded data dependency labels.", + "deps_labels": "Forwarded Go dependency labels.", "env": "Forwarded environment map.", "gc_linkopts": "Forwarded Go linker options.", "importpath": "Forwarded importpath attribute.", "rundir": "Forwarded runtime working directory.", + "srcs_labels": "Forwarded source labels.", "tags": "Forwarded raw-test tags.", }, ) WrapperOutputNameInfo = provider( - doc = "Computed wrapper output file name for Orchestrion wrapper tests.", + doc = "Computed launcher file name for ddtest_go_test wrapper tests.", fields = { "output_name": "The output file name selected by the wrapper helper.", }, @@ -82,10 +82,12 @@ def _go_test_capture_impl(ctx): RunEnvironmentInfo(environment = dict(ctx.attr.env)), ToptGoMacroCaptureInfo( data_labels = [str(dep.label) for dep in ctx.attr.data], + deps_labels = [str(dep.label) for dep in ctx.attr.deps], env = dict(ctx.attr.env), gc_linkopts = list(ctx.attr.gc_linkopts), importpath = ctx.attr.importpath, rundir = ctx.attr.rundir, + srcs_labels = [str(src.label) for src in ctx.attr.srcs], tags = list(ctx.attr.tags), ), ] @@ -118,6 +120,8 @@ _go_test_capture_rule = rule( implementation = _go_test_capture_impl, attrs = { "data": attr.label_list(allow_files = True), + # dd_topt_go_test adds Mini's testopt here. + "deps": attr.label_list(), "embed": attr.label_list(), "embedsrcs": attr.label_list(allow_files = True), "env": attr.string_dict(), @@ -129,38 +133,6 @@ _go_test_capture_rule = rule( executable = True, ) -def _go_test_transition_mode_impl(ctx): - """Expose the transitioned Orchestrion mode through the executable basename.""" - mode = ctx.attr._orchestrion_mode[BuildSettingInfo].value - out = ctx.actions.declare_file(ctx.label.name + "__orchestrion_mode_" + mode + ".sh") - ctx.actions.write(out, "#!/bin/sh\nexit 0\n", is_executable = True) - return [ - DefaultInfo( - files = depset([out]), - runfiles = ctx.runfiles(files = [out]), - executable = out, - ), - ] - -_go_test_transition_mode_rule = rule( - implementation = _go_test_transition_mode_impl, - attrs = { - "data": attr.label_list(allow_files = True), - "embed": attr.label_list(), - "embedsrcs": attr.label_list(allow_files = True), - "env": attr.string_dict(), - "gc_linkopts": attr.string_list(), - "importpath": attr.string(), - "rundir": attr.string(), - "srcs": attr.label_list(allow_files = True), - "_orchestrion_mode": attr.label( - default = "@rules_go//go/private/orchestrion:mode", - providers = [BuildSettingInfo], - ), - }, - executable = True, -) - def _fake_executable_impl(ctx): """Create a lightweight executable target for wrapper analysis tests.""" out = ctx.actions.declare_file(ctx.attr.executable_name) @@ -190,9 +162,8 @@ fake_metadata_rule = rule(implementation = _fake_metadata_impl) def _wrapper_output_name_target_impl(ctx): return [WrapperOutputNameInfo( - output_name = select_wrapper_output_name_for_tests( + output_name = wrapper_output_name_for_tests( ctx.attr.label_name, - ctx.attr.executable_basename, ctx.attr.is_windows, ), )] @@ -201,7 +172,6 @@ wrapper_output_name_target_rule = rule( implementation = _wrapper_output_name_target_impl, attrs = { "label_name": attr.string(mandatory = True), - "executable_basename": attr.string(mandatory = True), "is_windows": attr.bool(mandatory = True), }, ) @@ -379,6 +349,7 @@ def go_macro_internal_helper_tags_loading_tests( for helper_kind, suffix in [ ("payloads", "_topt_payloads"), ("metadata", "_topt_bazel_metadata"), + ("registration", "_ddtest_registration"), ]: helper = native.existing_rule(target_name + suffix) loadingtest.equals( @@ -402,6 +373,12 @@ def go_macro_internal_helper_tags_loading_tests( None, native.existing_rule(disabled_target + "_topt_bazel_metadata"), ) + loadingtest.equals( + env, + "disabled_registration_helper_absent", + None, + native.existing_rule(disabled_target + "_ddtest_registration"), + ) def go_macro_helper_compatibility_fixture(name): """Exercise caller constraints on every independently selectable target.""" @@ -426,16 +403,18 @@ def go_macro_helper_compatibility_fixture(name): ("composed", {"target_compatible_with": ["@platforms//os:linux"] + gate}), ]: target_name = mode + "_" + case + + # Configurable deps make the macro add Mini through an alias. dd_topt_go_test( name = target_name, topt_data = descriptor, go_test_rule = _go_test_capture_rule, - orchestrion_pin_files = [":go.mod"], tags = ["manual"], + deps = select({"//conditions:default": []}), **attrs ) expected = native.existing_rule(target_name).get("target_compatible_with", ()) - for suffix in ["__raw_go_test", "_topt_payloads", "_topt_bazel_metadata"]: + for suffix in ["__raw_go_test", "_topt_payloads", "_topt_bazel_metadata", "_ddtest_registration", "_ddtest_mini"]: actual = native.existing_rule(target_name + suffix).get("target_compatible_with", ()) loadingtest.equals(env, target_name + suffix, str(expected), str(actual)) @@ -448,8 +427,8 @@ def go_macro_helper_compatibility_fixture(name): service_name = "static-disabled-service", ), go_test_rule = _go_test_capture_rule, - orchestrion_pin_files = [":go.mod"], tags = ["manual"], + deps = select({"//conditions:default": []}), target_compatible_with = select({ ":enabled": [], "//conditions:default": ["@platforms//:incompatible"], @@ -470,7 +449,7 @@ def go_macro_helper_compatibility_fixture(name): str(native.existing_rule("static_select")["target_compatible_with"]), str(native.existing_rule("disabled_export")["target_compatible_with"]), ) - for suffix in ["__raw_go_test", "_topt_payloads", "_topt_bazel_metadata"]: + for suffix in ["__raw_go_test", "_topt_payloads", "_topt_bazel_metadata", "_ddtest_registration"]: loadingtest.equals(env, "disabled_export_absent" + suffix, None, native.existing_rule("disabled_export" + suffix)) def go_macro_disabled_raw_target(name, tags = None): @@ -604,90 +583,29 @@ def go_macro_stage_sources_select_target(name, tags = None): tags = tags, ) -def go_macro_orchestrion_pin_files_target(name, tags = None): - """Target under test for explicit module-root Orchestrion pin-file labels.""" - dd_topt_go_test( - name = name, - topt_data = _single_service_topt_data(), - go_test_rule = _go_test_capture_rule, - orchestrion_pin_files = [ - ":test_macro.bzl", - ":test_selection_utils.bzl", - ], - tags = tags, - ) - -def go_macro_test_optimization_mode_target(name, tags = None): - """Target under test for opt-in Test Optimization Orchestrion mode.""" - dd_topt_go_test( - name = name, - topt_data = _single_service_topt_data(), - go_test_rule = _go_test_capture_rule, - data = [":test_macro.bzl"], - orchestrion_mode = "test_optimization", - orchestrion_pin_files = [":go.mod"], - tags = tags, - ) - -def go_macro_test_optimization_public_wrapper_mode_target(name, tags = None): - """Target under test for public wrapper Orchestrion mode forwarding.""" - dd_topt_go_test( - name = name, - topt_data = _single_service_topt_data(), - go_test_rule = _go_test_transition_mode_rule, - orchestrion_mode = "test_optimization", - orchestrion_pin_files = [":go.mod"], - tags = tags, - ) - -def go_macro_default_general_public_wrapper_mode_target(name, tags = None): - """Target under test for omitted Orchestrion mode defaulting to general.""" - dd_topt_go_test( - name = name, - topt_data = _single_service_topt_data(), - go_test_rule = _go_test_transition_mode_rule, - tags = tags, - ) - def go_macro_test_optimization_linker_default_target(name, tags = None): - """Target under test for default Test Optimization linker flags.""" + """Target under test for the default test-binary linker flags.""" dd_topt_go_test( name = name, topt_data = _single_service_topt_data(), go_test_rule = _go_test_capture_rule, data = [":test_macro.bzl"], - orchestrion_mode = "test_optimization", - orchestrion_pin_files = [":go.mod"], tags = tags, ) def go_macro_test_optimization_linker_opt_out_target(name, tags = None): - """Target under test for disabling Test Optimization linker flags.""" + """Target under test for disabling the test-binary linker flags.""" dd_topt_go_test( name = name, topt_data = _single_service_topt_data(), go_test_rule = _go_test_capture_rule, data = [":test_macro.bzl"], - orchestrion_mode = "test_optimization", - orchestrion_pin_files = [":go.mod"], enable_test_binary_linker_optimization = False, gc_linkopts = ["-custom-link-flag"], tags = tags, ) -def go_macro_general_mode_linker_flags_target(name, tags = None): - """Target under test for preserving linker flags outside Test Optimization.""" - dd_topt_go_test( - name = name, - topt_data = _single_service_topt_data(), - go_test_rule = _go_test_capture_rule, - data = [":test_macro.bzl"], - orchestrion_mode = "general", - gc_linkopts = ["-custom-link-flag"], - tags = tags, - ) - -def orch_wrapper_materialized_actual_non_windows_target(name, tags = None): +def ddtest_wrapper_materialized_actual_non_windows_target(name, tags = None): """Target under test for non-Windows sibling executable materialization.""" fake_executable_rule( name = name + "_actual", @@ -698,14 +616,14 @@ def orch_wrapper_materialized_actual_non_windows_target(name, tags = None): name = name + "_metadata", tags = ["manual"], ) - orch_go_test( + ddtest_go_test( name = name, actual = ":" + name + "_actual", metadata = ":" + name + "_metadata", tags = tags, ) -def orch_wrapper_materialized_actual_windows_target(name, tags = None): +def ddtest_wrapper_materialized_actual_windows_target(name, tags = None): """Target under test for Windows sibling executable materialization.""" fake_executable_rule( name = name + "_actual", @@ -717,7 +635,7 @@ def orch_wrapper_materialized_actual_windows_target(name, tags = None): name = name + "_metadata", tags = ["manual"], ) - orch_go_test( + ddtest_go_test( name = name, actual = ":" + name + "_actual", metadata = ":" + name + "_metadata", @@ -735,6 +653,9 @@ def _go_macro_single_service_wiring_test_impl(ctx): asserts.true(env, _has_label_suffix(captured.data_labels, ":test_macro.bzl")) asserts.true(env, _has_fragment(captured.data_labels, "test_optimization_data")) asserts.true(env, _has_label_suffix(captured.data_labels, ":.testoptimization/manifest.txt")) + asserts.equals(env, [str(MINI_TESTOPT)], captured.deps_labels) + asserts.equals(env, 1, len(captured.srcs_labels)) + asserts.true(env, _has_label_suffix(captured.srcs_labels, ":go_macro_single_service_target_ddtest_registration")) manifest_env = captured.env.get("DD_TEST_OPTIMIZATION_MANIFEST_FILE") asserts.true(env, manifest_env != None) @@ -770,6 +691,18 @@ def _go_macro_inferred_importpath_metadata_test_impl(ctx): asserts.equals(env, "fallback", metadata["bazel.go.importpath_source"]) return analysistest.end(env) +def _go_macro_instrumentation_metadata_test_impl(ctx): + """Assert metadata names the pinned ddtest release and no Orchestrion state.""" + env = analysistest.begin(ctx) + metadata = analysistest.target_under_test(env)[ToptGoBazelMetadataInfo].metadata + asserts.equals(env, "ddtest", metadata["bazel.go.instrumentation.backend"]) + asserts.equals(env, DDTEST_RELEASE.runtime_version, metadata["bazel.go.instrumentation.version"]) + asserts.equals(env, DDTEST_RELEASE.tag, metadata["bazel.go.instrumentation.release"]) + asserts.equals(env, int(DDTEST_RELEASE.contract), metadata["bazel.go.instrumentation.contract"]) + asserts.equals(env, DDTEST_RELEASE.sdk_name, metadata["bazel.go.sdk.name"]) + asserts.equals(env, [], [key for key in metadata if key.startswith("bazel.go.orchestrion.")]) + return analysistest.end(env) + def _go_macro_disabled_raw_wiring_test_impl(ctx): """Assert disabled metadata forwards caller kwargs to one raw public test.""" env = analysistest.begin(ctx) @@ -825,7 +758,7 @@ def _go_macro_dynamic_manifest_payloads_test_impl(ctx): return analysistest.end(env) def _go_macro_static_wiring_test_impl(ctx): - """Assert static descriptors use stable repository labels and TO mode.""" + """Assert static descriptors use stable repository labels and raw-test tags.""" env = analysistest.begin(ctx) captured = analysistest.target_under_test(env)[ToptGoMacroCaptureInfo] asserts.true(env, _has_label_suffix(captured.data_labels, ":go_macro_static_target_topt_payloads")) @@ -840,7 +773,7 @@ def _go_macro_static_fallback_metadata_test_impl(ctx): env = analysistest.begin(ctx) metadata = analysistest.target_under_test(env)[ToptGoBazelMetadataInfo].metadata asserts.equals(env, "full_bundle_disabled", metadata["bazel.go.payload_selection"]) - asserts.equals(env, "test_optimization", metadata["bazel.go.orchestrion.mode"]) + asserts.equals(env, "ddtest", metadata["bazel.go.instrumentation.backend"]) return analysistest.end(env) def _go_macro_static_module_metadata_test_impl(ctx): @@ -998,45 +931,6 @@ def _go_macro_stage_sources_select_wiring_test_impl(ctx): asserts.equals(env, ".", captured.rundir) return analysistest.end(env) -def _go_macro_orchestrion_pin_files_wiring_test_impl(ctx): - """Assert explicit Orchestrion pin files are forwarded through the provider target.""" - env = analysistest.begin(ctx) - target = analysistest.target_under_test(env) - captured = target[ToptGoMacroCaptureInfo] - asserts.true(env, _has_label_suffix(captured.data_labels, ":go_macro_orchestrion_pin_files_target_topt_payloads")) - asserts.true(env, _has_label_suffix(captured.data_labels, ":go_macro_orchestrion_pin_files_target_topt_bazel_metadata")) - asserts.true(env, _has_label_suffix(captured.data_labels, ":go_macro_orchestrion_pin_files_target_orchestrion_pin_files")) - asserts.false(env, _has_label_suffix(captured.data_labels, ":test_macro.bzl")) - asserts.false(env, _has_label_suffix(captured.data_labels, ":test_selection_utils.bzl")) - return analysistest.end(env) - -def _go_macro_orchestrion_pin_files_provider_test_impl(ctx): - """Assert the hidden pin-files target carries only Orchestrion pin files.""" - env = analysistest.begin(ctx) - target = analysistest.target_under_test(env) - pin_files = target[OrchestrionPinFilesInfo].files.to_list() - asserts.equals(env, 3, len(pin_files)) - asserts.true(env, _has_file_basename(pin_files, "go.mod")) - asserts.true(env, _has_file_basename(pin_files, "test_macro.bzl")) - asserts.true(env, _has_file_basename(pin_files, "test_selection_utils.bzl")) - return analysistest.end(env) - -def _go_macro_test_optimization_mode_wiring_test_impl(ctx): - """Assert opt-in mode preserves raw test runtime data and env wiring.""" - env = analysistest.begin(ctx) - target = analysistest.target_under_test(env) - captured = target[ToptGoMacroCaptureInfo] - asserts.true(env, _has_label_suffix(captured.data_labels, ":test_macro.bzl")) - asserts.true(env, _has_label_suffix(captured.data_labels, ":go_macro_test_optimization_mode_target_topt_payloads")) - asserts.true(env, _has_label_suffix(captured.data_labels, ":go_macro_test_optimization_mode_target_topt_bazel_metadata")) - asserts.true(env, _has_label_suffix(captured.data_labels, ":go_macro_test_optimization_mode_target_orchestrion_pin_files")) - asserts.true(env, _has_label_suffix(captured.data_labels, ":.testoptimization/manifest.txt")) - asserts.equals(env, "$(rlocationpath @test_optimization_data//:.testoptimization/manifest.txt)", captured.env.get("DD_TEST_OPTIMIZATION_MANIFEST_FILE")) - asserts.equals(env, "go_macro_test_optimization_mode_target_topt_bazel_metadata.json", captured.env.get("DD_TEST_OPTIMIZATION_BAZEL_TARGET_METADATA_BASENAME")) - asserts.equals(env, "true", captured.env.get("DD_TEST_OPTIMIZATION_PAYLOADS_IN_FILES")) - asserts.equals(env, "true", captured.env.get("DD_CIVISIBILITY_ENABLED")) - return analysistest.end(env) - def _go_macro_test_optimization_linker_opt_out_wiring_test_impl(ctx): """Assert opt-out preserves caller-provided linker flags exactly.""" env = analysistest.begin(ctx) @@ -1046,7 +940,7 @@ def _go_macro_test_optimization_linker_opt_out_wiring_test_impl(ctx): return analysistest.end(env) def _go_macro_test_optimization_linker_default_wiring_test_impl(ctx): - """Assert optimized tests receive default strip linker flags in opt mode.""" + """Assert tests receive the default strip linker flags in opt mode.""" env = analysistest.begin(ctx) target = analysistest.target_under_test(env) captured = target[ToptGoMacroCaptureInfo] @@ -1069,14 +963,6 @@ def _go_macro_test_optimization_linker_metadata_false_test_impl(ctx): asserts.equals(env, False, metadata["bazel.go.test_binary_linker_optimization"]) return analysistest.end(env) -def _go_macro_general_mode_linker_flags_wiring_test_impl(ctx): - """Assert non-Test Optimization mode preserves caller-provided linker flags.""" - env = analysistest.begin(ctx) - target = analysistest.target_under_test(env) - captured = target[ToptGoMacroCaptureInfo] - asserts.equals(env, ["-custom-link-flag"], captured.gc_linkopts) - return analysistest.end(env) - def _go_macro_explicit_service_wiring_test_impl(ctx): """Assert explicit caller DD_SERVICE is preserved.""" env = analysistest.begin(ctx) @@ -1121,42 +1007,6 @@ def _go_macro_public_wrapper_test_impl(ctx): asserts.equals(env, "1", run_env.get("CUSTOM_ENV")) return analysistest.end(env) -def _go_macro_test_optimization_public_wrapper_mode_test_impl(ctx): - """Assert dd_topt_go_test forwards the optimized mode to the public wrapper.""" - env = analysistest.begin(ctx) - target = analysistest.target_under_test(env) - files = target[DefaultInfo].files.to_list() - asserts.true( - env, - _has_file_basename( - files, - ( - "go_macro_test_optimization_public_wrapper_mode_target__wrapped_" + - "go_macro_test_optimization_public_wrapper_mode_target__raw_go_test" + - "__orchestrion_mode_test_optimization.sh" - ), - ), - ) - return analysistest.end(env) - -def _go_macro_default_general_public_wrapper_mode_test_impl(ctx): - """Assert omitted orchestrion_mode forwards the default general mode.""" - env = analysistest.begin(ctx) - target = analysistest.target_under_test(env) - files = target[DefaultInfo].files.to_list() - asserts.true( - env, - _has_file_basename( - files, - ( - "go_macro_default_general_public_wrapper_mode_target__wrapped_" + - "go_macro_default_general_public_wrapper_mode_target__raw_go_test" + - "__orchestrion_mode_general.sh" - ), - ), - ) - return analysistest.end(env) - def _resolve_topt_service_key_missing_target_impl(_ctx): """Analysis target expected to fail on missing service in multi-service map.""" resolve_topt_service_key_for_tests( @@ -1179,19 +1029,9 @@ def _resolve_topt_service_key_unknown_target_impl(_ctx): ) return [] -def _validate_orchestrion_mode_invalid_target_impl(_ctx): - """Analysis target expected to fail on invalid Orchestrion mode.""" - validate_orchestrion_mode_for_tests("invalid") - return [] - -def _validate_test_optimization_pin_files_missing_go_mod_target_impl(_ctx): - """Analysis target expected to fail when optimized mode has no go.mod pin.""" - validate_test_optimization_pin_files_for_tests( - "test_optimization", - ["go.sum"], - [":test_selection_utils.bzl"], - [":test_selection_utils.bzl"], - ) +def _reject_removed_orchestrion_attrs_target_impl(ctx): + """Analysis target expected to fail on a removed Orchestrion attribute.""" + reject_removed_orchestrion_attrs_for_tests({name: None for name in ctx.attr.macro_attrs}) return [] resolve_topt_service_key_missing_target_rule = rule( @@ -1202,12 +1042,11 @@ resolve_topt_service_key_unknown_target_rule = rule( implementation = _resolve_topt_service_key_unknown_target_impl, ) -validate_orchestrion_mode_invalid_target_rule = rule( - implementation = _validate_orchestrion_mode_invalid_target_impl, -) - -validate_test_optimization_pin_files_missing_go_mod_target_rule = rule( - implementation = _validate_test_optimization_pin_files_missing_go_mod_target_impl, +reject_removed_orchestrion_attrs_target_rule = rule( + implementation = _reject_removed_orchestrion_attrs_target_impl, + attrs = { + "macro_attrs": attr.string_list(doc = "The attribute names passed to dd_topt_go_test."), + }, ) def _resolve_topt_service_key_missing_failure_test_impl(ctx): @@ -1224,16 +1063,17 @@ def _resolve_topt_service_key_unknown_failure_test_impl(ctx): asserts.expect_failure(env, "go_service, ruby_service") return analysistest.end(env) -def _validate_orchestrion_mode_invalid_failure_test_impl(ctx): - """Assert invalid Orchestrion mode failures use the public attr name.""" +def _reject_removed_orchestrion_mode_failure_test_impl(ctx): + """Assert orchestrion_mode fails with a migration hint instead of reaching go_test.""" env = analysistest.begin(ctx) - asserts.expect_failure(env, "orchestrion_mode must be one of general, test_optimization") + asserts.expect_failure(env, "[dd_topt_go_test] orchestrion_mode: Orchestrion was removed in 2.0.0") + asserts.expect_failure(env, "remove the attribute") return analysistest.end(env) -def _validate_test_optimization_pin_files_missing_go_mod_failure_test_impl(ctx): - """Assert optimized mode requires a real go.mod pin.""" +def _reject_removed_orchestrion_attrs_failure_test_impl(ctx): + """Assert the failure names every removed Orchestrion attribute at once.""" env = analysistest.begin(ctx) - asserts.expect_failure(env, "requires a package-local go.mod or explicit orchestrion_pin_files") + asserts.expect_failure(env, "[dd_topt_go_test] orchestrion_mode, orchestrion_pin_files: Orchestrion was removed in 2.0.0") return analysistest.end(env) def _wrapper_output_name_non_windows_test_impl(ctx): @@ -1266,47 +1106,18 @@ def _windows_wrapper_uses_file_payload_mode_test_impl(ctx): asserts.false(env, "HELPER" in content) return unittest.end(env) -def _has_package_local_go_mod_test_impl(ctx): - """Assert auto-discovered pin-file validation requires the module file.""" - env = unittest.begin(ctx) - asserts.true(env, has_package_local_go_mod_for_tests(["go.mod", "go.sum"])) - asserts.false(env, has_package_local_go_mod_for_tests(["go.sum", "orchestrion.yml"])) - asserts.false(env, has_package_local_go_mod_for_tests([])) - return unittest.end(env) - -def _has_go_mod_pin_test_impl(ctx): - """Assert explicit optimized-mode pin validation looks for go.mod labels.""" - env = unittest.begin(ctx) - asserts.true(env, has_go_mod_pin_for_tests(["//:go.mod"])) - asserts.true(env, has_go_mod_pin_for_tests([":go.mod"])) - asserts.true(env, has_go_mod_pin_for_tests(["nested/go.mod"])) - asserts.false(env, has_go_mod_pin_for_tests([":test_selection_utils.bzl"])) - asserts.false(env, has_go_mod_pin_for_tests([])) - return unittest.end(env) - -def _validate_orchestrion_mode_test_impl(ctx): - """Assert the public mode accepts only supported values.""" - env = unittest.begin(ctx) - validate_orchestrion_mode_for_tests("general") - validate_orchestrion_mode_for_tests("test_optimization") - return unittest.end(env) - -def _orch_transition_forwards_mode_test_impl(ctx): - """Assert the wrapper transition enables Orchestrion and forwards its mode.""" - env = unittest.begin(ctx) - result = orch_transition_impl_for_tests(None, struct(orchestrion_mode = "test_optimization")) - asserts.equals(env, 2, len(result)) - asserts.true(env, result["@rules_go//go/private/orchestrion:enabled"]) - asserts.equals(env, "test_optimization", result["@rules_go//go/private/orchestrion:mode"]) - return unittest.end(env) - -def _stdlib_warmup_transition_selects_test_optimization_test_impl(ctx): - """Assert the cache-warm target selects the exact instrumented stdlib.""" +def _reject_removed_orchestrion_attrs_test_impl(ctx): + """Assert ordinary go_test attributes pass the removed-attribute check.""" env = unittest.begin(ctx) - result = stdlib_warmup_transition_impl_for_tests(None, None) - asserts.equals(env, 2, len(result)) - asserts.true(env, result["@rules_go//go/private/orchestrion:enabled"]) - asserts.equals(env, "test_optimization", result["@rules_go//go/private/orchestrion:mode"]) + asserts.equals( + env, + None, + reject_removed_orchestrion_attrs_for_tests({ + "deps": [], + "importpath": "example.com/pkg", + "srcs": [], + }), + ) return unittest.end(env) def _stdlib_warmup_tags_test_impl(ctx): @@ -1318,7 +1129,7 @@ def _stdlib_warmup_tags_test_impl(ctx): return unittest.end(env) def _hidden_raw_tags_test_impl(ctx): - """Assert TestRunner-only policy stays off optimized build actions.""" + """Assert TestRunner-only policy stays off the raw test's build actions.""" env = unittest.begin(ctx) asserts.equals( env, @@ -1330,16 +1141,17 @@ def _hidden_raw_tags_test_impl(ctx): "no-remote-exec", "dd-test-optimization-source-manual", "manual", - ], "test_optimization"), + ]), ) asserts.equals( env, - ["dd-requires-docker", "no-remote-exec", "manual"], + ["dd-requires-docker", "manual"], hidden_raw_tags_for_tests([ "dd-requires-docker", "no-remote-exec", - ], "general"), + ]), ) + asserts.equals(env, ["manual"], hidden_raw_tags_for_tests(None)) return unittest.end(env) def _test_execution_requirements_test_impl(ctx): @@ -1364,39 +1176,39 @@ def _stdlib_warmup_target_has_outputs_test_impl(ctx): asserts.true(env, len(target[DefaultInfo].files.to_list()) > 0) return analysistest.end(env) -def _orch_wrapper_materialized_actual_non_windows_test_impl(ctx): +def _ddtest_wrapper_materialized_actual_non_windows_test_impl(ctx): """Assert the wrapper target ships transitioned inputs as siblings.""" env = analysistest.begin(ctx) target = analysistest.target_under_test(env) files = target[DefaultInfo].files.to_list() runfiles = target[DefaultInfo].default_runfiles.files.to_list() materialized_metadata = ( - "orch_wrapper_materialized_actual_non_windows_target__wrapped_" + - "orch_wrapper_materialized_actual_non_windows_target_metadata.json" + "ddtest_wrapper_materialized_actual_non_windows_target__wrapped_" + + "ddtest_wrapper_materialized_actual_non_windows_target_metadata.json" ) asserts.equals(env, 3, len(files)) - asserts.true(env, _has_file_basename(files, "orch_wrapper_materialized_actual_non_windows_target")) - asserts.true(env, _has_file_basename(files, "orch_wrapper_materialized_actual_non_windows_target__wrapped_hello_test__raw_go_test")) + asserts.true(env, _has_file_basename(files, "ddtest_wrapper_materialized_actual_non_windows_target")) + asserts.true(env, _has_file_basename(files, "ddtest_wrapper_materialized_actual_non_windows_target__wrapped_hello_test__raw_go_test")) asserts.true(env, _has_file_basename(files, materialized_metadata)) - asserts.true(env, _has_file_basename(runfiles, "orch_wrapper_materialized_actual_non_windows_target__wrapped_hello_test__raw_go_test")) + asserts.true(env, _has_file_basename(runfiles, "ddtest_wrapper_materialized_actual_non_windows_target__wrapped_hello_test__raw_go_test")) asserts.true(env, _has_file_basename(runfiles, materialized_metadata)) return analysistest.end(env) -def _orch_wrapper_materialized_actual_windows_test_impl(ctx): +def _ddtest_wrapper_materialized_actual_windows_test_impl(ctx): """Assert the Windows wrapper target ships transitioned inputs as siblings.""" env = analysistest.begin(ctx) target = analysistest.target_under_test(env) files = target[DefaultInfo].files.to_list() runfiles = target[DefaultInfo].default_runfiles.files.to_list() materialized_metadata = ( - "orch_wrapper_materialized_actual_windows_target__wrapped_" + - "orch_wrapper_materialized_actual_windows_target_metadata.json" + "ddtest_wrapper_materialized_actual_windows_target__wrapped_" + + "ddtest_wrapper_materialized_actual_windows_target_metadata.json" ) asserts.equals(env, 3, len(files)) - asserts.true(env, _has_file_basename(files, "orch_wrapper_materialized_actual_windows_target.bat")) - asserts.true(env, _has_file_basename(files, "orch_wrapper_materialized_actual_windows_target__wrapped_hello_test__raw_go_test.exe")) + asserts.true(env, _has_file_basename(files, "ddtest_wrapper_materialized_actual_windows_target.bat")) + asserts.true(env, _has_file_basename(files, "ddtest_wrapper_materialized_actual_windows_target__wrapped_hello_test__raw_go_test.exe")) asserts.true(env, _has_file_basename(files, materialized_metadata)) - asserts.true(env, _has_file_basename(runfiles, "orch_wrapper_materialized_actual_windows_target__wrapped_hello_test__raw_go_test.exe")) + asserts.true(env, _has_file_basename(runfiles, "ddtest_wrapper_materialized_actual_windows_target__wrapped_hello_test__raw_go_test.exe")) asserts.true(env, _has_file_basename(runfiles, materialized_metadata)) return analysistest.end(env) @@ -1406,6 +1218,9 @@ go_macro_single_service_wiring_test = analysistest.make( go_macro_inferred_importpath_metadata_test = analysistest.make( _go_macro_inferred_importpath_metadata_test_impl, ) +go_macro_instrumentation_metadata_test = analysistest.make( + _go_macro_instrumentation_metadata_test_impl, +) go_macro_disabled_raw_wiring_test = analysistest.make( _go_macro_disabled_raw_wiring_test_impl, ) @@ -1439,15 +1254,6 @@ go_macro_stage_sources_rundir_wiring_test = analysistest.make( go_macro_stage_sources_select_wiring_test = analysistest.make( _go_macro_stage_sources_select_wiring_test_impl, ) -go_macro_orchestrion_pin_files_wiring_test = analysistest.make( - _go_macro_orchestrion_pin_files_wiring_test_impl, -) -go_macro_orchestrion_pin_files_provider_test = analysistest.make( - _go_macro_orchestrion_pin_files_provider_test_impl, -) -go_macro_test_optimization_mode_wiring_test = analysistest.make( - _go_macro_test_optimization_mode_wiring_test_impl, -) go_macro_test_optimization_linker_default_wiring_test = analysistest.make( _go_macro_test_optimization_linker_default_wiring_test_impl, config_settings = { @@ -1481,21 +1287,12 @@ go_macro_test_optimization_linker_metadata_opt_out_test = analysistest.make( go_macro_test_optimization_linker_opt_out_wiring_test = analysistest.make( _go_macro_test_optimization_linker_opt_out_wiring_test_impl, ) -go_macro_general_mode_linker_flags_wiring_test = analysistest.make( - _go_macro_general_mode_linker_flags_wiring_test_impl, -) go_macro_explicit_service_wiring_test = analysistest.make( _go_macro_explicit_service_wiring_test_impl, ) go_macro_public_wrapper_test = analysistest.make( _go_macro_public_wrapper_test_impl, ) -go_macro_test_optimization_public_wrapper_mode_test = analysistest.make( - _go_macro_test_optimization_public_wrapper_mode_test_impl, -) -go_macro_default_general_public_wrapper_mode_test = analysistest.make( - _go_macro_default_general_public_wrapper_mode_test_impl, -) resolve_topt_service_key_missing_failure_test = analysistest.make( _resolve_topt_service_key_missing_failure_test_impl, expect_failure = True, @@ -1504,12 +1301,12 @@ resolve_topt_service_key_unknown_failure_test = analysistest.make( _resolve_topt_service_key_unknown_failure_test_impl, expect_failure = True, ) -validate_orchestrion_mode_invalid_failure_test = analysistest.make( - _validate_orchestrion_mode_invalid_failure_test_impl, +reject_removed_orchestrion_mode_failure_test = analysistest.make( + _reject_removed_orchestrion_mode_failure_test_impl, expect_failure = True, ) -validate_test_optimization_pin_files_missing_go_mod_failure_test = analysistest.make( - _validate_test_optimization_pin_files_missing_go_mod_failure_test_impl, +reject_removed_orchestrion_attrs_failure_test = analysistest.make( + _reject_removed_orchestrion_attrs_failure_test_impl, expect_failure = True, ) go_macro_static_wiring_test = analysistest.make( @@ -1551,20 +1348,8 @@ wrapper_output_name_windows_test = analysistest.make( windows_wrapper_uses_file_payload_mode_test = unittest.make( _windows_wrapper_uses_file_payload_mode_test_impl, ) -has_package_local_go_mod_test = unittest.make( - _has_package_local_go_mod_test_impl, -) -has_go_mod_pin_test = unittest.make( - _has_go_mod_pin_test_impl, -) -validate_orchestrion_mode_test = unittest.make( - _validate_orchestrion_mode_test_impl, -) -orch_transition_forwards_mode_test = unittest.make( - _orch_transition_forwards_mode_test_impl, -) -stdlib_warmup_transition_selects_test_optimization_test = unittest.make( - _stdlib_warmup_transition_selects_test_optimization_test_impl, +reject_removed_orchestrion_attrs_test = unittest.make( + _reject_removed_orchestrion_attrs_test_impl, ) stdlib_warmup_tags_test = unittest.make( _stdlib_warmup_tags_test_impl, @@ -1578,12 +1363,12 @@ test_execution_requirements_test = unittest.make( stdlib_warmup_target_has_outputs_test = analysistest.make( _stdlib_warmup_target_has_outputs_test_impl, ) -orch_wrapper_materialized_actual_non_windows_test = analysistest.make( - _orch_wrapper_materialized_actual_non_windows_test_impl, +ddtest_wrapper_materialized_actual_non_windows_test = analysistest.make( + _ddtest_wrapper_materialized_actual_non_windows_test_impl, ) -orch_wrapper_materialized_actual_windows_test = analysistest.make( - _orch_wrapper_materialized_actual_windows_test_impl, +ddtest_wrapper_materialized_actual_windows_test = analysistest.make( + _ddtest_wrapper_materialized_actual_windows_test_impl, config_settings = { - "//command_line_option:platforms": str(Label("@rules_go//go/toolchain:windows_amd64")), + "//command_line_option:platforms": str(Label("@io_bazel_rules_go//go/toolchain:windows_amd64")), }, ) diff --git a/modules/go/tests/test_payloads_selector.bzl b/modules/go/tests/test_payloads_selector.bzl index 57e31d1d..de3687b3 100644 --- a/modules/go/tests/test_payloads_selector.bzl +++ b/modules/go/tests/test_payloads_selector.bzl @@ -8,7 +8,7 @@ load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") load("@datadog-rules-test-optimization-go//:topt_go_infer.bzl", "topt_go_payloads_selector") -load("@rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library") _COMMON_MODULE_GROUPS = [ ":module_example_com_explicit_pkg", diff --git a/modules/go/tests/test_selection_utils.bzl b/modules/go/tests/test_selection_utils.bzl index c80cb604..e7054a0e 100644 --- a/modules/go/tests/test_selection_utils.bzl +++ b/modules/go/tests/test_selection_utils.bzl @@ -10,7 +10,6 @@ load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts", "unittest") load( "@datadog-rules-test-optimization-go//:topt_go_infer.bzl", "go_runtime_module_identifiers_for_tests", - "orchestrion_metadata_enabled_for_tests", "select_module_group_name_for_tests", ) load( @@ -172,14 +171,6 @@ def _go_stub_includes_manifest_in_files_test(ctx): asserts.true(env, test_management in filegroup_block) return unittest.end(env) -def _orchestrion_metadata_enabled_test(ctx): - """Validate metadata reports false when the tool repo is effectively empty.""" - env = unittest.begin(ctx) - asserts.true(env, orchestrion_metadata_enabled_for_tests(True, [struct(path = "tool")])) - asserts.equals(env, False, orchestrion_metadata_enabled_for_tests(True, [])) - asserts.equals(env, False, orchestrion_metadata_enabled_for_tests(False, [struct(path = "tool")])) - return unittest.end(env) - def _build_module_labels_invalid_shape_target_impl(_ctx): build_module_labels_for_tests("repo_name", "mod_a") return [] @@ -257,7 +248,6 @@ go_runtime_module_identifiers_test = unittest.make(_go_runtime_module_identifier normalize_user_data_handles_none_test = unittest.make(_normalize_user_data_handles_none_test) build_module_labels_valid_test = unittest.make(_build_module_labels_valid_test) go_stub_includes_manifest_in_files_test = unittest.make(_go_stub_includes_manifest_in_files_test) -orchestrion_metadata_enabled_test = unittest.make(_orchestrion_metadata_enabled_test) build_module_labels_invalid_shape_failure_test = analysistest.make( _build_module_labels_invalid_shape_failure_test_impl, expect_failure = True, diff --git a/modules/go/tests/test_workspace_helpers.bzl b/modules/go/tests/test_workspace_helpers.bzl index d5745adf..28b0311d 100644 --- a/modules/go/tests/test_workspace_helpers.bzl +++ b/modules/go/tests/test_workspace_helpers.bzl @@ -7,10 +7,6 @@ """Unit tests for the public WORKSPACE bootstrap helpers.""" load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") -load( - "@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", - "build_orchestrion_repo_call_for_tests", -) load( "@datadog-rules-test-optimization-go//:topt_go_workspace.bzl", "build_go_workspace_sync_specs_for_tests", @@ -118,58 +114,6 @@ def _go_workspace_specs_default_to_config_gated_test(ctx): asserts.equals(env, True, result["sync_specs"][0]["enabled_by_env"]) return unittest.end(env) -def _orchestrion_call_spec_test(ctx): - env = unittest.begin(ctx) - call = build_orchestrion_repo_call_for_tests( - dd_trace_go_version = "v2.9.1", - version = "v1.12.0", - go_sdk_root = "@go_sdk//:ROOT", - go_sdk_version = "1.25.0", - log_timing = True, - ) - asserts.equals( - env, - { - "name": "rules_go_orchestrion_tool", - "dd_trace_go_version": "v2.9.1", - "dd_trace_go_versions": {}, - "dd_trace_go_pin_files": [], - "enabled_by_env": True, - "version": "v1.12.0", - "go_sdk_root": "@go_sdk//:ROOT", - "go_sdk_version": "1.25.0", - "log_timing": True, - }, - call, - ) - asserts.false(env, "enabled" in call) - return unittest.end(env) - -def _orchestrion_pin_file_call_spec_test(ctx): - env = unittest.begin(ctx) - call = build_orchestrion_repo_call_for_tests( - dd_trace_go_pin_files = [ - "@//:go.mod", - "@//:go.sum", - ], - version = "v1.12.0", - go_sdk_root = "@go_sdk//:ROOT", - go_sdk_version = "1.25.0", - ) - asserts.equals( - env, - [ - "@//:go.mod", - "@//:go.sum", - ], - call["dd_trace_go_pin_files"], - ) - asserts.equals(env, "", call["dd_trace_go_version"]) - asserts.equals(env, {}, call["dd_trace_go_versions"]) - return unittest.end(env) - go_workspace_single_specs_test = unittest.make(_go_workspace_single_specs_test) go_workspace_multi_specs_test = unittest.make(_go_workspace_multi_specs_test) go_workspace_specs_default_to_config_gated_test = unittest.make(_go_workspace_specs_default_to_config_gated_test) -orchestrion_call_spec_test = unittest.make(_orchestrion_call_spec_test) -orchestrion_pin_file_call_spec_test = unittest.make(_orchestrion_pin_file_call_spec_test) diff --git a/modules/go/tools/dd_topt_go_bootstrap/BUILD.bazel b/modules/go/tools/dd_topt_go_bootstrap/BUILD.bazel index f0513924..a304eecd 100644 --- a/modules/go/tools/dd_topt_go_bootstrap/BUILD.bazel +++ b/modules/go/tools/dd_topt_go_bootstrap/BUILD.bazel @@ -4,14 +4,11 @@ # This product includes software developed at Datadog # (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. -load("@rules_go//go:def.bzl", "go_library", "go_test") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "bootstrap_lib", - srcs = [ - "archive.go", - "main.go", - ], + srcs = ["main.go"], importpath = "github.com/DataDog/rules_test_optimization/modules/go/tools/dd_topt_go_bootstrap", visibility = ["//visibility:public"], deps = ["//tools/onboardingpins"], @@ -19,9 +16,6 @@ go_library( go_test( name = "bootstrap_test", - srcs = [ - "archive_test.go", - "main_test.go", - ], + srcs = ["main_test.go"], embed = [":bootstrap_lib"], ) diff --git a/modules/go/tools/dd_topt_go_bootstrap/archive.go b/modules/go/tools/dd_topt_go_bootstrap/archive.go deleted file mode 100644 index be40f992..00000000 --- a/modules/go/tools/dd_topt_go_bootstrap/archive.go +++ /dev/null @@ -1,290 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under -// the Apache 2.0 License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. - -package main - -import ( - "errors" - "fmt" - "path" - "reflect" - "regexp" - "strconv" - "strings" - - "github.com/DataDog/rules_test_optimization/modules/go/tools/onboardingpins" -) - -// moduleArchive describes the whole Datadog source archive, not just the fork. -// Keep mirrors and the checksum together when deriving a rules_go subdirectory. -type moduleArchive struct { - urls []string - sha256 string - prefix string - archiveType string -} - -func (a moduleArchive) validate() error { - if len(a.urls) == 0 || !regexp.MustCompile(`^[a-fA-F0-9]{64}$`).MatchString(a.sha256) { - return errors.New("archive override requires literal non-empty urls and a 64-character sha256; use --rto-archive-url, --rto-archive-sha256 and --rto-archive-prefix for computed MODULE values") - } - for _, url := range a.urls { - if strings.TrimSpace(url) == "" { - return errors.New("archive override contains an empty URL") - } - } - if a.prefix == "" || path.IsAbs(a.prefix) || path.Clean(a.prefix) != a.prefix || a.prefix == ".." || strings.HasPrefix(a.prefix, "../") { - return errors.New("archive override requires a non-empty relative strip_prefix without parent traversal") - } - return nil -} - -func (a moduleArchive) render(forkPrefix string) string { - var result strings.Builder - result.WriteString("archive_override(\n module_name = \"rules_go\",\n urls = [\n") - for _, url := range a.urls { - fmt.Fprintf(&result, " %q,\n", url) - } - fmt.Fprintf(&result, " ],\n sha256 = %q,\n strip_prefix = %q,\n", a.sha256, a.prefix+"/"+forkPrefix) - if a.archiveType != "" { - fmt.Fprintf(&result, " type = %q,\n", a.archiveType) - } - result.WriteString(")\n") - return result.String() -} - -// configureModuleArchive prefers an existing rules_go archive, then a core/Go -// archive. Explicit Git flags keep their previous meaning. No URL is fetched. -func configureModuleArchive(cfg *config, content string) error { - if cfg.rulesGoFetchSet && cfg.rulesGoFetch == "git" && cfg.rtoArchiveURL != "" { - return errors.New("--rules-go-fetch=git conflicts with explicit --rto-archive-* flags for Bzlmod") - } - if cfg.rtoArchiveURL != "" || cfg.rulesGoFetch == "archive" { - // Explicit pins can replace computed values, but must not erase patches - // from a previously managed fork when its block is rewritten. - for _, block := range topLevelCallBlocks(stripStarlarkLineComments(content)) { - if block.function == "archive_override" && literalAttribute(block.text, "module_name") == "rules_go" { - if err := validateRulesGoArchiveAttributes(block.text); err != nil { - return err - } - } - } - archive := &moduleArchive{urls: []string{cfg.rtoArchiveURL}, sha256: cfg.rtoArchiveSHA256, prefix: cfg.rtoArchivePrefix, archiveType: cfg.rtoArchiveType} - if err := archive.validate(); err != nil { - return err - } - cfg.rulesGoArchive = archive - return nil - } - if cfg.rulesGoCommitSet || cfg.rulesGoRemoteSet || cfg.rulesGoFetchSet { - return nil - } - if remote, commit := inferManagedRulesGoOverride(content); remote != "" && commit != "" { - cfg.rulesGoRemote, cfg.rulesGoCommit = remote, commit - return nil - } - for _, name := range []string{"rules_go", "datadog-rules-test-optimization-go", "datadog-rules-test-optimization"} { - archive, err := findModuleArchive(content, name) - if err != nil { - return err - } - if archive == nil { - continue - } - if name == "rules_go" { - index := strings.Index(archive.prefix, "/third_party/rgo/") - if index < 0 { - return errors.New("rules_go archive strip_prefix is not a supported Datadog fork path") - } - upstream, variant, err := onboardingpins.RulesGoSelectionForStripPrefix(archive.prefix[index+1:]) - if err != nil { - return err - } - if !cfg.rulesGoUpstreamSet && !cfg.rulesGoVariantSet { - cfg.rulesGoUpstream, cfg.rulesGoVariant = upstream, variant - } - fork, err := rulesGoStripPrefix(*cfg) - if err != nil { - return err - } - if archive.prefix[index+1:] != fork { - return errors.New("existing rules_go archive selects another upstream; update its override before changing the bootstrap selection") - } - archive.prefix = archive.prefix[:index] - } else { - if name == "datadog-rules-test-optimization-go" { - if !strings.HasSuffix(archive.prefix, "/modules/go") { - return errors.New("Go companion archive strip_prefix must end with /modules/go; pass explicit archive flags for a custom layout") - } - archive.prefix = strings.TrimSuffix(archive.prefix, "/modules/go") - } - } - cfg.rulesGoArchive = archive - return nil - } - return nil -} - -func moduleOverridePresent(content, module string) bool { - for _, block := range topLevelCallBlocks(stripStarlarkLineComments(content)) { - if strings.HasSuffix(block.function, "_override") && literalAttribute(block.text, "module_name") == module { - return true - } - } - return false -} - -func moduleArchiveCompatible(content string, cfg config) (bool, error) { - existing, err := findModuleArchive(content, "rules_go") - if err != nil && (cfg.rtoArchiveURL != "" || cfg.rulesGoFetch == "archive") { - // Complete explicit pins can replace computed values, but never discard - // fork patches, authentication, or other unsupported archive attributes. - for _, block := range topLevelCallBlocks(stripStarlarkLineComments(content)) { - if block.function == "archive_override" && literalAttribute(block.text, "module_name") == "rules_go" { - if err := validateRulesGoArchiveAttributes(block.text); err != nil { - return false, err - } - return true, nil - } - } - } - if err != nil || existing == nil { - return false, err - } - fork, err := rulesGoStripPrefix(cfg) - if err != nil { - return false, err - } - wanted := cfg.rulesGoArchive - return reflect.DeepEqual(existing.urls, wanted.urls) && strings.EqualFold(existing.sha256, wanted.sha256) && existing.prefix == wanted.prefix+"/"+fork && existing.archiveType == wanted.archiveType, nil -} - -func validateRulesGoArchiveAttributes(block string) error { - // Only outer call arguments belong to archive_override. Explicit pins may - // replace computed values containing their own calls and keyword arguments. - syntax := []byte(maskStarlarkStringsAndComments(block)) - depth := 0 - for i, char := range syntax { - switch char { - case '(', '[', '{': - depth++ - case ')', ']', '}': - depth-- - default: - if depth > 1 { - syntax[i] = ' ' - } - } - } - if regexp.MustCompile(`(?:\(|,)\s*\*\*`).Match(syntax) { - return errors.New("cannot manage rules_go archive attribute expansion; keep custom patches/authentication wiring outside bootstrap") - } - for _, attr := range regexp.MustCompile(`(?:\(|,)\s*([A-Za-z_][A-Za-z0-9_]*)\s*=`).FindAllStringSubmatch(string(syntax), -1) { - switch attr[1] { - case "module_name", "urls", "sha256", "strip_prefix", "type": - default: - return fmt.Errorf("cannot manage rules_go archive attribute %q; keep custom patches/authentication wiring outside bootstrap", attr[1]) - } - } - return nil -} - -// These are literal-only readers, like the existing bootstrap's Git readers. -// Never guess the result of a Starlark expression or silently drop a fork patch. -func findModuleArchive(content, module string) (*moduleArchive, error) { - for _, block := range topLevelCallBlocks(stripStarlarkLineComments(content)) { - if block.function != "archive_override" || literalAttribute(block.text, "module_name") != module { - continue - } - if module == "rules_go" { - if err := validateRulesGoArchiveAttributes(block.text); err != nil { - return nil, err - } - } - archive := &moduleArchive{sha256: literalAttribute(block.text, "sha256"), prefix: literalAttribute(block.text, "strip_prefix"), archiveType: literalAttribute(block.text, "type")} - if value, present := callAttributeValue(block.text, "type"); present { - if !regexp.MustCompile(`^("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')$`).MatchString(value) { - return nil, errors.New("archive type must be a literal string; pass explicit --rto-archive-* flags for computed values") - } - } - value, present := callAttributeValue(block.text, "urls") - if !present || !strings.HasPrefix(value, "[") || !strings.HasSuffix(value, "]") { - return nil, errors.New("archive urls must be a literal list; pass explicit --rto-archive-* flags for computed values") - } - remaining := strings.TrimSpace(value[1 : len(value)-1]) - literalPattern := regexp.MustCompile(`^("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')`) - for remaining != "" { - literal := literalPattern.FindString(remaining) - if literal == "" { - return nil, errors.New("archive urls must be a literal list; pass explicit --rto-archive-* flags for computed values") - } - url, err := unquoteStarlarkLiteral(literal) - if err != nil { - return nil, err - } - archive.urls = append(archive.urls, url) - remaining = strings.TrimSpace(remaining[len(literal):]) - if remaining == "" { - break - } - if remaining[0] != ',' { - return nil, errors.New("archive urls must be a literal list; pass explicit --rto-archive-* flags for computed values") - } - remaining = strings.TrimSpace(remaining[1:]) - } - if err := archive.validate(); err != nil { - return nil, fmt.Errorf("%s: %w", module, err) - } - return archive, nil - } - return nil, nil -} - -func literalAttribute(block, name string) string { - value, present := callAttributeValue(block, name) - if !present || !regexp.MustCompile(`^("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')$`).MatchString(value) { - return "" - } - literal, _ := unquoteStarlarkLiteral(value) - return literal -} - -// Read one complete outer call argument, never a nested keyword or a fragment -// of a computed expression. The masked view preserves original byte offsets. -func callAttributeValue(block, name string) (string, bool) { - syntax := maskStarlarkStringsAndComments(block) - opening := strings.IndexByte(syntax, '(') - if opening < 0 { - return "", false - } - attribute := regexp.MustCompile(`^\s*` + regexp.QuoteMeta(name) + `\s*=`) - depth, start := 1, opening+1 - for i := start; i < len(syntax); i++ { - switch syntax[i] { - case '(', '[', '{': - depth++ - case ')', ']', '}': - depth-- - } - if (depth == 1 && syntax[i] == ',') || depth == 0 { - if match := attribute.FindStringIndex(syntax[start:i]); match != nil { - return strings.TrimSpace(stripStarlarkLineComments(block[start+match[1] : i])), true - } - start = i + 1 - if depth == 0 { - break - } - } - } - return "", false -} - -func unquoteStarlarkLiteral(value string) (string, error) { - if strings.HasPrefix(value, "'") { - value = `"` + strings.ReplaceAll(strings.ReplaceAll(value[1:len(value)-1], `\'`, `'`), `"`, `\"`) + `"` - } - return strconv.Unquote(value) -} diff --git a/modules/go/tools/dd_topt_go_bootstrap/archive_test.go b/modules/go/tools/dd_topt_go_bootstrap/archive_test.go deleted file mode 100644 index 83b969e5..00000000 --- a/modules/go/tools/dd_topt_go_bootstrap/archive_test.go +++ /dev/null @@ -1,451 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under -// the Apache 2.0 License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. - -package main - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "testing" -) - -func archiveTestOverride(module, prefix string) string { - return fmt.Sprintf(`archive_override( - module_name = %q, - urls = ["https://mirror.example/source.tar.gz", "https://github.com/DataDog/rules_test_optimization/releases/download/v1.3.0/source.tar.gz"], - sha256 = %q, - strip_prefix = %q, -) -`, module, strings.Repeat("a", 64), prefix) -} - -func TestArchiveBootstrapSourcesAndIdempotence(t *testing.T) { - for _, module := range []string{"datadog-rules-test-optimization", "datadog-rules-test-optimization-go", "rules_go"} { - t.Run(module, func(t *testing.T) { - prefix := "rules_test_optimization-1.3.0" - if module == "datadog-rules-test-optimization-go" { - prefix += "/modules/go" - } else if module == "rules_go" { - prefix += "/third_party/rgo/v0_60_0/base" - } - input := "module(name = \"consumer\")\n" + archiveTestOverride(module, prefix) - file := filepath.Join(t.TempDir(), "MODULE.bazel") - if err := os.WriteFile(file, []byte(input), 0o644); err != nil { - t.Fatal(err) - } - cfg := config{moduleFile: file, rulesGoVariant: "base", orchestrionVersion: defaultOrchestrionVersion, ddTraceGoVersion: defaultDDTraceGoVersion} - if err := patchModuleFile(cfg); err != nil { - t.Fatal(err) - } - first, _ := os.ReadFile(file) - if strings.Contains(string(first), "git_override(") || strings.Count(string(first), `module_name = "rules_go"`) != 1 { - t.Fatalf("expected exactly one archive fork override, no Git:\n%s", first) - } - for _, wanted := range []string{"https://mirror.example/source.tar.gz", strings.Repeat("a", 64), "rules_test_optimization-1.3.0/third_party/rgo/v0_60_0/base"} { - if !strings.Contains(string(first), wanted) { - t.Fatalf("missing %q:\n%s", wanted, first) - } - } - if err := patchModuleFile(cfg); err != nil { - t.Fatal(err) - } - second, _ := os.ReadFile(file) - if string(first) != string(second) { - t.Fatalf("bootstrap is not idempotent:\n%s\nthen:\n%s", first, second) - } - }) - } -} - -func TestArchiveBootstrapPreservesQuotedURLBrackets(t *testing.T) { - for _, quote := range []string{`"`, `'`} { - for _, url := range []string{ - "https://[2001:db8::1]/source.tar.gz", - "https://mirror.example/source.tar.gz?part=[first,second]", - } { - t.Run(quote+url, func(t *testing.T) { - input := "module(name = \"consumer\")\n" + strings.Replace(archiveTestOverride("rules_go", "release/third_party/rgo/v0_60_0/base"), `"https://mirror.example/source.tar.gz"`, quote+url+quote, 1) - archive, err := findModuleArchive(input, "rules_go") - if err != nil || archive == nil || len(archive.urls) != 2 || archive.urls[0] != url { - t.Fatalf("literal URL brackets changed archive inference: archive=%+v err=%v", archive, err) - } - file := filepath.Join(t.TempDir(), "MODULE.bazel") - if err := os.WriteFile(file, []byte(input), 0o644); err != nil { - t.Fatal(err) - } - cfg := config{moduleFile: file} - if err := patchModuleFile(cfg); err != nil { - t.Fatal(err) - } - first, err := os.ReadFile(file) - if err != nil || !strings.Contains(string(first), url) { - t.Fatalf("URL lost during migration: %v\n%s", err, first) - } - if err := patchModuleFile(cfg); err != nil { - t.Fatal(err) - } - second, err := os.ReadFile(file) - if err != nil || string(first) != string(second) { - t.Fatalf("URL bracket migration is not idempotent: %v", err) - } - }) - } - } -} - -func TestRunArchiveExplicitPinsReplaceComputedValues(t *testing.T) { - t.Setenv("GOPROXY", "off") - t.Setenv("GOSUMDB", "off") - for _, managed := range []bool{false, true} { - for name, edit := range map[string][2]string{ - "computed prefix": {`strip_prefix = "release/third_party/rgo/v0_60_0/base"`, `strip_prefix = "release/" + "third_party/rgo/v0_60_0/base"`}, - "computed type": {"archive_override(\n", "archive_override(\n type = ARCHIVE_TYPE,\n"}, - } { - t.Run(fmt.Sprintf("managed=%t/%s", managed, name), func(t *testing.T) { - dir := t.TempDir() - override := strings.Replace(archiveTestOverride("rules_go", "release/third_party/rgo/v0_60_0/base"), edit[0], edit[1], 1) - if managed { - override = managedBlockStart + "\n" + override + managedBlockEnd + "\n" - } - const preserved = "bazel_dep(name = \"keep_me\", version = \"1.0.0\")\n" - input := "module(name = \"consumer\")\n" + override + preserved - moduleFile := filepath.Join(dir, "MODULE.bazel") - if err := os.WriteFile(moduleFile, []byte(input), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/consumer\n\ngo 1.25.0\n"), 0o644); err != nil { - t.Fatal(err) - } - cfg := config{ - workspaceDir: dir, goModSync: "off", datadogFetch: defaultDatadogFetch, - rulesGoFetch: "archive", rulesGoVariant: defaultRulesGoVariant, - orchestrionVersion: defaultOrchestrionVersion, ddTraceGoVersion: defaultDDTraceGoVersion, - rtoArchiveURL: "https://mirror.example/replacement", rtoArchiveSHA256: strings.Repeat("b", 64), - rtoArchivePrefix: "replacement", rtoArchiveType: "tar.gz", - } - if err := run(cfg); err != nil { - t.Fatalf("explicit archive pins could not replace computed values: %v", err) - } - first, err := os.ReadFile(moduleFile) - if err != nil { - t.Fatal(err) - } - archive, err := findModuleArchive(string(first), "rules_go") - if err != nil || archive == nil || len(archive.urls) != 1 || archive.urls[0] != cfg.rtoArchiveURL || archive.sha256 != cfg.rtoArchiveSHA256 || archive.prefix != "replacement/third_party/rgo/v0_60_0/base" || archive.archiveType != "tar.gz" { - t.Fatalf("explicit pins were not preserved: archive=%+v err=%v\n%s", archive, err, first) - } - if strings.Count(string(first), `module_name = "rules_go"`) != 1 || !strings.Contains(string(first), preserved) || strings.Contains(string(first), "ARCHIVE_TYPE") { - t.Fatalf("migration damaged surrounding declarations or duplicated the override:\n%s", first) - } - if err := run(cfg); err != nil { - t.Fatalf("rerun failed: %v", err) - } - second, err := os.ReadFile(moduleFile) - if err != nil || string(first) != string(second) { - t.Fatalf("explicit-pin migration is not idempotent: %v", err) - } - }) - } - } -} - -func TestArchiveBootstrapRejectsInvalidInputsWithoutWriting(t *testing.T) { - valid := archiveTestOverride("datadog-rules-test-optimization", "release") - for name, input := range map[string]string{ - "missing checksum": strings.Replace(valid, strings.Repeat("a", 64), "", 1), - "malformed checksum": strings.Replace(valid, strings.Repeat("a", 64), "not-a-sha", 1), - "computed URLs": strings.Replace(valid, `"https://mirror.example/source.tar.gz"`, "MIRROR_URL", 1), - "conditional URLs": strings.Replace(valid, "],\n", "] if USE_MIRROR else [\"https://fallback.example/source.tar.gz\"],\n", 1), - "concatenated URLs": strings.Replace(valid, "],\n", "] + [\"https://fallback.example/source.tar.gz\"],\n", 1), - "computed prefix": strings.Replace(valid, `strip_prefix = "release"`, `strip_prefix = PREFIX`, 1), - "parent traversal": strings.Replace(valid, `strip_prefix = "release"`, `strip_prefix = "../release"`, 1), - "fork patches": strings.Replace(archiveTestOverride("rules_go", "release/third_party/rgo/v0_60_0/base"), "archive_override(", "archive_override(\n patches = [\"//:custom.patch\"],", 1), - } { - t.Run(name, func(t *testing.T) { - file := filepath.Join(t.TempDir(), "MODULE.bazel") - if err := os.WriteFile(file, []byte(input), 0o644); err != nil { - t.Fatal(err) - } - if err := patchModuleFile(config{moduleFile: file, rulesGoVariant: "base"}); err == nil { - t.Fatal("expected actionable validation error") - } - after, _ := os.ReadFile(file) - if string(after) != input { - t.Fatal("invalid input changed MODULE.bazel") - } - }) - } -} - -func TestArchiveInferenceRejectsComputedURLLists(t *testing.T) { - const module = "datadog-rules-test-optimization" - valid := archiveTestOverride(module, "release") - if archive, err := findModuleArchive(valid, module); err != nil || archive == nil || len(archive.urls) != 2 { - t.Fatalf("expected literal URL-list inference to succeed: archive=%+v err=%v", archive, err) - } - for _, suffix := range []string{` if USE_MIRROR else ["https://fallback.example/source.tar.gz"]`, ` + ["https://fallback.example/source.tar.gz"]`} { - t.Run(suffix, func(t *testing.T) { - input := strings.Replace(valid, "],\n", "]"+suffix+",\n", 1) - if archive, err := findModuleArchive(input, module); err == nil || archive != nil { - t.Fatalf("computed URL list was accepted: archive=%+v err=%v", archive, err) - } - }) - } -} - -func TestArchiveInferenceRejectsNestedComputedURLs(t *testing.T) { - for _, value := range []string{ - `mirror_urls(urls = ["https://mirror.example/source.tar.gz"])`, - `dict(urls = ["https://mirror.example/source.tar.gz"])["urls"]`, - } { - t.Run(value, func(t *testing.T) { - input := archiveTestOverride("datadog-rules-test-optimization", "release") - input = strings.Replace(input, `urls = ["https://mirror.example/source.tar.gz", "https://github.com/DataDog/rules_test_optimization/releases/download/v1.3.0/source.tar.gz"]`, "urls = "+value, 1) - if archive, err := findModuleArchive(input, "datadog-rules-test-optimization"); err == nil || archive != nil { - t.Fatalf("nested computed URL list was accepted: archive=%+v err=%v", archive, err) - } - }) - } -} - -func TestExplicitArchivePinsRejectUnmanagedForkAttributesWithoutWriting(t *testing.T) { - for _, attr := range []string{`patches = ["//:custom.patch"]`, `auth_patterns = {"mirror.example": "Bearer "}`, `**{"patches": ["//:custom.patch"]}`} { - t.Run(attr, func(t *testing.T) { - input := "module(name = \"consumer\")\n" + strings.Replace(archiveTestOverride("rules_go", "release/third_party/rgo/v0_60_0/base"), "archive_override(\n", "archive_override(\n type = ARCHIVE_TYPE,\n "+attr+",\n", 1) - file := filepath.Join(t.TempDir(), "MODULE.bazel") - if err := os.WriteFile(file, []byte(input), 0o644); err != nil { - t.Fatal(err) - } - cfg := config{moduleFile: file, rulesGoFetch: "archive", rtoArchiveURL: "https://mirror.example/source", rtoArchiveSHA256: strings.Repeat("b", 64), rtoArchivePrefix: "release", rtoArchiveType: "tar.gz"} - if err := patchModuleFile(cfg); err == nil || !strings.Contains(err.Error(), "cannot manage rules_go archive attribute") { - t.Errorf("explicit pins accepted unsupported fork attributes: %v", err) - } - after, err := os.ReadFile(file) - if err != nil || string(after) != input { - t.Fatalf("unsupported fork attributes changed MODULE.bazel: %v", err) - } - }) - } -} - -func TestExplicitArchivePinsDoNotReplaceDifferentLiteralFork(t *testing.T) { - input := "module(name = \"consumer\")\n" + archiveTestOverride("rules_go", "release/third_party/rgo/v0_60_0/base") - file := filepath.Join(t.TempDir(), "MODULE.bazel") - if err := os.WriteFile(file, []byte(input), 0o644); err != nil { - t.Fatal(err) - } - cfg := config{moduleFile: file, rulesGoFetch: "archive", rtoArchiveURL: "https://mirror.example/replacement.tar.gz", rtoArchiveSHA256: strings.Repeat("b", 64), rtoArchivePrefix: "replacement"} - if err := patchModuleFile(cfg); err == nil || !strings.Contains(err.Error(), "incompatible rules_go override") { - t.Fatalf("different literal fork was accepted: %v", err) - } - after, err := os.ReadFile(file) - if err != nil || string(after) != input { - t.Fatalf("different literal fork changed MODULE.bazel: %v", err) - } -} - -func TestArchiveBootstrapRejectsComputedTypesWithoutWriting(t *testing.T) { - for _, module := range []string{"datadog-rules-test-optimization", "datadog-rules-test-optimization-go", "rules_go"} { - prefix := "release" - if module == "datadog-rules-test-optimization-go" { - prefix += "/modules/go" - } else if module == "rules_go" { - prefix += "/third_party/rgo/v0_60_0/base" - } - for _, value := range []string{`ARCHIVE_TYPE`, `"tar." + EXTENSION`, `"tar.gz" if USE_GZIP else "zip"`, `archive_type(format = "tar.gz")`} { - t.Run(module+"/"+value, func(t *testing.T) { - input := strings.Replace(archiveTestOverride(module, prefix), "archive_override(\n", "archive_override(\n type = "+value+",\n", 1) - if archive, err := findModuleArchive(input, module); err == nil || archive != nil { - t.Errorf("computed archive type was accepted: archive=%+v err=%v", archive, err) - } - file := filepath.Join(t.TempDir(), "MODULE.bazel") - if err := os.WriteFile(file, []byte("module(name = \"consumer\")\n"+input), 0o644); err != nil { - t.Fatal(err) - } - before, _ := os.ReadFile(file) - if err := patchModuleFile(config{moduleFile: file}); err == nil || !strings.Contains(err.Error(), "archive type must be a literal string") { - t.Errorf("expected actionable computed-type error, got %v", err) - } - after, _ := os.ReadFile(file) - if string(after) != string(before) { - t.Fatal("computed archive type changed MODULE.bazel") - } - }) - } - } -} - -func TestExplicitArchiveFlagsRejectManagedForkPatchesWithoutWriting(t *testing.T) { - for _, attr := range []string{`patches = ["//:custom.patch"]`, `patch_cmds = ["echo patch"]`, `patch_args = ["-p1"]`, `**{"patches": ["//:custom.patch"]}`} { - t.Run(attr, func(t *testing.T) { - override := strings.Replace(archiveTestOverride("rules_go", "release/third_party/rgo/v0_60_0/base"), "archive_override(\n", "archive_override(\n "+attr+",\n", 1) - input := "module(name = \"consumer\")\n" + managedBlockStart + "\n" + override + managedBlockEnd + "\n" - file := filepath.Join(t.TempDir(), "MODULE.bazel") - if err := os.WriteFile(file, []byte(input), 0o644); err != nil { - t.Fatal(err) - } - cfg := config{moduleFile: file, rulesGoFetch: "archive", rtoArchiveURL: "https://mirror.example/source", rtoArchiveSHA256: strings.Repeat("b", 64), rtoArchivePrefix: "release", rtoArchiveType: "tar.gz"} - if err := patchModuleFile(cfg); err == nil || !strings.Contains(err.Error(), "cannot manage rules_go archive attribute") { - t.Errorf("explicit archive flags accepted fork patch attributes: %v", err) - } - after, _ := os.ReadFile(file) - if string(after) != input { - t.Fatal("explicit archive flags deleted custom fork patches") - } - }) - } -} - -func TestArchiveBootstrapPreservesLiteralTypesAndExplicitComputedEscapeHatch(t *testing.T) { - for _, value := range []string{`"tar.gz"`, `'tar.gz'`, `ARCHIVE_TYPE`, `archive_type(format = "tar.gz")`, `archive_type(options = ["tar.gz"])`, `archive_type(options = {"format": "tar.gz"})`, `"tar.gz" if FORMAT == "gzip" else "zip"`} { - t.Run(value, func(t *testing.T) { - override := strings.Replace(archiveTestOverride("rules_go", "release/third_party/rgo/v0_60_0/base"), "archive_override(\n", "archive_override(\n type = "+value+",\n", 1) - override = strings.ReplaceAll(override, "source.tar.gz", "source?type=asset") - input := "module(name = \"consumer\")\n" + managedBlockStart + "\n" + override + managedBlockEnd + "\nbazel_dep(name = \"keep_me\", version = \"1.0.0\")\n" - file := filepath.Join(t.TempDir(), "MODULE.bazel") - if err := os.WriteFile(file, []byte(input), 0o644); err != nil { - t.Fatal(err) - } - cfg := config{moduleFile: file} - if value != `"tar.gz"` && value != `'tar.gz'` { - cfg.rulesGoFetch, cfg.rtoArchiveURL, cfg.rtoArchiveSHA256, cfg.rtoArchivePrefix, cfg.rtoArchiveType = "archive", "https://mirror.example/source", strings.Repeat("b", 64), "release", "tar.gz" - } - if err := patchModuleFile(cfg); err != nil { - t.Fatal(err) - } - result, _ := os.ReadFile(file) - if !strings.Contains(string(result), `type = "tar.gz"`) || !strings.Contains(string(result), `bazel_dep(name = "keep_me", version = "1.0.0")`) { - t.Fatalf("archive type or unrelated declaration lost:\n%s", result) - } - }) - } -} - -func TestArchiveBootstrapPreservesDeclarationsAroundCommentedOverrides(t *testing.T) { - for _, comment := range []string{"# example: foo(", "# example: foo)", "# Unicode: héllo( and an unmatched quote \""} { - t.Run(comment, func(t *testing.T) { - prefix := "# Existing header: é\nmodule(name = \"consumer\")\n" - suffix := "\nbazel_dep(name = \"keep_me\", version = \"1.0.0\")\n" - override := strings.Replace(archiveTestOverride("rules_go", "release/third_party/rgo/v0_60_0/base"), "archive_override(\n", "archive_override(\n "+comment+"\n", 1) - input := prefix + override + suffix - file := filepath.Join(t.TempDir(), "MODULE.bazel") - if err := os.WriteFile(file, []byte(input), 0o644); err != nil { - t.Fatal(err) - } - cfg := config{moduleFile: file} - if err := patchModuleFile(cfg); err != nil { - t.Fatal(err) - } - first, _ := os.ReadFile(file) - if !strings.HasPrefix(string(first), prefix) || !strings.Contains(string(first), suffix) || strings.Count(string(first), "archive_override(") != 1 { - t.Fatalf("moving an override changed surrounding declarations:\n%s", first) - } - if err := patchModuleFile(cfg); err != nil { - t.Fatal(err) - } - second, _ := os.ReadFile(file) - if string(first) != string(second) { - t.Fatal("commented override is not idempotent") - } - }) - } -} - -func TestTopLevelCallSpansIgnoreQuotedAndCommentedDelimiters(t *testing.T) { - for _, value := range []string{`"https://mirror.example/file)#asset"`, "\"\"\"first )\nsecond (\nend\"\"\"", `'escaped \' ) # text'`} { - input := "# λ before a call(\n" + "archive_override(\n urls = [" + value + "], # ) ( \"\n)\n" + "bazel_dep(name = \"keep\", version = \"1\")\n" - blocks := topLevelCallBlocks(input) - if len(blocks) != 2 || blocks[0].function != "archive_override" || blocks[1].function != "bazel_dep" { - t.Fatalf("incorrect call spans for %q: %#v", value, blocks) - } - if input[blocks[0].start:blocks[0].end] != "archive_override(\n urls = ["+value+"], # ) ( \"\n)\n" { - t.Fatalf("scanner did not preserve original byte offsets for %q", value) - } - } -} - -func TestArchiveBootstrapExplicitFlagsAndGitEscapeHatch(t *testing.T) { - cfg := config{rulesGoFetch: "archive", rtoArchiveURL: "file:///tmp/source.tar.gz", rtoArchiveSHA256: strings.Repeat("b", 64), rtoArchivePrefix: "release"} - if err := configureModuleArchive(&cfg, ""); err != nil || cfg.rulesGoArchive == nil { - t.Fatalf("explicit archive flags: %v", err) - } - for _, cfg := range []config{{rulesGoCommitSet: true}, {rulesGoRemoteSet: true}, {rulesGoFetch: "git", rulesGoFetchSet: true}} { - if err := configureModuleArchive(&cfg, archiveTestOverride("datadog-rules-test-optimization", "release")); err != nil || cfg.rulesGoArchive != nil { - t.Fatalf("explicit Git flags should disable archive inference: %v", err) - } - } -} - -func TestArchiveBootstrapPreservesSupportedUpstreams(t *testing.T) { - for _, upstream := range []string{"v0_60_0", "v0_61_1", "v0_62_0", "v0_63_0"} { - t.Run(upstream, func(t *testing.T) { - input := managedBlockStart + "\n" + archiveTestOverride("rules_go", "release/third_party/rgo/"+upstream+"/base") + managedBlockEnd - cfg := config{} - if err := hydrateManagedRulesGoVariant(&cfg, input); err != nil || cfg.rulesGoUpstream != upstream { - t.Fatalf("hydrate archive selection: cfg=%+v err=%v", cfg, err) - } - if err := configureModuleArchive(&cfg, input); err != nil { - t.Fatal(err) - } - block, err := managedModuleBlockWithError(cfg) - if err != nil || !strings.Contains(block, "release/third_party/rgo/"+upstream+"/base") { - t.Fatalf("preserve archive fork: %v\n%s", err, block) - } - }) - } -} - -func TestArchiveBootstrapCommentsAndLiteralFormatting(t *testing.T) { - input := "# " + strings.ReplaceAll(archiveTestOverride("rules_go", "wrong"), "\n", "\n# ") + "\n" + strings.ReplaceAll(archiveTestOverride("datadog-rules-test-optimization", "release"), `"`, `'`) - cfg := config{} - if err := configureModuleArchive(&cfg, input); err != nil || cfg.rulesGoArchive == nil || cfg.rulesGoArchive.prefix != "release" { - t.Fatalf("comments/single quotes: %v %+v", err, cfg.rulesGoArchive) - } -} - -func TestArchiveBootstrapSingleQuotedManualForkAndURLParameters(t *testing.T) { - input := "module(name = \"consumer\")\n" + strings.ReplaceAll(archiveTestOverride("rules_go", "release/third_party/rgo/v0_60_0/base"), `"`, `'`) - input = strings.ReplaceAll(input, "source.tar.gz", "source.tar.gz?version=1") - file := filepath.Join(t.TempDir(), "MODULE.bazel") - if err := os.WriteFile(file, []byte(input), 0o644); err != nil { - t.Fatal(err) - } - if err := patchModuleFile(config{moduleFile: file}); err != nil { - t.Fatal(err) - } - result, _ := os.ReadFile(file) - if strings.Count(string(result), "archive_override(") != 1 || !strings.Contains(string(result), "?version=1") { - t.Fatalf("manual fork should move into managed block, preserving URL parameters:\n%s", result) - } -} - -func TestReplaceManagedSectionPreservesSurroundingTextOnRerun(t *testing.T) { - for _, suffix := range []string{"", "\n", "\n\n# Other settings\n"} { - block := "# BEGIN\nowned = True\n# END\n" - input := "# Existing header\n" + block + suffix - first, err := replaceManagedSection(input, "# BEGIN", "# END", block) - if err != nil || first != input { - t.Fatalf("managed newline replacement changed surrounding text: %v\n%q -> %q", err, input, first) - } - } -} - -func TestArchiveBootstrapDoesNotReplaceAnExistingGitFork(t *testing.T) { - input := archiveTestOverride("datadog-rules-test-optimization", "release") + managedBlockStart + ` -git_override( - module_name = "rules_go", - remote = "https://example.com/owned-fork.git", - commit = "existing-published-commit", - strip_prefix = "third_party/rgo/v0_60_0/base", -) -` + managedBlockEnd - cfg := config{} - if err := configureModuleArchive(&cfg, input); err != nil || cfg.rulesGoArchive != nil || cfg.rulesGoCommit != "existing-published-commit" { - t.Fatalf("existing Git fork must win over a core archive: %+v, %v", cfg, err) - } -} diff --git a/modules/go/tools/dd_topt_go_bootstrap/main.go b/modules/go/tools/dd_topt_go_bootstrap/main.go index ecdc1cb2..d7ec6ada 100644 --- a/modules/go/tools/dd_topt_go_bootstrap/main.go +++ b/modules/go/tools/dd_topt_go_bootstrap/main.go @@ -9,15 +9,12 @@ package main import ( "bytes" "context" - "encoding/json" "errors" "flag" "fmt" "os" - "os/exec" "path/filepath" "regexp" - "runtime" "strconv" "strings" @@ -25,92 +22,46 @@ import ( ) const ( - defaultRulesGoVersion = "0.60.0" - defaultRulesGoRemote = "https://github.com/DataDog/rules_test_optimization.git" - defaultRulesGoCommit = "" - defaultRulesGoVariant = "base" - defaultDatadogFetch = "git" - defaultRulesGoFetch = "git" - defaultRulesGoRepoName = "io_bazel_rules_go" - defaultOrchestrionVersion = "v1.12.0" - defaultDDTraceGoVersion = "v2.9.1" - defaultSyncRepoName = "test_optimization_data" - defaultDoctorTargetName = "dd_test_optimization_doctor" - defaultUploaderTargetName = "dd_upload_payloads" - defaultBazelrcPath = ".bazelrc" - defaultBazelrcConfig = "test-optimization" - defaultValidationScriptPath = "tools/test_optimization/validate_go_pilot.sh" - defaultGoRepositoriesFile = "repositories.bzl" - defaultBazelCommand = "bazel" - defaultGoBinary = "go" - defaultGoModSync = "targeted" - defaultWrapperPackage = "tools/build" - defaultWorkspaceWrapperFile = "dd_topt_go_test.bzl" - defaultPlainWrapperName = "dd_go_test" - defaultOptimizedWrapperName = "dd_topt_go_test" - defaultMinFreeDiskGB = 25 - managedBlockStart = "# BEGIN Datadog Go Orchestrion bootstrap" - managedBlockEnd = "# END Datadog Go Orchestrion bootstrap" - bazelrcBlockStart = "# BEGIN Datadog Test Optimization Bazelrc" - bazelrcBlockEnd = "# END Datadog Test Optimization Bazelrc" - validationScriptMarker = "# Generated by Datadog Go validation script generator." - guidedBlockStart = "# BEGIN Datadog Go Guided Setup" - guidedBlockEnd = "# END Datadog Go Guided Setup" - doctorBlockStart = "# BEGIN Datadog Go Doctor" - doctorBlockEnd = "# END Datadog Go Doctor" - uploaderBlockStart = "# BEGIN Datadog Go Uploader" - uploaderBlockEnd = "# END Datadog Go Uploader" - pinExportsBlockStart = "# BEGIN Datadog Go Pin Files" - pinExportsBlockEnd = "# END Datadog Go Pin Files" - wrapperBlockStart = "# BEGIN Datadog Go Wrapper" - wrapperBlockEnd = "# END Datadog Go Wrapper" - onboardingSummaryMarker = "" - defaultStarterOrchestrionYML = `--- -# yaml-language-server: $schema=https://datadoghq.dev/orchestrion/schema.json -meta: - name: datadog/go-bootstrap - description: Datadog starter configuration for Orchestrion. - -aspects: [] -` - sharedOrchestrionCacheDirName = "datadog-orchestrion-go-cache" - toolsBuildDir = "tools/build" - wrapperFileName = "dd_go_test.bzl" + defaultRulesGoVersion = "0.60.0" + defaultDatadogFetch = "git" + defaultRulesGoRepoName = "io_bazel_rules_go" + defaultSyncRepoName = "test_optimization_data" + defaultDoctorTargetName = "dd_test_optimization_doctor" + defaultUploaderTargetName = "dd_upload_payloads" + defaultBazelrcPath = ".bazelrc" + defaultBazelrcConfig = "test-optimization" + defaultValidationScriptPath = "tools/test_optimization/validate_go_pilot.sh" + defaultBazelCommand = "bazel" + defaultWrapperPackage = "tools/build" + defaultWorkspaceWrapperFile = "dd_topt_go_test.bzl" + defaultPlainWrapperName = "dd_go_test" + defaultOptimizedWrapperName = "dd_topt_go_test" + defaultMinFreeDiskGB = 25 + managedBlockStart = "# BEGIN Datadog Go Test Optimization bootstrap" + managedBlockEnd = "# END Datadog Go Test Optimization bootstrap" + // The Orchestrion bootstrap's MODULE.bazel block, which a rerun replaces. + legacyManagedBlockStart = "# BEGIN Datadog Go Orchestrion bootstrap" + legacyManagedBlockEnd = "# END Datadog Go Orchestrion bootstrap" + bazelrcBlockStart = "# BEGIN Datadog Test Optimization Bazelrc" + bazelrcBlockEnd = "# END Datadog Test Optimization Bazelrc" + validationScriptMarker = "# Generated by Datadog Go validation script generator." + guidedBlockStart = "# BEGIN Datadog Go Guided Setup" + guidedBlockEnd = "# END Datadog Go Guided Setup" + doctorBlockStart = "# BEGIN Datadog Go Doctor" + doctorBlockEnd = "# END Datadog Go Doctor" + uploaderBlockStart = "# BEGIN Datadog Go Uploader" + uploaderBlockEnd = "# END Datadog Go Uploader" + wrapperBlockStart = "# BEGIN Datadog Go Wrapper" + wrapperBlockEnd = "# END Datadog Go Wrapper" + onboardingSummaryMarker = "" + toolsBuildDir = "tools/build" + wrapperFileName = "dd_go_test.bzl" ) -var orchestrionPinFiles = []string{ - "go.mod", - "go.sum", - "orchestrion.tool.go", - "orchestrion.yml", -} - -var ddTraceGoModules = []string{ - "github.com/DataDog/dd-trace-go/v2", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2", -} - -var ddTraceGoValidationPackages = []string{ - "github.com/DataDog/dd-trace-go/v2/orchestrion", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2", -} - -// orchestrionToolPackages is the minimal package graph bootstrap resolves in -// targeted mode so Orchestrion can build from a readonly Go module graph. -var orchestrionToolPackages = append([]string{"github.com/DataDog/orchestrion"}, ddTraceGoValidationPackages...) - -var ddTraceGoWarmPackages = []string{ - "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer", - "github.com/DataDog/dd-trace-go/v2/internal/civisibility/integrations", - "github.com/DataDog/dd-trace-go/v2/internal/civisibility/integrations/gotesting", - "github.com/DataDog/dd-trace-go/v2/internal/civisibility/integrations/gotesting/coverage", - "github.com/DataDog/dd-trace-go/v2/profiler", - "github.com/DataDog/dd-trace-go/v2/instrumentation/env", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2", -} +// rulesGoForkPath matches the paths of the rules_go fork that Datadog vendored +// for Orchestrion, which ddtest instrument replaced: third_party/rgo/... and +// the older third_party/rules_go_orchestrion_. +var rulesGoForkPath = regexp.MustCompile(`third_party/(?:rgo/|rules_go_orchestrion_)`) // bazelrcRepoEnvKeys mirrors the public sync metadata environment contract. // The generated .bazelrc passes these values to repository/module resolution, @@ -184,74 +135,42 @@ type config struct { writeValidationScript bool writeOnboardingSummary string writeRootTargets bool - writeOrchestrionFiles bool writeWrapperTemplate bool - // checkGoRepositories enables WORKSPACE go_repository drift diagnostics. - checkGoRepositories bool - // printGoRepositoryUpdates prints the expected go_repository version tuple. - printGoRepositoryUpdates bool - bazelrcPath string - bazelrcConfig string - validationScriptPath string - // goRepositoriesFile is the workspace-relative file inspected for drift. - goRepositoriesFile string - // goRepositoriesRefreshCommand is an operator-owned command used to refresh - // go_repository declarations after targeted Go module sync succeeds. - goRepositoriesRefreshCommand string - bazelCommand string - bazelConfig string - goBinary string - goModSync string - goModSyncSet bool - service string - runtimeVersion string - goModulePath string - syncRepoName string - doctorTargetName string - uploaderTargetName string - validationDoctorTarget string - validationUploadTarget string - controlTargets []string - expectedTargets []string - extraSyncFlags []string - extraTestFlags []string - extraRunFlags []string - largeMonorepo bool - minFreeDiskGB int - shutdownBazelOnExit bool - defaultJobs int - wrapperPackage string - wrapperFile string - plainWrapperName string - optimizedWrapperName string - orchestrionVersion string - ddTraceGoVersion string - ddTraceGoVersions map[string]string - ddTraceGoVersionSet bool - validationDoctorSet bool - validationUploadSet bool - rulesGoRemote string - rulesGoCommit string - datadogFetch string - rulesGoFetch string - rulesGoFetchSet bool - rulesGoArchive *moduleArchive - rulesGoRepoName string - rtoCommit string - rtoArchiveURL string - rtoArchiveSHA256 string - rtoArchivePrefix string - rtoArchiveType string - // rulesGoRemoteSet records whether the operator explicitly selected the - // rules_go fork remote instead of accepting the inferred/default remote. - rulesGoRemoteSet bool - // rulesGoCommitSet records whether the operator explicitly selected the - // rules_go fork commit instead of accepting an inferred commit. - rulesGoCommitSet bool - rulesGoUpstream string - rulesGoUpstreamSet bool - rulesGoVariant string - rulesGoVariantSet bool + bazelrcPath string + bazelrcConfig string + validationScriptPath string + bazelCommand string + bazelConfig string + service string + runtimeVersion string + goModulePath string + syncRepoName string + doctorTargetName string + uploaderTargetName string + validationDoctorTarget string + validationUploadTarget string + controlTargets []string + expectedTargets []string + extraSyncFlags []string + extraTestFlags []string + extraRunFlags []string + largeMonorepo bool + minFreeDiskGB int + shutdownBazelOnExit bool + defaultJobs int + wrapperPackage string + wrapperFile string + plainWrapperName string + optimizedWrapperName string + validationDoctorSet bool + validationUploadSet bool + datadogFetch string + rulesGoRepoName string + rtoCommit string + rtoArchiveURL string + rtoArchiveSHA256 string + rtoArchivePrefix string + rtoArchiveType string // publishedPinsArchiveFetcher is test-only override for codeload downloads. publishedPinsArchiveFetcher onboardingpins.ArchiveFetcher // publishedPinsGitRunner is test-only override for git reachability checks. @@ -280,7 +199,7 @@ func parseFlags() config { var extraTestFlags stringListFlag var extraRunFlags stringListFlag flag.StringVar(&cfg.workspaceDir, "workspace", workspaceDefault, "Workspace root to update") - flag.StringVar(&cfg.goModuleDir, "go-module-dir", "", "Go module directory to pin for Orchestrion (defaults to workspace root)") + flag.StringVar(&cfg.goModuleDir, "go-module-dir", "", "Go module directory whose go.mod gives the module path and, without --runtime-version, the Go version to instrument (defaults to workspace root)") flag.BoolVar(&cfg.force, "force", false, "Allow overwriting Datadog-managed bootstrap files") flag.BoolVar(&cfg.guided, "guided", false, "Generate Datadog-managed single-service Go onboarding files") flag.BoolVar(&cfg.workspaceMode, "workspace-mode", false, "Enable WORKSPACE-oriented onboarding generation without editing WORKSPACE") @@ -289,26 +208,19 @@ func parseFlags() config { flag.BoolVar(&cfg.printValidationScript, "print-validation-script", false, "Print the Go Test Optimization validation script and exit without modifying files") flag.BoolVar(&cfg.printPublishedPins, "print-published-pins", false, "Print published Go onboarding pins and exit without modifying files") flag.BoolVar(&cfg.printOnboardingSummary, "print-onboarding-summary", false, "Print a Markdown Go onboarding summary and exit without modifying files") - flag.BoolVar(&cfg.verifyMainReachable, "verify-main-reachable", false, "Require --rto-commit or --rules-go-commit to be reachable from origin/main in --workspace") + flag.BoolVar(&cfg.verifyMainReachable, "verify-main-reachable", false, "Require --rto-commit to be reachable from origin/main in --workspace") flag.BoolVar(&cfg.writeBazelrc, "write-bazelrc", false, "Insert or replace the Datadog-managed .bazelrc configuration block") flag.BoolVar(&cfg.writeValidationScript, "write-validation-script", false, "Write the Go Test Optimization validation script") flag.StringVar(&cfg.writeOnboardingSummary, "write-onboarding-summary", "", "Workspace-relative Markdown file for the generated Go onboarding summary") - flag.BoolVar(&cfg.writeRootTargets, "write-root-targets", false, "Insert or replace root BUILD targets for doctor, uploader, and pin file exports") - flag.BoolVar(&cfg.writeOrchestrionFiles, "write-orchestrion-files", false, "Write orchestrion.tool.go and orchestrion.yml in the selected Go module directory") + flag.BoolVar(&cfg.writeRootTargets, "write-root-targets", false, "Insert or replace root BUILD targets for the doctor and uploader") flag.BoolVar(&cfg.writeWrapperTemplate, "write-wrapper-template", false, "Write a configurable repo-local Go wrapper template") - flag.BoolVar(&cfg.checkGoRepositories, "check-go-repositories", false, "Check checked-in WORKSPACE go_repository declarations for Orchestrion-related module drift") - flag.BoolVar(&cfg.printGoRepositoryUpdates, "print-go-repository-updates", false, "Print expected Orchestrion-related go_repository versions while checking repository drift") flag.StringVar(&cfg.bazelrcPath, "bazelrc-path", defaultBazelrcPath, "Path to the .bazelrc file to update when --write-bazelrc is set") flag.StringVar(&cfg.bazelrcConfig, "bazelrc-config", defaultBazelrcConfig, "Bazel config name used by the generated .bazelrc block") flag.StringVar(&cfg.validationScriptPath, "validation-script-path", defaultValidationScriptPath, "Workspace-relative path for --write-validation-script") - flag.StringVar(&cfg.goRepositoriesFile, "go-repositories-file", defaultGoRepositoriesFile, "Workspace-relative repositories.bzl file to inspect with --check-go-repositories") - flag.StringVar(&cfg.goRepositoriesRefreshCommand, "go-repositories-refresh-command", "", "Repository-owned command to refresh go_repository declarations after successful targeted module sync") flag.StringVar(&cfg.bazelCommand, "bazel-command", defaultBazelCommand, "Bazel command used by the generated validation script") flag.StringVar(&cfg.bazelConfig, "bazel-config", defaultBazelrcConfig, "Bazel config used by the generated validation script") - flag.StringVar(&cfg.goBinary, "go-binary", defaultGoBinary, "Go binary used for bootstrap module graph synchronization") - flag.StringVar(&cfg.goModSync, "go-mod-sync", defaultGoModSync, "Go module synchronization mode: targeted, tidy, or off") flag.StringVar(&cfg.service, "service", "", "Datadog service name for guided single-service Go setup") - flag.StringVar(&cfg.runtimeVersion, "runtime-version", "", "Go runtime version for guided single-service Go setup") + flag.StringVar(&cfg.runtimeVersion, "runtime-version", "", "Go version of the SDK that ddtest instruments, also the sync runtime version of guided setup") flag.StringVar(&cfg.syncRepoName, "sync-repo-name", defaultSyncRepoName, "Repository name generated by guided single-service Go setup") flag.StringVar(&cfg.doctorTargetName, "doctor-target-name", defaultDoctorTargetName, "Root doctor target name generated by guided single-service Go setup") flag.StringVar(&cfg.uploaderTargetName, "uploader-target-name", defaultUploaderTargetName, "Root uploader target name generated by guided single-service Go setup") @@ -327,43 +239,15 @@ func parseFlags() config { flag.StringVar(&cfg.wrapperFile, "wrapper-file", defaultWorkspaceWrapperFile, "Wrapper .bzl filename used with --write-wrapper-template") flag.StringVar(&cfg.plainWrapperName, "plain-wrapper-name", defaultPlainWrapperName, "Central repo-local Go test wrapper name used in the generated wrapper template") flag.StringVar(&cfg.optimizedWrapperName, "optimized-wrapper-name", defaultOptimizedWrapperName, "Compatibility alias for the central repo-local Go test wrapper") - flag.StringVar(&cfg.orchestrionVersion, "orchestrion-version", defaultOrchestrionVersion, "Orchestrion version to configure") - flag.StringVar(&cfg.ddTraceGoVersion, "dd-trace-go-version", defaultDDTraceGoVersion, "dd-trace-go version to pin for Orchestrion-backed instrumentation") - flag.StringVar(&cfg.rulesGoRemote, "rules-go-remote", defaultRulesGoRemote, "rules_go fork remote used for Orchestrion support") - flag.StringVar(&cfg.rulesGoCommit, "rules-go-commit", defaultRulesGoCommit, "rules_go fork commit used for Orchestrion support; inferred from Datadog git_override wiring when omitted") - flag.StringVar(&cfg.rulesGoUpstream, "rules-go-upstream", "default", "Datadog-managed rules_go upstream support id, for example v0_60_0") - flag.StringVar(&cfg.rulesGoVariant, "rules-go-variant", defaultRulesGoVariant, "rules_go Orchestrion variant to use: base") flag.StringVar(&cfg.datadogFetch, "datadog-fetch", defaultDatadogFetch, "WORKSPACE snippet fetch mode for Datadog companion repositories: git or archive") - flag.StringVar(&cfg.rulesGoFetch, "rules-go-fetch", defaultRulesGoFetch, "rules_go fetch mode: git or archive; Bzlmod preserves existing archive overrides by default") - flag.StringVar(&cfg.rulesGoRepoName, "rules-go-repo-name", defaultRulesGoRepoName, "WORKSPACE snippet repository name for the rules_go fork") - flag.StringVar(&cfg.rtoCommit, "rto-commit", "", "WORKSPACE snippet commit for Datadog repositories; falls back to --rules-go-commit when omitted") - flag.StringVar(&cfg.rtoArchiveURL, "rto-archive-url", "", "Datadog source archive URL for Bzlmod or WORKSPACE archive mode") + flag.StringVar(&cfg.rulesGoRepoName, "rules-go-repo-name", defaultRulesGoRepoName, "WORKSPACE snippet repository name of upstream rules_go") + flag.StringVar(&cfg.rtoCommit, "rto-commit", "", "WORKSPACE snippet and published pins commit for Datadog repositories") + flag.StringVar(&cfg.rtoArchiveURL, "rto-archive-url", "", "Datadog source archive URL for WORKSPACE archive mode") flag.StringVar(&cfg.rtoArchiveSHA256, "rto-archive-sha256", "", "Datadog source archive SHA256 for archive mode") flag.StringVar(&cfg.rtoArchivePrefix, "rto-archive-prefix", "", "Datadog source archive root prefix for archive mode") flag.StringVar(&cfg.rtoArchiveType, "rto-archive-type", "tar.gz", "Datadog source archive type for archive mode") flag.Parse() flag.Visit(func(f *flag.Flag) { - if f.Name == "dd-trace-go-version" { - cfg.ddTraceGoVersionSet = true - } - if f.Name == "rules-go-remote" { - cfg.rulesGoRemoteSet = true - } - if f.Name == "rules-go-commit" { - cfg.rulesGoCommitSet = true - } - if f.Name == "rules-go-fetch" { - cfg.rulesGoFetchSet = true - } - if f.Name == "rules-go-upstream" { - cfg.rulesGoUpstreamSet = true - } - if f.Name == "rules-go-variant" { - cfg.rulesGoVariantSet = true - } - if f.Name == "go-mod-sync" { - cfg.goModSyncSet = true - } if f.Name == "doctor-target" { cfg.validationDoctorSet = true } @@ -382,9 +266,6 @@ func parseFlags() config { cfg.extraSyncFlags = []string(extraSyncFlags) cfg.extraTestFlags = []string(extraTestFlags) cfg.extraRunFlags = []string(extraRunFlags) - if cfg.printGoRepositoryUpdates { - cfg.checkGoRepositories = true - } return cfg } @@ -392,37 +273,13 @@ func run(cfg config) error { if cfg.workspaceDir == "" { return errors.New("workspace directory is required") } - if strings.TrimSpace(cfg.goBinary) == "" { - cfg.goBinary = defaultGoBinary - } - if err := validateGoBinary(cfg.goBinary); err != nil { - return err - } - if strings.TrimSpace(cfg.goModSync) == "" { - cfg.goModSync = defaultGoModSync - } normalizeValidationScriptTargets(&cfg) - if _, err := rulesGoStripPrefix(cfg); err != nil { - return err - } if err := validateFetchMode(cfg.datadogFetch, "datadog-fetch"); err != nil { return err } - if err := validateFetchMode(cfg.rulesGoFetch, "rules-go-fetch"); err != nil { - return err - } - if err := validateGoModSyncMode(cfg.goModSync); err != nil { - return err - } - if err := validateGoRepositoryDiagnosticsConfig(cfg); err != nil { - return err - } if err := validateBootstrapMode(cfg); err != nil { return err } - if err := validateGoRuntimeCompatibility(cfg); err != nil { - return err - } if err := validateValidationScriptConfig(cfg); err != nil { return err } @@ -541,35 +398,6 @@ func run(cfg config) error { } cfg.goModulePath = goModulePath - moduleContent, err := os.ReadFile(cfg.moduleFile) - if err != nil { - return fmt.Errorf("read MODULE.bazel: %w", err) - } - - originalDDTraceGoVersionQuery := cfg.ddTraceGoVersion - if err := ensureBootstrapCanManageTracerConfig(string(moduleContent)); err != nil { - return err - } - if !cfg.ddTraceGoVersionSet { - if err := hydrateManagedTracerConfig(&cfg, string(moduleContent)); err != nil { - return err - } - } - if !cfg.rulesGoVariantSet && !cfg.rulesGoUpstreamSet { - if err := hydrateManagedRulesGoVariant(&cfg, string(moduleContent)); err != nil { - return err - } - } - - if cfg.ddTraceGoVersionSet { - if err := normalizeDDTraceGoVersion(&cfg); err != nil { - return err - } - } else if !cfg.hasTracerConfig() { - cfg.ddTraceGoVersion = defaultDDTraceGoVersion - cfg.ddTraceGoVersions = nil - } - if err := patchModuleFile(cfg); err != nil { return err } @@ -578,43 +406,8 @@ func run(cfg config) error { return err } } - if err := writeOrchestrionToolFile(cfg); err != nil { - return err - } - if err := ensureCIVisibilityOrchestrionImport(cfg); err != nil { - return err - } - targetedSyncSucceeded := false - if cfg.goModSync != "off" { - if err := syncDDTraceGoVersion(cfg); err != nil { - return err - } - targetedSyncSucceeded = cfg.goModSync == "targeted" - if cfg.goModSync == "tidy" { - if err := warmOrchestrionModuleCache(cfg); err != nil { - return err - } - } - if err := verifyResolvedBootstrapModuleVersions(cfg); err != nil { - return fmt.Errorf("%w\nbootstrap may have already updated these files: MODULE.bazel, go.mod, go.sum, orchestrion.tool.go%s", err, maybeChangedStarterYML(cfg.goModuleDir)) - } - } - if cfg.checkGoRepositories { - if err := checkGoRepositories(cfg, targetedSyncSucceeded); err != nil { - return err - } - } - if err := writeStarterOrchestrionYML(cfg); err != nil { - return err - } - fmt.Printf("Updated %s and pinned Orchestrion in %s\n", cfg.moduleFile, cfg.goModuleDir) - switch { - case cfg.ddTraceGoVersionSet && cfg.usesPerModuleTracerConfig(): - fmt.Printf("Resolved dd-trace-go query %q to per-module versions and persisted the exact resolved versions.\n", originalDDTraceGoVersionQuery) - case cfg.ddTraceGoVersionSet && originalDDTraceGoVersionQuery != cfg.ddTraceGoVersion: - fmt.Printf("Normalized dd-trace-go query %q to canonical version %q and persisted the canonical version.\n", originalDDTraceGoVersionQuery, cfg.ddTraceGoVersion) - } + fmt.Printf("Updated %s: Go tests build with the Go SDK that ddtest instruments\n", cfg.moduleFile) return nil } @@ -633,7 +426,7 @@ func ensureFileExists(path, label string) error { } // validateBootstrapMode rejects flag combinations that would make bootstrap -// mutate the wrong build system or run an implicit large-repo module sync. +// mutate the wrong build system. func validateBootstrapMode(cfg config) error { if cfg.guided && cfg.workspaceMode { return errors.New("--guided and --workspace-mode are mutually exclusive") @@ -654,11 +447,8 @@ func validateBootstrapMode(cfg config) error { return errors.New("--workspace-mode --print-workspace-snippet requires --runtime-version") } } - if !cfg.printWorkspaceSnippet && !cfg.printBazelrcSnippet && !cfg.printValidationScript && !cfg.printPublishedPins && !cfg.printOnboardingSummary && strings.TrimSpace(cfg.writeOnboardingSummary) == "" && !cfg.writeBazelrc && !cfg.writeValidationScript && !cfg.writeRootTargets && !cfg.writeOrchestrionFiles && !cfg.writeWrapperTemplate && !cfg.checkGoRepositories && !cfg.goModSyncSet { - return errors.New("--workspace-mode requires at least one action: --print-workspace-snippet, --print-bazelrc-snippet, --print-validation-script, --print-published-pins, --print-onboarding-summary, --write-onboarding-summary, --write-bazelrc, --write-validation-script, --write-root-targets, --write-orchestrion-files, --write-wrapper-template, --check-go-repositories, or an explicit --go-mod-sync") - } - if cfg.goModSyncSet && cfg.goModSync != "off" && !cfg.writeOrchestrionFiles { - return errors.New("--workspace-mode with --go-mod-sync=targeted or tidy requires --write-orchestrion-files so tool imports exist before module sync") + if !cfg.printWorkspaceSnippet && !cfg.printBazelrcSnippet && !cfg.printValidationScript && !cfg.printPublishedPins && !cfg.printOnboardingSummary && strings.TrimSpace(cfg.writeOnboardingSummary) == "" && !cfg.writeBazelrc && !cfg.writeValidationScript && !cfg.writeRootTargets && !cfg.writeWrapperTemplate { + return errors.New("--workspace-mode requires at least one action: --print-workspace-snippet, --print-bazelrc-snippet, --print-validation-script, --print-published-pins, --print-onboarding-summary, --write-onboarding-summary, --write-bazelrc, --write-validation-script, --write-root-targets, or --write-wrapper-template") } for _, label := range cfg.expectedTargets { if strings.TrimSpace(label) == "" { @@ -671,66 +461,10 @@ func validateBootstrapMode(cfg config) error { return nil } -// validateGoRuntimeCompatibility rejects managed SDK versions that cannot -// build the configured Orchestrion release before bootstrap writes any files. -func validateGoRuntimeCompatibility(cfg config) error { - runtimeVersion := strings.TrimSpace(cfg.runtimeVersion) - if runtimeVersion == "" { - return nil - } - orchestrionVersion, err := parseVersionTriplet(cfg.orchestrionVersion) - if err != nil { - return fmt.Errorf("invalid Orchestrion version %q: %w", cfg.orchestrionVersion, err) - } - if compareVersionTriplets(orchestrionVersion, [3]int{1, 12, 0}) < 0 { - return nil - } - goVersion, err := parseVersionTriplet(runtimeVersion) - if err != nil { - return fmt.Errorf("invalid Go runtime version %q: %w", runtimeVersion, err) - } - if compareVersionTriplets(goVersion, [3]int{1, 25, 0}) < 0 { - return fmt.Errorf("Orchestrion %s requires Go 1.25.0 or newer; got --runtime-version=%s", cfg.orchestrionVersion, runtimeVersion) - } - return nil -} - -func parseVersionTriplet(value string) ([3]int, error) { - match := regexp.MustCompile(`^[vV]?(\d+)\.(\d+)(?:\.(\d+))?(?:[-+].*)?$`).FindStringSubmatch(strings.TrimSpace(value)) - if match == nil { - return [3]int{}, errors.New("expected a semantic version such as 1.25.0") - } - var parsed [3]int - for i := range parsed { - if match[i+1] == "" { - continue - } - component, err := strconv.Atoi(match[i+1]) - if err != nil { - return [3]int{}, fmt.Errorf("parse numeric component: %w", err) - } - parsed[i] = component - } - return parsed, nil -} - -func compareVersionTriplets(left, right [3]int) int { - for i := range left { - if left[i] < right[i] { - return -1 - } - if left[i] > right[i] { - return 1 - } - } - return 0 -} - // runWorkspaceMode writes only local scaffolding files for WORKSPACE consumers. // It never edits WORKSPACE itself; operators can pair it with the printed // repository snippet and place that snippet where their monorepo expects it. func runWorkspaceMode(cfg config) error { - targetedSyncSucceeded := false if cfg.writeBazelrc { if err := writeBazelrcBlock(cfg); err != nil { return err @@ -746,44 +480,11 @@ func runWorkspaceMode(cfg config) error { return err } } - if cfg.writeOrchestrionFiles { - if err := writeWorkspaceOrchestrionToolFile(cfg); err != nil { - return err - } - if err := writeStarterOrchestrionYML(cfg); err != nil { - return err - } - if err := ensureGuidedGoModuleBuild(cfg); err != nil { - return err - } - } if cfg.writeWrapperTemplate { if err := ensureWorkspaceWrapperTemplate(cfg); err != nil { return err } } - if cfg.goModSyncSet && cfg.goModSync != "off" { - if err := ensureFileExists(filepath.Join(cfg.goModuleDir, "go.mod"), "go.mod"); err != nil { - return err - } - if err := syncDDTraceGoVersion(cfg); err != nil { - return err - } - targetedSyncSucceeded = cfg.goModSync == "targeted" - if cfg.goModSync == "tidy" { - if err := warmOrchestrionModuleCache(cfg); err != nil { - return err - } - } - if err := verifyResolvedBootstrapModuleVersions(cfg); err != nil { - return fmt.Errorf("%w\nbootstrap may have already updated these files: go.mod, go.sum, orchestrion.tool.go%s", err, maybeChangedStarterYML(cfg.goModuleDir)) - } - } - if cfg.checkGoRepositories { - if err := checkGoRepositories(cfg, targetedSyncSucceeded); err != nil { - return err - } - } return nil } @@ -820,9 +521,6 @@ func patchModuleFile(cfg config) error { return fmt.Errorf("read MODULE.bazel: %w", err) } text := string(content) - if err := configureModuleArchive(&cfg, text); err != nil { - return err - } if cfg.guided { if err := validateGuidedPrerequisites(text); err != nil { @@ -830,6 +528,13 @@ func patchModuleFile(cfg config) error { } } + // The Orchestrion block's fork override is replaced below; any other + // override of rules_go with the fork is the consumer's to remove. + unmanaged, _ := stripManagedSectionIfPresent(text, legacyManagedBlockStart, legacyManagedBlockEnd) + if err := rejectRulesGoForkOverride(unmanaged); err != nil { + return err + } + if !strings.Contains(text, `bazel_dep(name = "rules_go"`) && !strings.Contains(text, `bazel_dep(name="rules_go"`) { snippet := fmt.Sprintf(`bazel_dep(name = "rules_go", version = "%s")%s`, defaultRulesGoVersion, "\n") text, err = insertAfterModuleDecl(text, snippet) @@ -838,51 +543,21 @@ func patchModuleFile(cfg config) error { } } - if cfg.rulesGoArchive == nil && !cfg.rulesGoCommitSet { - if remote, commit := inferManagedRulesGoOverride(text); remote != "" && commit != "" { - if !cfg.rulesGoRemoteSet { - cfg.rulesGoRemote = remote - } - cfg.rulesGoCommit = commit - } - } - - if cfg.rulesGoArchive == nil && !cfg.rulesGoRemoteSet && !cfg.rulesGoCommitSet && cfg.rulesGoCommit == "" { - if remote, commit := inferDatadogRepoOverride(text); remote != "" && commit != "" { - cfg.rulesGoRemote = remote - cfg.rulesGoCommit = commit - } - } - - if cfg.rulesGoArchive == nil && strings.TrimSpace(cfg.rulesGoCommit) == "" { - return errors.New("rules_go fork commit is required; add a git_override for datadog-rules-test-optimization-go/datadog-rules-test-optimization or pass --rules-go-commit explicitly") + managedBlock, err := managedModuleBlock(cfg) + if err != nil { + return err } - if !strings.Contains(text, managedBlockStart) && moduleOverridePresent(text, "rules_go") { - compatible, err := rulesGoOverrideCompatible(text, cfg) - if err != nil { - return err - } - if !compatible { - return errors.New("MODULE.bazel already contains an incompatible rules_go override; update it manually before running the Datadog bootstrap") - } - if cfg.rulesGoArchive != nil { - // Move the compatible fork override into the managed block rather - // than emitting a second override for the same module. - for _, block := range topLevelCallBlocks(text) { - if block.function == "archive_override" && literalAttribute(stripStarlarkLineComments(block.text), "module_name") == "rules_go" { - text = text[:block.start] + text[block.end:] - break - } - } - } + // Rerunning the bootstrap migrates a workspace from Orchestrion: the new + // block takes the place of the old one. + if strings.Contains(text, managedBlockStart) { + text, err = removeManagedSectionIfPresent(text, legacyManagedBlockStart, legacyManagedBlockEnd) + } else if strings.Contains(text, legacyManagedBlockStart) || strings.Contains(text, legacyManagedBlockEnd) { + text, err = replaceManagedSection(text, legacyManagedBlockStart, legacyManagedBlockEnd, managedBlock) } - - managedBlock, err := managedModuleBlockWithError(cfg) if err != nil { return err } - text, err = replaceManagedSection(text, managedBlockStart, managedBlockEnd, managedBlock) if err != nil { return err @@ -907,66 +582,59 @@ func patchModuleFile(cfg config) error { return nil } -func managedModuleBlock(cfg config) string { - block, _ := managedModuleBlockWithError(cfg) - return block -} - -func managedModuleBlockWithError(cfg config) (string, error) { - stripPrefix, err := rulesGoStripPrefix(cfg) - if err != nil { - return "", err - } - override := fmt.Sprintf(`git_override( - module_name = "rules_go", - remote = "%s", - commit = "%s", - strip_prefix = "%s", -) -`, cfg.rulesGoRemote, cfg.rulesGoCommit, stripPrefix) - if cfg.rulesGoArchive != nil { - override = cfg.rulesGoArchive.render(stripPrefix) +// managedModuleBlock configures the Go SDK that ddtest instruments: the +// --runtime-version release, or else the version that the Go module's go.mod +// selects. +func managedModuleBlock(cfg config) (string, error) { + sdk := fmt.Sprintf("go_version = %q", strings.TrimSpace(cfg.runtimeVersion)) + if strings.TrimSpace(cfg.runtimeVersion) == "" { + pkg, err := goModuleBazelPackage(cfg) + if err != nil { + return "", err + } + label := pkg + ":go.mod" + // The extension reads go.mod through its label, which needs a package. + dir := goModuleDirOrWorkspace(cfg) + buildFile, err := selectPackageFile(dir) + if err != nil { + return "", err + } + if _, err := os.Stat(buildFile); err != nil { + return "", fmt.Errorf("topt_go_ddtest.sdk(go_mod = %q) needs a BUILD file next to go.mod in %s; add one, or pass --runtime-version", label, dir) + } + sdk = fmt.Sprintf("go_mod = %q", label) } return fmt.Sprintf(`%s -%s -%s -orchestrion = use_extension("@rules_go//go:extensions.bzl", "orchestrion") -orchestrion.from_source( - version = "%s", -%s -%s +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", ) -use_repo(orchestrion, "rules_go_orchestrion_tool") +topt_go_ddtest.sdk(%s) %s -`, managedBlockStart, override, managedGoSDKBlock(cfg), cfg.orchestrionVersion, managedTracerConfigBlock(cfg), managedOrchestrionGoSDKAttrs(cfg), managedBlockEnd), nil -} - -func managedGoSDKBlock(cfg config) string { - version := strings.TrimSpace(cfg.runtimeVersion) - if version == "" { - return "" - } - return fmt.Sprintf(`test_optimization_go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") -test_optimization_go_sdk.download( - name = "test_optimization_go_sdk", - version = %q, -) -use_repo(test_optimization_go_sdk, "test_optimization_go_sdk") -`, version) -} - -func managedOrchestrionGoSDKAttrs(cfg config) string { - version := strings.TrimSpace(cfg.runtimeVersion) - if version == "" { - return "" +`, managedBlockStart, sdk, managedBlockEnd), nil +} + +// rejectRulesGoForkOverride fails when MODULE.bazel overrides rules_go with +// the removed Datadog fork, which upstream rules_go replaces. +func rejectRulesGoForkOverride(content string) error { + for _, block := range topLevelCallBlocks(stripStarlarkLineComments(content)) { + attr := "strip_prefix" + switch block.function { + case "git_override", "archive_override": + case "local_path_override": + attr = "path" + default: + continue + } + if literalAttribute(block.text, "module_name") != "rules_go" { + continue + } + value, _ := callAttributeValue(block.text, attr) + if rulesGoForkPath.MatchString(strings.NewReplacer(`\\`, "/", `\`, "/").Replace(value)) { + return fmt.Errorf("MODULE.bazel has a %s of rules_go with the removed Datadog rules_go fork (%s = %s); remove it so that rules_go comes from the Bazel Central Registry, then rerun the bootstrap", block.function, attr, value) + } } - return fmt.Sprintf(` go_sdk_root = "@test_optimization_go_sdk//:ROOT", - go_sdk_version = %q,`, version) -} - -func validateRulesGoVariant(variant string) error { - _, err := onboardingpins.RulesGoStripPrefix("default", variant) - return err + return nil } func validateFetchMode(value, flagName string) error { @@ -976,188 +644,8 @@ func validateFetchMode(value, flagName string) error { return fmt.Errorf("--%s must be \"git\" or \"archive\", got %q", flagName, value) } -// validateGoModSyncMode validates the bootstrap strategy used for go.mod/go.sum. -func validateGoModSyncMode(value string) error { - switch value { - case "targeted", "tidy", "off": - return nil - default: - return fmt.Errorf("--go-mod-sync must be \"targeted\", \"tidy\", or \"off\", got %q", value) - } -} - -// validateGoRepositoryDiagnosticsConfig rejects ambiguous diagnostics options -// before bootstrap has a chance to mutate any workspace files. -func validateGoRepositoryDiagnosticsConfig(cfg config) error { - if !cfg.checkGoRepositories && strings.TrimSpace(cfg.goRepositoriesRefreshCommand) == "" { - return nil - } - if !cfg.checkGoRepositories { - return errors.New("--go-repositories-refresh-command requires --check-go-repositories") - } - if strings.TrimSpace(cfg.goRepositoriesFile) == "" { - return errors.New("--go-repositories-file must be non-empty") - } - if strings.ContainsAny(cfg.goRepositoriesFile, "\x00\r\n") { - return fmt.Errorf("--go-repositories-file must not contain control characters, got %q", cfg.goRepositoriesFile) - } - if command := cfg.goRepositoriesRefreshCommand; command != "" && strings.ContainsAny(command, "\x00\r\n") { - return fmt.Errorf("--go-repositories-refresh-command must not contain control characters, got %q", command) - } - return nil -} - -// goRepositoryDeclaration captures the small go_repository surface bootstrap -// needs for diagnostics. The checker intentionally does not try to evaluate -// Starlark; it only reads literal declarations generated by common Gazelle -// update-repos flows. -type goRepositoryDeclaration struct { - importpath string - name string - version string -} - -// goRepositoryIssue describes one required go_repository declaration that is -// missing or pinned to a version different from the bootstrap contract. -type goRepositoryIssue struct { - modulePath string - currentVersion string - expectedVersion string - missing bool -} - -// goRepositoryDiagnostics is the complete result of checking one repositories -// file against the effective Go/Orchestrion bootstrap versions. -type goRepositoryDiagnostics struct { - path string - issues []goRepositoryIssue - expected map[string]string -} - -// ok reports whether every required go_repository declaration is present and -// pinned to the expected version. -func (d goRepositoryDiagnostics) ok() bool { - return len(d.issues) == 0 -} - -// checkGoRepositories verifies checked-in WORKSPACE repository declarations -// without rewriting them. If the operator provides a refresh command and this -// bootstrap run already completed targeted module sync, the command is run and -// the file is rechecked. -func checkGoRepositories(cfg config, targetedSyncSucceeded bool) error { - path, err := resolveWorkspaceRelativeFile(cfg.workspaceDir, cfg.goRepositoriesFile, "--go-repositories-file") - if err != nil { - return err - } - diagnostics, err := evaluateGoRepositoriesFile(cfg, path) - if err != nil { - return err - } - if cfg.printGoRepositoryUpdates { - fmt.Print(renderGoRepositoryUpdateSummary(diagnostics)) - } - if diagnostics.ok() { - return nil - } - - initialMessage := renderGoRepositoryDiagnostics(diagnostics) - refreshCommand := strings.TrimSpace(cfg.goRepositoriesRefreshCommand) - if refreshCommand == "" { - return errors.New(initialMessage) - } - if !targetedSyncSucceeded { - return fmt.Errorf("%s\n\n%s", initialMessage, "The --go-repositories-refresh-command was not run because it only runs after a successful --go-mod-sync=targeted bootstrap sync.") - } - if err := runGoRepositoriesRefreshCommand(cfg, refreshCommand); err != nil { - return fmt.Errorf("%s\n\nrun --go-repositories-refresh-command %q: %w", initialMessage, refreshCommand, err) - } - diagnostics, err = evaluateGoRepositoriesFile(cfg, path) - if err != nil { - return err - } - if cfg.printGoRepositoryUpdates { - fmt.Print(renderGoRepositoryUpdateSummary(diagnostics)) - } - if !diagnostics.ok() { - return fmt.Errorf("go_repository declarations are still stale after --go-repositories-refresh-command %q:\n\n%s", refreshCommand, renderGoRepositoryDiagnostics(diagnostics)) - } - return nil -} - -// evaluateGoRepositoriesFile parses one repositories.bzl file and compares it -// with the exact module versions configured by bootstrap. -func evaluateGoRepositoriesFile(cfg config, path string) (goRepositoryDiagnostics, error) { - content, err := os.ReadFile(path) - if err != nil { - if os.IsNotExist(err) { - return goRepositoryDiagnostics{}, fmt.Errorf("go_repository file not found at %s; pass --go-repositories-file or disable --check-go-repositories", path) - } - return goRepositoryDiagnostics{}, fmt.Errorf("read go_repository file %s: %w", path, err) - } - declarations := parseGoRepositoryDeclarations(string(content)) - expected := expectedGoRepositoryVersions(cfg) - issues := make([]goRepositoryIssue, 0) - for _, modulePath := range requiredGoRepositoryImportpaths() { - expectedVersion := expected[modulePath] - decl, ok := declarations[modulePath] - if !ok { - issues = append(issues, goRepositoryIssue{ - modulePath: modulePath, - expectedVersion: expectedVersion, - missing: true, - }) - continue - } - if decl.version != expectedVersion { - issues = append(issues, goRepositoryIssue{ - modulePath: modulePath, - currentVersion: decl.version, - expectedVersion: expectedVersion, - }) - } - } - return goRepositoryDiagnostics{ - path: path, - issues: issues, - expected: expected, - }, nil -} - -// parseGoRepositoryDeclarations extracts literal go_repository importpath, -// name, and version attributes. This intentionally small parser is enough for -// Gazelle-generated repository files and avoids pretending to execute Starlark. -func parseGoRepositoryDeclarations(content string) map[string]goRepositoryDeclaration { - content = stripStarlarkLineComments(content) - blockPattern := regexp.MustCompile(`(?s)go_repository\s*\((.*?)\)`) - attrPattern := regexp.MustCompile(`(?m)([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:"([^"]*)"|'([^']*)')`) - declarations := map[string]goRepositoryDeclaration{} - for _, block := range blockPattern.FindAllStringSubmatch(content, -1) { - attrs := map[string]string{} - for _, match := range attrPattern.FindAllStringSubmatch(block[1], -1) { - value := match[2] - if value == "" { - value = match[3] - } - attrs[match[1]] = value - } - importpath := attrs["importpath"] - if importpath == "" { - continue - } - if _, exists := declarations[importpath]; exists { - continue - } - declarations[importpath] = goRepositoryDeclaration{ - importpath: importpath, - name: attrs["name"], - version: attrs["version"], - } - } - return declarations -} - // stripStarlarkLineComments removes comments before the lightweight -// go_repository parser runs. It keeps hash characters inside quoted strings so +// MODULE.bazel readers run. It keeps hash characters inside quoted strings so // unusual but valid attribute values do not change meaning. func stripStarlarkLineComments(content string) string { var buf strings.Builder @@ -1193,148 +681,29 @@ func stripStarlarkLineComments(content string) string { return buf.String() } -// expectedGoRepositoryVersions returns the module versions that WORKSPACE -// go_repository declarations must match for the current bootstrap config. -func expectedGoRepositoryVersions(cfg config) map[string]string { - versions := cfg.effectiveDDTraceGoVersions() - expected := make(map[string]string, len(versions)+1) - expected["github.com/DataDog/orchestrion"] = cfg.orchestrionVersion - for _, modulePath := range ddTraceGoModules { - expected[modulePath] = versions[modulePath] - } - return expected -} - -// requiredGoRepositoryImportpaths keeps diagnostics scoped to modules that the -// Go/Orchestrion bootstrap owns. Repository-specific modules are intentionally -// out of scope. -func requiredGoRepositoryImportpaths() []string { - modules := make([]string, 0, len(ddTraceGoModules)+1) - modules = append(modules, "github.com/DataDog/orchestrion") - modules = append(modules, ddTraceGoModules...) - return modules -} - -// renderGoRepositoryDiagnostics builds the actionable error shown when checked -// in go_repository declarations are missing or stale. -func renderGoRepositoryDiagnostics(diagnostics goRepositoryDiagnostics) string { - var buf strings.Builder - fmt.Fprintf(&buf, "go_repository diagnostics failed for %s.\n", diagnostics.path) - buf.WriteString("Refresh the checked-in go_repository declarations with your repository-owned update command, or pass --go-repositories-refresh-command after a successful --go-mod-sync=targeted run.\n\n") - buf.WriteString("Mismatches:\n") - for _, issue := range diagnostics.issues { - if issue.missing { - fmt.Fprintf(&buf, "- %s is missing; expected version %s\n", issue.modulePath, issue.expectedVersion) - continue - } - current := issue.currentVersion - if current == "" { - current = "" - } - fmt.Fprintf(&buf, "- %s is %s; expected %s\n", issue.modulePath, current, issue.expectedVersion) +// normalizeValidationScriptTargets keeps generated validation scripts aligned +// with guided root targets unless the operator selected explicit script labels. +func normalizeValidationScriptTargets(cfg *config) { + if !cfg.validationDoctorSet && cfg.doctorTargetName != "" && (cfg.validationDoctorTarget == "" || cfg.validationDoctorTarget == "//:"+defaultDoctorTargetName) { + cfg.validationDoctorTarget = "//:" + cfg.doctorTargetName } - buf.WriteString("\nExpected declarations:\n") - for _, modulePath := range requiredGoRepositoryImportpaths() { - fmt.Fprintf(&buf, "go_repository(\n name = %q,\n importpath = %q,\n version = %q,\n)\n\n", goRepositoryName(modulePath), modulePath, diagnostics.expected[modulePath]) + if !cfg.validationUploadSet && cfg.uploaderTargetName != "" && (cfg.validationUploadTarget == "" || cfg.validationUploadTarget == "//:"+defaultUploaderTargetName) { + cfg.validationUploadTarget = "//:" + cfg.uploaderTargetName } - return strings.TrimRight(buf.String(), "\n") } -// renderGoRepositoryUpdateSummary prints the same expected version tuple in a -// success-friendly format for operators who want to update repositories by hand. -func renderGoRepositoryUpdateSummary(diagnostics goRepositoryDiagnostics) string { - var buf strings.Builder - fmt.Fprintf(&buf, "Expected Orchestrion go_repository versions for %s:\n", diagnostics.path) - for _, modulePath := range requiredGoRepositoryImportpaths() { - fmt.Fprintf(&buf, "- %s -> %s\n", modulePath, diagnostics.expected[modulePath]) +// validateBazelrcConfig validates the config suffix used in generated Bazel +// flags. +func validateBazelrcConfig(configName string) error { + // Bazel config suffixes cannot be empty because every generated command is + // documented as --config=. + if strings.TrimSpace(configName) == "" { + return errors.New("--bazelrc-config must be non-empty") } - if diagnostics.ok() { - buf.WriteString("All checked go_repository declarations match the bootstrap configuration.\n") + if strings.ContainsAny(configName, " \t\r\n") { + return fmt.Errorf("--bazelrc-config must not contain whitespace, got %q", configName) } - return buf.String() -} - -// goRepositoryName mirrors Gazelle's common name normalization closely enough -// for copy/paste diagnostics. Consumers should still run their own refresh -// command so checksums and repository naming conventions remain repo-owned. -func goRepositoryName(modulePath string) string { - parts := strings.Split(modulePath, "/") - if len(parts) > 0 { - domainParts := strings.Split(parts[0], ".") - for left, right := 0, len(domainParts)-1; left < right; left, right = left+1, right-1 { - domainParts[left], domainParts[right] = domainParts[right], domainParts[left] - } - parts[0] = strings.Join(domainParts, ".") - } - name := strings.ToLower(strings.Join(parts, "/")) - replacer := strings.NewReplacer("/", "_", ".", "_", "-", "_") - return replacer.Replace(name) -} - -// runGoRepositoriesRefreshCommand runs the repository-owned update command. -// The command is explicit operator input, so shell execution is intentional: -// it supports existing monorepo refresh commands with flags and environment. -func runGoRepositoriesRefreshCommand(cfg config, command string) error { - var cmd *exec.Cmd - if runtime.GOOS == "windows" { - cmd = exec.Command("cmd", "/C", command) - } else { - cmd = exec.Command("sh", "-c", command) - } - cmd.Dir = cfg.workspaceDir - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Env = os.Environ() - return cmd.Run() -} - -// validateGoBinary rejects shell-style command strings. Bootstrap executes the -// selected Go binary directly, so this flag must contain only an executable name -// or path, not arguments or shell syntax. -func validateGoBinary(value string) error { - trimmed := strings.TrimSpace(value) - if trimmed == "" { - return errors.New("--go-binary cannot be empty") - } - if trimmed != value { - return fmt.Errorf("--go-binary must not contain leading or trailing whitespace: %q", value) - } - if strings.ContainsAny(trimmed, "\x00\r\n\t") { - return fmt.Errorf("--go-binary must not contain control characters: %q", value) - } - if len(strings.Fields(trimmed)) != 1 { - return fmt.Errorf("--go-binary must be a single executable path without arguments: %q", value) - } - base := filepath.Base(trimmed) - if base != "go" && base != "go.exe" { - return fmt.Errorf("--go-binary must point to an executable named \"go\" or \"go.exe\", got %q", value) - } - return nil -} - -// normalizeValidationScriptTargets keeps generated validation scripts aligned -// with guided root targets unless the operator selected explicit script labels. -func normalizeValidationScriptTargets(cfg *config) { - if !cfg.validationDoctorSet && cfg.doctorTargetName != "" && (cfg.validationDoctorTarget == "" || cfg.validationDoctorTarget == "//:"+defaultDoctorTargetName) { - cfg.validationDoctorTarget = "//:" + cfg.doctorTargetName - } - if !cfg.validationUploadSet && cfg.uploaderTargetName != "" && (cfg.validationUploadTarget == "" || cfg.validationUploadTarget == "//:"+defaultUploaderTargetName) { - cfg.validationUploadTarget = "//:" + cfg.uploaderTargetName - } -} - -// validateBazelrcConfig validates the config suffix used in generated Bazel -// flags. -func validateBazelrcConfig(configName string) error { - // Bazel config suffixes cannot be empty because every generated command is - // documented as --config=. - if strings.TrimSpace(configName) == "" { - return errors.New("--bazelrc-config must be non-empty") - } - if strings.ContainsAny(configName, " \t\r\n") { - return fmt.Errorf("--bazelrc-config must not contain whitespace, got %q", configName) - } - return nil + return nil } // validateValidationScriptConfig validates every value copied into the @@ -1478,17 +847,6 @@ func bazelrcSnippet(cfg config) (string, error) { return buf.String(), nil } -func apparentRulesGoRepoName(cfg config) string { - rulesGoRepoName := cfg.rulesGoRepoName - if !cfg.workspaceMode { - return "rules_go" - } - if rulesGoRepoName == "" { - return defaultRulesGoRepoName - } - return rulesGoRepoName -} - // writeBazelrcBlock inserts or replaces the managed .bazelrc block. func writeBazelrcBlock(cfg config) error { snippet, err := bazelrcSnippet(cfg) @@ -1538,7 +896,6 @@ func validationScript(cfg config) (string, error) { fmt.Fprintf(&buf, "SYNC_REPO=%s\n", shellQuote(cfg.syncRepoName)) fmt.Fprintf(&buf, "DOCTOR_TARGET=%s\n", shellQuote(cfg.validationDoctorTarget)) fmt.Fprintf(&buf, "UPLOAD_TARGET=%s\n", shellQuote(cfg.validationUploadTarget)) - fmt.Fprintf(&buf, "RULES_GO_ORCHESTRION_PACKAGE=%s\n", shellQuote("@"+apparentRulesGoRepoName(cfg)+"//go/private/orchestrion")) buf.WriteString("WORKSPACE_DIR=\"$(pwd -P)\"\n") buf.WriteString("BEP_TMP_ROOT=\"\"\n") buf.WriteString("BEP_JSON_DIR=\"\"\n") @@ -1659,22 +1016,12 @@ run_step() { } validate_disabled_bootstrap() { - local alias_files log "validate ordinary no-config bootstrap" if ! env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ "${BAZEL}" query "@${SYNC_REPO}//:test_optimization_files"; then warn "ordinary no-config metadata bootstrap failed" return 1 fi - alias_files="$( - env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ - "${BAZEL}" cquery \ - "${RULES_GO_ORCHESTRION_PACKAGE}:tool_binary" --output=files - )" || return $? - if [[ -n "${alias_files}" ]]; then - warn "ordinary no-config Orchestrion alias unexpectedly exposed files: ${alias_files}" - return 1 - fi log "validate explicit disabled precedence" env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ @@ -1839,10 +1186,7 @@ func writeValidationScriptFile(cfg config) error { func resolvePublishedPins(cfg config) (onboardingpins.Pins, error) { commit := strings.TrimSpace(cfg.rtoCommit) if commit == "" { - commit = strings.TrimSpace(cfg.rulesGoCommit) - } - if commit == "" { - return onboardingpins.Pins{}, errors.New("published pins require --rto-commit or --rules-go-commit with the squash-merged origin/main SHA") + return onboardingpins.Pins{}, errors.New("published pins require --rto-commit with the squash-merged origin/main SHA") } workspaceDir := cfg.workspaceDir if workspaceDir == "" { @@ -1851,12 +1195,7 @@ func resolvePublishedPins(cfg config) (onboardingpins.Pins, error) { return onboardingpins.Resolve(context.Background(), onboardingpins.Options{ WorkspaceDir: workspaceDir, Commit: commit, - Remote: cfg.rulesGoRemote, - RulesGoUpstream: cfg.rulesGoUpstream, - Variant: cfg.rulesGoVariant, ArchiveType: cfg.rtoArchiveType, - DDTraceGoVersion: cfg.ddTraceGoVersion, - OrchestrionVersion: cfg.orchestrionVersion, VerifyMainReachable: cfg.verifyMainReachable, FetchArchive: cfg.publishedPinsArchiveFetcher, RunGit: cfg.publishedPinsGitRunner, @@ -1952,13 +1291,10 @@ func workspaceSnippet(cfg config) (string, error) { return "", errors.New("--rules-go-repo-name must be non-empty") } rtoCommit := cfg.rtoCommit - if strings.TrimSpace(rtoCommit) == "" { - rtoCommit = cfg.rulesGoCommit - } - if (cfg.datadogFetch == "git" || cfg.rulesGoFetch == "git") && strings.TrimSpace(rtoCommit) == "" { - return "", errors.New("--rto-commit is required when --datadog-fetch or --rules-go-fetch is git") + if cfg.datadogFetch == "git" && strings.TrimSpace(rtoCommit) == "" { + return "", errors.New("--rto-commit is required when --datadog-fetch is git") } - if cfg.datadogFetch == "archive" || cfg.rulesGoFetch == "archive" { + if cfg.datadogFetch == "archive" { missing := []string{} if cfg.rtoArchiveURL == "" { missing = append(missing, "--rto-archive-url") @@ -1978,11 +1314,19 @@ func workspaceSnippet(cfg config) (string, error) { } var buf strings.Builder - buf.WriteString(`# Datadog Test Optimization Go/Orchestrion WORKSPACE wiring. + buf.WriteString(`# Datadog Test Optimization WORKSPACE wiring for Go tests instrumented by ddtest. load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") `) + buf.WriteString(fmt.Sprintf(`# Declare upstream rules_go %s or newer first, as its release notes show: +# http_archive( +# name = "%s", +# sha256 = "", +# urls = ["https://github.com/bazel-contrib/rules_go/releases/download/v%s/rules_go-v%s.zip"], +# ) + +`, defaultRulesGoVersion, cfg.rulesGoRepoName, defaultRulesGoVersion, defaultRulesGoVersion)) if cfg.datadogFetch == "git" { buf.WriteString(fmt.Sprintf(`git_repository( name = "datadog-rules-test-optimization", @@ -1990,7 +1334,7 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") remote = "%s", ) -`, rtoCommit, cfg.rulesGoRemote)) +`, rtoCommit, onboardingpins.DefaultRemote)) } else { buf.WriteString(fmt.Sprintf(`http_archive( name = "datadog-rules-test-optimization", @@ -2009,12 +1353,9 @@ datadog_go_test_optimization_workspace_repositories( rto_commit = "%s", rto_remote = "%s", datadog_fetch = "%s", - rules_go_fetch = "%s", rules_go_repo_name = "%s", - rules_go_upstream = "%s", - rules_go_variant = "%s", -`, rtoCommit, cfg.rulesGoRemote, cfg.datadogFetch, cfg.rulesGoFetch, cfg.rulesGoRepoName, effectiveRulesGoUpstream(cfg), cfg.rulesGoVariant)) - if cfg.datadogFetch == "archive" || cfg.rulesGoFetch == "archive" { +`, rtoCommit, onboardingpins.DefaultRemote, cfg.datadogFetch, cfg.rulesGoRepoName)) + if cfg.datadogFetch == "archive" { buf.WriteString(fmt.Sprintf(` rto_archive_url = "%s", rto_archive_sha256 = "%s", rto_archive_prefix = "%s", @@ -2028,16 +1369,15 @@ datadog_go_test_optimization_workspace_repositories( goVersion = strings.TrimSpace(cfg.runtimeVersion) } buf.WriteString(fmt.Sprintf(`load("@%s//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") -load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo") -dd_topt_go_orchestrion_tool_repo( - version = "%s", -%s -%s -) go_rules_dependencies() + go_register_toolchains(version = "%s") -`, cfg.rulesGoRepoName, cfg.orchestrionVersion, workspaceSnippetTracerConfig(cfg), workspaceSnippetOrchestrionGoSDKAttrs(cfg), goVersion)) + +load("@datadog-rules-test-optimization-go//:topt_go_ddtest_repository.bzl", "topt_go_ddtest_workspace_repositories") + +topt_go_ddtest_workspace_repositories(go_version = "%s") +`, cfg.rulesGoRepoName, goVersion, goVersion)) if cfg.workspaceMode || strings.TrimSpace(cfg.service) != "" || strings.TrimSpace(cfg.runtimeVersion) != "" { buf.WriteString("\n") buf.WriteString(workspaceSyncSnippet(cfg)) @@ -2063,56 +1403,6 @@ dd_topt_go_workspace_sync_repositories( `, cfg.syncRepoName, cfg.service, cfg.runtimeVersion, modulePathLine) } -func workspaceSnippetTracerConfig(cfg config) string { - if cfg.usesPerModuleTracerConfig() { - var buf strings.Builder - buf.WriteString(" dd_trace_go_versions = {\n") - for _, modulePath := range ddTraceGoModules { - buf.WriteString(fmt.Sprintf(" %q: %q,\n", modulePath, cfg.ddTraceGoVersions[modulePath])) - } - buf.WriteString(" },") - return buf.String() - } - return fmt.Sprintf(" dd_trace_go_version = %q,", cfg.ddTraceGoVersion) -} - -func workspaceSnippetOrchestrionGoSDKAttrs(cfg config) string { - version := strings.TrimSpace(cfg.runtimeVersion) - if version == "" { - return "" - } - return fmt.Sprintf(` go_sdk_root = "@go_sdk//:ROOT", - go_sdk_version = %q,`, version) -} - -func rulesGoStripPrefix(cfg config) (string, error) { - variant := cfg.rulesGoVariant - if variant == "" { - variant = defaultRulesGoVariant - } - return onboardingpins.RulesGoStripPrefix(effectiveRulesGoUpstream(cfg), variant) -} - -func effectiveRulesGoUpstream(cfg config) string { - if cfg.rulesGoUpstream == "" { - return "default" - } - return cfg.rulesGoUpstream -} - -func managedTracerConfigBlock(cfg config) string { - if cfg.usesPerModuleTracerConfig() { - var buf strings.Builder - buf.WriteString(" dd_trace_go_versions = {\n") - for _, modulePath := range ddTraceGoModules { - buf.WriteString(fmt.Sprintf(" %q: %q,\n", modulePath, cfg.ddTraceGoVersions[modulePath])) - } - buf.WriteString(" },") - return buf.String() - } - return fmt.Sprintf(" dd_trace_go_version = %q,", cfg.ddTraceGoVersion) -} - func managedGuidedModuleBlock(cfg config) string { modulePathLine := "" if cfg.goModulePath != "" { @@ -2196,59 +1486,6 @@ func removeManagedSectionIfPresent(content, startMarker, endMarker string) (stri } } -func inferDatadogRepoOverride(content string) (string, string) { - overridePattern := regexp.MustCompile(`(?s)git_override\(\s*module_name\s*=\s*"([^"]+)"(.*?)\n\)`) - remotePattern := regexp.MustCompile(`remote\s*=\s*"([^"]+)"`) - commitPattern := regexp.MustCompile(`commit\s*=\s*"([^"]+)"`) - - for _, match := range overridePattern.FindAllStringSubmatch(content, -1) { - moduleName := match[1] - if moduleName != "datadog-rules-test-optimization" && moduleName != "datadog-rules-test-optimization-go" { - continue - } - body := match[2] - remoteMatch := remotePattern.FindStringSubmatch(body) - commitMatch := commitPattern.FindStringSubmatch(body) - if len(remoteMatch) < 2 || len(commitMatch) < 2 { - continue - } - return remoteMatch[1], commitMatch[1] - } - return "", "" -} - -func inferManagedRulesGoOverride(content string) (string, string) { - // Rerunning bootstrap should preserve the published fork commit already - // written in the managed rules_go override. This avoids depending on an - // embedded commit that may not survive squash-merge publication. - start := strings.Index(content, managedBlockStart) - end := strings.Index(content, managedBlockEnd) - if start < 0 || end < 0 || end <= start { - return "", "" - } - return inferRulesGoOverride(content[start:end]) -} - -func inferRulesGoOverride(content string) (string, string) { - // Return the remote and commit from an existing rules_go override when the - // workspace already owns that wiring. - overridePattern := regexp.MustCompile(`(?s)git_override\(\s*module_name\s*=\s*"rules_go"(.*?)\n\)`) - remotePattern := regexp.MustCompile(`remote\s*=\s*"([^"]+)"`) - commitPattern := regexp.MustCompile(`commit\s*=\s*"([^"]+)"`) - - match := overridePattern.FindStringSubmatch(content) - if len(match) < 2 { - return "", "" - } - body := match[1] - remoteMatch := remotePattern.FindStringSubmatch(body) - commitMatch := commitPattern.FindStringSubmatch(body) - if len(remoteMatch) < 2 || len(commitMatch) < 2 { - return "", "" - } - return remoteMatch[1], commitMatch[1] -} - type goExtensionCall struct { name string service string @@ -2371,40 +1608,10 @@ func moduleUsesRepo(content, repoName string) bool { return false } -func rulesGoOverrideCompatible(content string, cfg config) (bool, error) { - if cfg.rulesGoArchive != nil { - return moduleArchiveCompatible(content, cfg) - } - overridePattern := regexp.MustCompile(`(?s)git_override\(\s*module_name\s*=\s*"rules_go"(.*?)\n\)`) - remotePattern := regexp.MustCompile(`remote\s*=\s*"([^"]+)"`) - commitPattern := regexp.MustCompile(`commit\s*=\s*"([^"]+)"`) - stripPrefixPattern := regexp.MustCompile(`strip_prefix\s*=\s*"([^"]+)"`) - - match := overridePattern.FindStringSubmatch(content) - if len(match) < 2 { - return false, nil - } - body := match[1] - remoteMatch := remotePattern.FindStringSubmatch(body) - commitMatch := commitPattern.FindStringSubmatch(body) - stripPrefixMatch := stripPrefixPattern.FindStringSubmatch(body) - if len(remoteMatch) < 2 || len(commitMatch) < 2 || len(stripPrefixMatch) < 2 { - return false, nil - } - stripPrefix, err := rulesGoStripPrefix(cfg) - if err != nil { - return false, err - } - return remoteMatch[1] == cfg.rulesGoRemote && commitMatch[1] == cfg.rulesGoCommit && stripPrefixMatch[1] == stripPrefix, nil -} - func ensureGuidedWorkspaceFiles(cfg config) error { if err := ensureGuidedRootBuild(cfg); err != nil { return err } - if err := ensureGuidedGoModuleBuild(cfg); err != nil { - return err - } if err := ensureGuidedWrapper(cfg); err != nil { return err } @@ -2469,67 +1676,12 @@ dd_test_optimization_targets( return err } - if sameCleanPath(goModuleDirOrWorkspace(cfg), cfg.workspaceDir) { - pinBlock := renderPinExportsBlock() - text, err = replaceManagedSection(text, pinExportsBlockStart, pinExportsBlockEnd, pinBlock) - if err != nil { - return err - } - } else { - text, err = removeManagedSectionIfPresent(text, pinExportsBlockStart, pinExportsBlockEnd) - if err != nil { - return err - } - } - if err := os.WriteFile(buildPath, []byte(text), 0o644); err != nil { return fmt.Errorf("write %s: %w", buildPath, err) } return nil } -// ensureGuidedGoModuleBuild ensures the configured Go module package exports -// Orchestrion pin files. -func ensureGuidedGoModuleBuild(cfg config) error { - if _, err := goModuleBazelPackage(cfg); err != nil { - return err - } - moduleDir := goModuleDirOrWorkspace(cfg) - buildPath, err := selectPackageFile(moduleDir) - if err != nil { - return err - } - content, err := os.ReadFile(buildPath) - if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("read %s: %w", buildPath, err) - } - text := string(content) - - pinBlock := renderPinExportsBlock() - text, err = replaceManagedSection(text, pinExportsBlockStart, pinExportsBlockEnd, pinBlock) - if err != nil { - return err - } - - if err := os.WriteFile(buildPath, []byte(text), 0o644); err != nil { - return fmt.Errorf("write %s: %w", buildPath, err) - } - return nil -} - -func renderPinExportsBlock() string { - var buf bytes.Buffer - buf.WriteString(pinExportsBlockStart) - buf.WriteString("\nexports_files([\n") - for _, file := range orchestrionPinFiles { - fmt.Fprintf(&buf, " %q,\n", file) - } - buf.WriteString("])\n") - buf.WriteString(pinExportsBlockEnd) - buf.WriteString("\n") - return buf.String() -} - // renderExpectedTargetsAttr renders the optional doctor target allowlist. An // empty list leaves discovery global so simple repositories do not need to // maintain duplicate target lists. @@ -2548,11 +1700,6 @@ func renderExpectedTargetsAttr(targets []string) string { } func ensureGuidedWrapper(cfg config) error { - pinLabels, err := orchestrionPinFileLabels(cfg) - if err != nil { - return err - } - wrapperDir := filepath.Join(cfg.workspaceDir, toolsBuildDir) if err := os.MkdirAll(wrapperDir, 0o755); err != nil { return fmt.Errorf("create %s: %w", wrapperDir, err) @@ -2584,20 +1731,14 @@ func ensureGuidedWrapper(cfg config) error { load("@datadog-rules-test-optimization-go//:topt_go_test.bzl", "dd_topt_go_test") load("@%s//:export.bzl", "topt_data") -_ORCHESTRION_PIN_FILES = [ -%s -] - def dd_go_test(name, **kwargs): dd_topt_go_test( name = name, topt_data = topt_data, - orchestrion_mode = "test_optimization", - orchestrion_pin_files = _ORCHESTRION_PIN_FILES, **kwargs ) %s -`, wrapperBlockStart, cfg.syncRepoName, renderPinLabelLines(pinLabels), wrapperBlockEnd) +`, wrapperBlockStart, cfg.syncRepoName, wrapperBlockEnd) if err := os.WriteFile(wrapperPath, []byte(wrapperContent), 0o644); err != nil { return fmt.Errorf("write %s: %w", wrapperPath, err) } @@ -2624,10 +1765,6 @@ func ensureWorkspaceWrapperTemplate(cfg config) error { return errors.New("--optimized-wrapper-name must be non-empty") } - pinLabels, err := orchestrionPinFileLabels(cfg) - if err != nil { - return err - } wrapperDir, err := resolveWorkspaceRelativeDir(cfg.workspaceDir, cfg.wrapperPackage, "--wrapper-package") if err != nil { return err @@ -2656,7 +1793,7 @@ func ensureWorkspaceWrapperTemplate(cfg config) error { return fmt.Errorf("%s already exists and is not Datadog-managed; rerun with --force or choose a different --wrapper-file", wrapperPath) } - wrapperContent := workspaceWrapperTemplate(cfg, pinLabels) + wrapperContent := workspaceWrapperTemplate(cfg) if err := os.WriteFile(wrapperPath, []byte(wrapperContent), 0o644); err != nil { return fmt.Errorf("write %s: %w", wrapperPath, err) } @@ -2666,7 +1803,7 @@ func ensureWorkspaceWrapperTemplate(cfg config) error { // workspaceWrapperTemplate renders one repo-local public wrapper. Consumers keep // scheduling, tags, flaky handling, and platform policy inside // _apply_repo_go_test_policy instead of editing the public Datadog macro. -func workspaceWrapperTemplate(cfg config, pinLabels []string) string { +func workspaceWrapperTemplate(cfg config) string { compatibilityAlias := "" if cfg.optimizedWrapperName != cfg.plainWrapperName { compatibilityAlias = fmt.Sprintf(` @@ -2679,18 +1816,14 @@ func workspaceWrapperTemplate(cfg config, pinLabels []string) string { Keep repository-specific scheduling, tags, Docker, flaky, and platform policy inside _apply_repo_go_test_policy. Every test uses the same public wrapper; ---config=test-optimization selects enabled metadata and Orchestrion behavior, -while omitting the config preserves the normal go_test behavior. +--config=test-optimization selects enabled metadata and the ddtest +instrumentation, while omitting the config preserves the normal go_test behavior. """ %s load("@datadog-rules-test-optimization-go//:topt_go_test.bzl", _raw_dd_topt_go_test = "dd_topt_go_test") load("@%s//:export.bzl", "topt_data") -_ORCHESTRION_PIN_FILES = [ -%s -] - def _apply_repo_go_test_policy(go_test_macro, name, **kwargs): """Apply repository-local go_test policy before calling the selected macro.""" go_test_macro(name = name, **kwargs) @@ -2699,19 +1832,15 @@ def %s(name, **kwargs): """Run a config-gated go_test with repository-local policy.""" if "topt_data" in kwargs: fail("%s injects topt_data from @%s; remove the explicit topt_data attr") - if "orchestrion_pin_files" in kwargs: - fail("%s injects orchestrion_pin_files from the bootstrap-managed pin list") _apply_repo_go_test_policy( _raw_dd_topt_go_test, name = name, topt_data = topt_data, - orchestrion_mode = "test_optimization", - orchestrion_pin_files = _ORCHESTRION_PIN_FILES, **kwargs ) %s %s -`, wrapperBlockStart, cfg.syncRepoName, strings.TrimRight(renderPinLabelLines(pinLabels), "\n"), cfg.plainWrapperName, cfg.plainWrapperName, cfg.syncRepoName, cfg.plainWrapperName, strings.TrimRight(compatibilityAlias, "\n"), wrapperBlockEnd) +`, wrapperBlockStart, cfg.syncRepoName, cfg.plainWrapperName, cfg.plainWrapperName, cfg.syncRepoName, strings.TrimRight(compatibilityAlias, "\n"), wrapperBlockEnd) } // resolveWorkspaceRelativeDir resolves a user-selected directory and rejects @@ -2727,34 +1856,6 @@ func resolveWorkspaceRelativeDir(workspaceDir, value, flagName string) (string, return filepath.Join(workspaceDir, cleaned), nil } -// renderPinLabelLines renders Starlark list entries for Orchestrion pin-file -// labels. -func renderPinLabelLines(labels []string) string { - var buf bytes.Buffer - for _, label := range labels { - fmt.Fprintf(&buf, " %q,\n", label) - } - return buf.String() -} - -// orchestrionPinFileLabels returns Bazel labels for pin files in the -// configured Go module package. -func orchestrionPinFileLabels(cfg config) ([]string, error) { - pkg, err := goModuleBazelPackage(cfg) - if err != nil { - return nil, err - } - labels := make([]string, 0, len(orchestrionPinFiles)) - for _, file := range orchestrionPinFiles { - if pkg == "//" { - labels = append(labels, "//:"+file) - } else { - labels = append(labels, pkg+":"+file) - } - } - return labels, nil -} - // goModuleBazelPackage returns the Bazel package label for the configured Go // module directory. func goModuleBazelPackage(cfg config) (string, error) { @@ -2782,12 +1883,6 @@ func goModuleDirOrWorkspace(cfg config) string { return cfg.workspaceDir } -// sameCleanPath returns whether two local paths are equal after filepath -// cleanup. -func sameCleanPath(left, right string) bool { - return filepath.Clean(left) == filepath.Clean(right) -} - func selectPackageFile(dir string) (string, error) { buildBazel := filepath.Join(dir, "BUILD.bazel") if _, err := os.Stat(buildBazel); err == nil { @@ -2957,187 +2052,6 @@ func maskStarlarkStringsAndComments(content string) string { return string(masked) } -type tracerConfig struct { - shared string - perModule map[string]string -} - -func (c tracerConfig) isSet() bool { - return strings.TrimSpace(c.shared) != "" || len(c.perModule) > 0 -} - -func (cfg config) hasTracerConfig() bool { - return strings.TrimSpace(cfg.ddTraceGoVersion) != "" || len(cfg.ddTraceGoVersions) > 0 -} - -func (cfg config) usesPerModuleTracerConfig() bool { - return len(cfg.ddTraceGoVersions) > 0 -} - -func (cfg config) effectiveDDTraceGoVersions() map[string]string { - if cfg.usesPerModuleTracerConfig() { - return copyDDTraceGoVersions(cfg.ddTraceGoVersions) - } - versions := make(map[string]string, len(ddTraceGoModules)) - for _, modulePath := range ddTraceGoModules { - versions[modulePath] = cfg.ddTraceGoVersion - } - return versions -} - -func copyDDTraceGoVersions(src map[string]string) map[string]string { - if len(src) == 0 { - return nil - } - dst := make(map[string]string, len(src)) - for key, value := range src { - dst[key] = value - } - return dst -} - -func sharedDDTraceGoVersion(versions map[string]string) (string, bool) { - if len(versions) == 0 { - return "", false - } - shared := strings.TrimSpace(versions[ddTraceGoModules[0]]) - if shared == "" { - return "", false - } - for _, modulePath := range ddTraceGoModules[1:] { - if strings.TrimSpace(versions[modulePath]) != shared { - return "", false - } - } - return shared, true -} - -func managedTracerConfig(content string) (tracerConfig, error) { - start := strings.Index(content, managedBlockStart) - end := strings.Index(content, managedBlockEnd) - if start < 0 || end < 0 || end <= start { - return tracerConfig{}, nil - } - return parseTracerConfigFromContent(content[start:end]) -} - -func ensureBootstrapCanManageTracerConfig(content string) error { - stripped, _ := stripManagedSectionIfPresent(content, managedBlockStart, managedBlockEnd) - configs, err := parseTracerConfigsFromContent(stripped) - if err != nil { - return err - } - if len(configs) == 0 { - return nil - } - return errors.New("manual tracer configuration is already active outside the Datadog-managed block; remove or migrate that orchestrion.from_source(...) tracer config before rerunning bootstrap") -} - -func hydrateManagedTracerConfig(cfg *config, content string) error { - managed, err := managedTracerConfig(content) - if err != nil { - return err - } - if !managed.isSet() { - return nil - } - cfg.ddTraceGoVersion = managed.shared - cfg.ddTraceGoVersions = copyDDTraceGoVersions(managed.perModule) - return nil -} - -func hydrateManagedRulesGoVariant(cfg *config, content string) error { - // Rerunning the bootstrap should preserve the variant the managed block - // already selected so workspace snippets remain stable across reruns. - start := strings.Index(content, managedBlockStart) - end := strings.Index(content, managedBlockEnd) - if start < 0 || end < 0 || end <= start { - return nil - } - prefix := literalAttribute(content[start:end], "strip_prefix") - if prefix == "" { - // Computed archive values are validated or replaced by explicit pins - // in configureModuleArchive. Do not infer a fork from part of a value. - return nil - } - if strings.Contains(content[start:end], "archive_override(") { - if index := strings.Index(prefix, "third_party/rgo/"); index > 0 { - prefix = prefix[index:] - } - } - upstream, variant, err := onboardingpins.RulesGoSelectionForStripPrefix(prefix) - if err != nil { - return err - } - cfg.rulesGoUpstream = upstream - cfg.rulesGoVariant = variant - return nil -} - -func parseTracerConfigsFromContent(content string) ([]tracerConfig, error) { - aliases := map[string]struct{}{} - for _, block := range topLevelCallBlocks(content) { - if block.function != "use_extension" || block.assign == "" { - continue - } - if strings.Contains(block.text, `"@rules_go//go:extensions.bzl"`) && strings.Contains(block.text, `"orchestrion"`) { - aliases[block.assign] = struct{}{} - } - } - configs := []tracerConfig{} - for _, block := range topLevelCallBlocks(content) { - parts := strings.Split(block.function, ".") - if len(parts) != 2 || parts[1] != "from_source" { - continue - } - if _, ok := aliases[parts[0]]; !ok { - continue - } - cfg, err := parseTracerConfigFromCall(block.text) - if err != nil { - return nil, err - } - if cfg.isSet() { - configs = append(configs, cfg) - } - } - return configs, nil -} - -func parseTracerConfigFromContent(content string) (tracerConfig, error) { - configs, err := parseTracerConfigsFromContent(content) - if err != nil { - return tracerConfig{}, err - } - if len(configs) == 0 { - return tracerConfig{}, nil - } - if len(configs) > 1 { - return tracerConfig{}, errors.New("multiple active orchestrion.from_source(...) tracer configs found") - } - return configs[0], nil -} - -func parseTracerConfigFromCall(call string) (tracerConfig, error) { - sharedMatch := regexp.MustCompile(`(?m)^\s*dd_trace_go_version\s*=\s*"([^"]+)"`).FindStringSubmatch(call) - perModuleMatch := regexp.MustCompile(`(?s)dd_trace_go_versions\s*=\s*\{(.*?)\}`).FindStringSubmatch(call) - if len(sharedMatch) > 1 && len(perModuleMatch) > 1 { - return tracerConfig{}, errors.New("dd_trace_go_version and dd_trace_go_versions cannot both be set in the same orchestrion.from_source(...) call") - } - if len(sharedMatch) > 1 { - return tracerConfig{shared: sharedMatch[1]}, nil - } - if len(perModuleMatch) > 1 { - perModule := map[string]string{} - entryPattern := regexp.MustCompile(`"([^"]+)"\s*:\s*"([^"]+)"`) - for _, match := range entryPattern.FindAllStringSubmatch(perModuleMatch[1], -1) { - perModule[match[1]] = match[2] - } - return tracerConfig{perModule: perModule}, nil - } - return tracerConfig{}, nil -} - func hasNamedTarget(content, targetName string) bool { for _, block := range topLevelCallBlocks(content) { if block.function == "load" { @@ -3182,467 +2096,50 @@ func extractBlockName(block string) string { return match[1] } -// writeOrchestrionToolFile writes the managed tools-tagged Orchestrion entrypoint -// that the bootstrap flow keeps aligned with the Bazel-side Orchestrion wiring. -// The helper writes this file directly instead of depending on `orchestrion pin` -// so bootstrap stays deterministic even when upstream pin behavior changes. -func writeOrchestrionToolFile(cfg config) error { - path := filepath.Join(cfg.goModuleDir, "orchestrion.tool.go") - if err := os.WriteFile(path, []byte(managedOrchestrionToolFileSource()), 0o644); err != nil { - return fmt.Errorf("write orchestrion.tool.go: %w", err) - } - return nil -} - -// writeWorkspaceOrchestrionToolFile protects WORKSPACE users from -// accidentally replacing a hand-maintained tools file. Existing managed content -// is idempotent; custom content requires --force. -func writeWorkspaceOrchestrionToolFile(cfg config) error { - path := filepath.Join(cfg.goModuleDir, "orchestrion.tool.go") - existing, err := os.ReadFile(path) - if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("read orchestrion.tool.go: %w", err) - } - managed := managedOrchestrionToolFileSource() - if len(existing) > 0 && string(existing) != managed && !cfg.force { - return fmt.Errorf("%s already exists and is not bootstrap-managed; rerun with --force to replace it", path) - } - return writeOrchestrionToolFile(cfg) -} - -// managedOrchestrionToolFileSource returns the canonical bootstrap-owned -// Orchestrion tools file. The import set matches the Orchestrion module-proxy -// seed so bootstrap and Bazel action-time module resolution stay aligned. -func managedOrchestrionToolFileSource() string { - return `//go:build tools - -package tools - -import ( - _ "github.com/DataDog/orchestrion" // integration - _ "github.com/DataDog/dd-trace-go/contrib/log/slog/v2" // integration - _ "github.com/DataDog/dd-trace-go/contrib/net/http/v2" // integration - _ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration -) -` -} - -func syncDDTraceGoVersion(cfg config) error { - goBinary := effectiveGoBinary(cfg) - mode := cfg.goModSync - if strings.TrimSpace(mode) == "" { - mode = defaultGoModSync - } - for _, args := range bootstrapSyncCommands(cfg) { - // no-dd-sa: --go-binary is validated as a single Go executable path, - // and exec.Command invokes it directly without a shell. - cmd := exec.Command(goBinary, args...) - cmd.Dir = cfg.goModuleDir - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Env = orchestrionBootstrapEnv() - if err := cmd.Run(); err != nil { - if mode == "targeted" && len(args) >= 3 && args[0] == "list" && args[1] == "-mod=readonly" { - return fmt.Errorf("run `%s %s` in %s: %w\nOrchestrion could not resolve its tool module graph with readonly Go modules after targeted sync. Re-run bootstrap with --go-mod-sync=targeted and refresh any checked-in go_repository declarations if this workspace uses WORKSPACE mode", goBinary, strings.Join(args, " "), cfg.goModuleDir, err) - } - return fmt.Errorf("run `%s %s` in %s: %w", goBinary, strings.Join(args, " "), cfg.goModuleDir, err) - } - } - return nil -} - -// effectiveGoBinary returns the command used for bootstrap Go module work. -func effectiveGoBinary(cfg config) string { - if strings.TrimSpace(cfg.goBinary) != "" { - return cfg.goBinary - } - return defaultGoBinary -} - -// bootstrapSyncCommands returns the exact go command sequence bootstrap uses to -// pin the workspace module graph. Targeted mode avoids `go mod tidy` so large -// monorepos do not rewrite unrelated module state while making the Orchestrion -// tool graph complete for readonly Bazel actions. -func bootstrapSyncCommands(cfg config) [][]string { - commands := make([][]string, 0, len(ddTraceGoModules)+3) - versions := cfg.effectiveDDTraceGoVersions() - mode := cfg.goModSync - if strings.TrimSpace(mode) == "" { - mode = defaultGoModSync - } - - if mode == "off" { - return nil - } - - commands = append(commands, []string{"mod", "edit", "-require=github.com/DataDog/orchestrion@" + cfg.orchestrionVersion}) - for _, modulePath := range ddTraceGoModules { - commands = append(commands, []string{"mod", "edit", "-require=" + modulePath + "@" + versions[modulePath]}) - } - - switch mode { - case "targeted": - // Seed every selected module version explicitly before loading package - // roots. On a cold cache, go list may otherwise query @latest even - // though go.mod already contains the intended requirements. - commands = append(commands, []string{"mod", "download", "github.com/DataDog/orchestrion@" + cfg.orchestrionVersion}) - for _, modulePath := range ddTraceGoModules { - commands = append(commands, []string{"mod", "download", modulePath + "@" + versions[modulePath]}) - } - // Resolve package roots one at a time. A single go list spanning several - // module roots can ignore freshly edited requirements on a cold module - // cache and fall back to @latest for some roots. - for _, packagePath := range orchestrionToolPackages { - commands = append(commands, []string{"list", "-mod=mod", "-tags=tools", packagePath}) - } - for _, packagePath := range orchestrionToolPackages { - commands = append(commands, []string{"list", "-mod=readonly", "-tags=tools", packagePath}) - } - case "tidy": - commands = append(commands, - []string{"get", "github.com/DataDog/dd-trace-go/v2/orchestrion@" + versions["github.com/DataDog/dd-trace-go/v2"]}, - []string{"mod", "tidy"}, - ) - } - - return commands -} - -func normalizeDDTraceGoVersion(cfg *config) error { - resolvedVersions, err := resolveDDTraceGoVersionQuery(cfg.ddTraceGoVersion, effectiveGoBinary(*cfg), orchestrionBootstrapEnv()) - if err != nil { - return err - } - if shared, ok := sharedDDTraceGoVersion(resolvedVersions); ok { - cfg.ddTraceGoVersion = shared - cfg.ddTraceGoVersions = nil - return nil - } - cfg.ddTraceGoVersion = "" - cfg.ddTraceGoVersions = copyDDTraceGoVersions(resolvedVersions) - return nil -} - -func ensureCIVisibilityOrchestrionImport(cfg config) error { - path := filepath.Join(cfg.goModuleDir, "orchestrion.tool.go") - content, err := os.ReadFile(path) - if err != nil { - return fmt.Errorf("read orchestrion.tool.go: %w", err) - } - text := string(content) - - const ( - legacyCIVisibilityImport = `_ "gopkg.in/DataDog/dd-trace-go.v1/civisibility" // integration` - legacyCIVisibilityImportBare = `_ "gopkg.in/DataDog/dd-trace-go.v1/civisibility"` - v2OrchestrionImport = `_ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration` - v2OrchestrionImportBare = `_ "github.com/DataDog/dd-trace-go/v2/orchestrion"` - v2NetHTTPImport = `_ "github.com/DataDog/dd-trace-go/contrib/net/http/v2" // integration` - v2NetHTTPImportBare = `_ "github.com/DataDog/dd-trace-go/contrib/net/http/v2"` - v2SlogImport = `_ "github.com/DataDog/dd-trace-go/contrib/log/slog/v2" // integration` - v2SlogImportBare = `_ "github.com/DataDog/dd-trace-go/contrib/log/slog/v2"` - v2AllImport = `_ "github.com/DataDog/dd-trace-go/orchestrion/all/v2" // integration` - v2AllImportBare = `_ "github.com/DataDog/dd-trace-go/orchestrion/all/v2"` - v2GotestingImport = `_ "github.com/DataDog/dd-trace-go/v2/internal/civisibility/integrations/gotesting" // integration` - v2GotestingImportBare = `_ "github.com/DataDog/dd-trace-go/v2/internal/civisibility/integrations/gotesting"` - ) - - updated := strings.ReplaceAll(text, legacyCIVisibilityImport+"\n", "") - updated = strings.ReplaceAll(updated, legacyCIVisibilityImport, "") - updated = strings.ReplaceAll(updated, legacyCIVisibilityImportBare+"\n", "") - updated = strings.ReplaceAll(updated, legacyCIVisibilityImportBare, "") - updated = strings.ReplaceAll(updated, v2OrchestrionImport+"\n", "") - updated = strings.ReplaceAll(updated, v2OrchestrionImport, "") - updated = strings.ReplaceAll(updated, v2OrchestrionImportBare+"\n", "") - updated = strings.ReplaceAll(updated, v2OrchestrionImportBare, "") - updated = strings.ReplaceAll(updated, v2NetHTTPImport+"\n", "") - updated = strings.ReplaceAll(updated, v2NetHTTPImport, "") - updated = strings.ReplaceAll(updated, v2NetHTTPImportBare+"\n", "") - updated = strings.ReplaceAll(updated, v2NetHTTPImportBare, "") - updated = strings.ReplaceAll(updated, v2SlogImport+"\n", "") - updated = strings.ReplaceAll(updated, v2SlogImport, "") - updated = strings.ReplaceAll(updated, v2SlogImportBare+"\n", "") - updated = strings.ReplaceAll(updated, v2SlogImportBare, "") - updated = strings.ReplaceAll(updated, v2AllImport+"\n", "") - updated = strings.ReplaceAll(updated, v2AllImport, "") - updated = strings.ReplaceAll(updated, v2AllImportBare+"\n", "") - updated = strings.ReplaceAll(updated, v2AllImportBare, "") - updated = strings.ReplaceAll(updated, v2GotestingImport+"\n", "") - updated = strings.ReplaceAll(updated, v2GotestingImport, "") - updated = strings.ReplaceAll(updated, v2GotestingImportBare+"\n", "") - updated = strings.ReplaceAll(updated, v2GotestingImportBare, "") - updated = strings.ReplaceAll(updated, "\n\n\n", "\n\n") - - requiredImports := []string{ - v2OrchestrionImport, - v2NetHTTPImport, - v2SlogImport, - } - - baseOrchestrionPattern := `(?m)^(\s*_\s*"github\.com/DataDog/orchestrion"(?:\s*//.*)?\s*)$` - if re := regexp.MustCompile(baseOrchestrionPattern); re.MatchString(updated) { - updated = re.ReplaceAllString(updated, `${1}`+"\n\t"+strings.Join(requiredImports, "\n\t")) - } else if strings.Contains(updated, ")\n") { - updated = strings.Replace(updated, ")\n", "\t"+strings.Join(requiredImports, "\n\t")+"\n)\n", 1) - } else { - return fmt.Errorf("patch orchestrion.tool.go: could not locate import block") - } - - if err := os.WriteFile(path, []byte(updated), 0o644); err != nil { - return fmt.Errorf("write orchestrion.tool.go: %w", err) - } - return nil -} - -func warmOrchestrionModuleCache(cfg config) error { - goBinary := effectiveGoBinary(cfg) - commands := make([][]string, 0, len(ddTraceGoModules)+len(ddTraceGoWarmPackages)) - versions := cfg.effectiveDDTraceGoVersions() - for _, modulePath := range ddTraceGoModules { - commands = append(commands, []string{"mod", "download", modulePath + "@" + versions[modulePath]}) - } - for _, packagePath := range ddTraceGoWarmPackages { - commands = append(commands, []string{"list", "-mod=mod", packagePath}) - } - - for _, args := range commands { - // no-dd-sa: --go-binary is validated as a single Go executable path, - // and exec.Command invokes it directly without a shell. - cmd := exec.Command(goBinary, args...) - cmd.Dir = cfg.goModuleDir - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Env = orchestrionBootstrapEnv() - if err := cmd.Run(); err != nil { - return fmt.Errorf("run `%s %s` in %s: %w", goBinary, strings.Join(args, " "), cfg.goModuleDir, err) - } - } - - return nil -} - -type goListModule struct { - Version string `json:"Version"` - Replace *struct { - Version string `json:"Version"` - Path string `json:"Path"` - } `json:"Replace"` -} - -func verifyResolvedBootstrapModuleVersions(cfg config) error { - goBinary := effectiveGoBinary(cfg) - orchestrionVersion, err := resolvedModuleVersion(goBinary, cfg.goModuleDir, orchestrionBootstrapEnv(), "github.com/DataDog/orchestrion") - if err != nil { - return err - } - if orchestrionVersion != cfg.orchestrionVersion { - return fmt.Errorf("resolved orchestrion version mismatch in %s: configured %s, resolved %s", cfg.goModuleDir, cfg.orchestrionVersion, orchestrionVersion) - } - - expected := cfg.effectiveDDTraceGoVersions() - for _, modulePath := range ddTraceGoModules { - resolved, err := resolvedModuleVersion(goBinary, cfg.goModuleDir, orchestrionBootstrapEnv(), modulePath) - if err != nil { - return err - } - if resolved != expected[modulePath] { - return fmt.Errorf("resolved dd-trace-go version mismatch in %s for %s: configured %s, resolved %s", cfg.goModuleDir, modulePath, expected[modulePath], resolved) - } - } - return nil -} - -func resolveDDTraceGoVersionQuery(query, goExe string, env []string) (map[string]string, error) { - query = strings.TrimSpace(query) - if query == "" { - return nil, errors.New("dd-trace-go version query cannot be empty") - } - - neutralModuleDir, err := os.MkdirTemp("", "dd-trace-go-version-check-") - if err != nil { - return nil, fmt.Errorf("create temporary module for dd-trace-go version resolution: %w", err) - } - defer os.RemoveAll(neutralModuleDir) - - if err := os.WriteFile(filepath.Join(neutralModuleDir, "go.mod"), []byte("module ddtraceversioncheck\n\ngo 1.21\n"), 0o644); err != nil { - return nil, fmt.Errorf("write temporary go.mod for dd-trace-go version resolution: %w", err) - } - - resolvedVersions := make(map[string]string, len(ddTraceGoModules)) - for _, modulePath := range ddTraceGoModules { - resolvedVersion, err := resolveModuleVersionFromQuery(goExe, neutralModuleDir, env, modulePath, query) - if err != nil { - return nil, err - } - resolvedVersions[modulePath] = resolvedVersion - } - - if err := os.WriteFile(filepath.Join(neutralModuleDir, "go.mod"), []byte(syntheticDDTraceGoVersionCheckMod(resolvedVersions)), 0o644); err != nil { - return nil, fmt.Errorf("write temporary go.mod for dd-trace-go package validation: %w", err) - } - for _, packagePath := range ddTraceGoValidationPackages { - if err := verifyPackageAvailable(goExe, neutralModuleDir, env, packagePath); err != nil { - return nil, fmt.Errorf("dd-trace-go query %q package validation failed for %s: %w", query, packagePath, err) - } - } - - return resolvedVersions, nil -} - -func resolveModuleVersionFromQuery(goExe, moduleDir string, env []string, modulePath, query string) (string, error) { - // no-dd-sa: callers pass the validated Go executable; modulePath/query are - // separate argv entries and are never evaluated by a shell. - cmd := exec.Command(goExe, "list", "-m", "-json", modulePath+"@"+query) - cmd.Dir = moduleDir - cmd.Env = normalizedGoEnv(env) - output, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("resolve dd-trace-go query %q for %s: %w: %s", query, modulePath, err, strings.TrimSpace(string(output))) - } - var module goListModule - if err := json.Unmarshal(output, &module); err != nil { - return "", fmt.Errorf("parse resolved dd-trace-go query %q for %s: %w", query, modulePath, err) - } - if strings.TrimSpace(module.Version) == "" { - return "", fmt.Errorf("resolve dd-trace-go query %q for %s: empty resolved version", query, modulePath) +// literalAttribute returns a call argument whose value is one string literal, +// or "" for a computed value. Never guess the result of a Starlark expression. +func literalAttribute(block, name string) string { + value, present := callAttributeValue(block, name) + if !present || !regexp.MustCompile(`^("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')$`).MatchString(value) { + return "" } - return strings.TrimSpace(module.Version), nil + literal, _ := unquoteStarlarkLiteral(value) + return literal } -func resolvedModuleVersion(goExe, moduleDir string, env []string, modulePath string) (string, error) { - // no-dd-sa: callers pass the validated Go executable; modulePath is a - // separate argv entry and is never evaluated by a shell. - cmd := exec.Command(goExe, "list", "-mod=mod", "-m", "-json", modulePath) - cmd.Dir = moduleDir - cmd.Env = normalizedGoEnv(env) - output, err := cmd.Output() - if err != nil { - return "", fmt.Errorf("resolve %s version in %s with %s: %w", modulePath, moduleDir, goExe, err) - } - var module goListModule - if err := json.Unmarshal(output, &module); err != nil { - return "", fmt.Errorf("parse %s version in %s: %w", modulePath, moduleDir, err) +// Read one complete outer call argument, never a nested keyword or a fragment +// of a computed expression. The masked view preserves original byte offsets. +func callAttributeValue(block, name string) (string, bool) { + syntax := maskStarlarkStringsAndComments(block) + opening := strings.IndexByte(syntax, '(') + if opening < 0 { + return "", false } - if module.Replace != nil { - if strings.TrimSpace(module.Replace.Version) == "" { - return "", fmt.Errorf("resolve %s version in %s: local replace targets are not supported for bootstrap verification", modulePath, moduleDir) + attribute := regexp.MustCompile(`^\s*` + regexp.QuoteMeta(name) + `\s*=`) + depth, start := 1, opening+1 + for i := start; i < len(syntax); i++ { + switch syntax[i] { + case '(', '[', '{': + depth++ + case ')', ']', '}': + depth-- } - return module.Replace.Version, nil - } - if strings.TrimSpace(module.Version) == "" { - return "", fmt.Errorf("resolve %s version in %s: empty module version", modulePath, moduleDir) - } - return module.Version, nil -} - -func verifyPackageAvailable(goExe, moduleDir string, env []string, packagePath string) error { - // no-dd-sa: callers pass the validated Go executable; packagePath is a - // separate argv entry and is never evaluated by a shell. - cmd := exec.Command(goExe, "list", "-mod=mod", packagePath) - cmd.Dir = moduleDir - cmd.Env = normalizedGoEnv(env) - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("resolve %s in %s: %w: %s", packagePath, moduleDir, err, strings.TrimSpace(string(output))) - } - return nil -} - -func syntheticDDTraceGoVersionCheckMod(versions map[string]string) string { - var buf strings.Builder - buf.WriteString("module ddtraceversioncheck\n\n") - buf.WriteString("go 1.21\n\n") - buf.WriteString("require (\n") - for _, modulePath := range ddTraceGoModules { - buf.WriteString("\t") - buf.WriteString(modulePath) - buf.WriteString(" ") - buf.WriteString(versions[modulePath]) - buf.WriteString("\n") - } - buf.WriteString(")\n") - return buf.String() -} - -func normalizedGoEnv(env []string) []string { - normalized := append([]string{}, env...) - normalized = setEnvValue(normalized, "GO111MODULE", "on") - normalized = setEnvValue(normalized, "GOWORK", "off") - if strings.TrimSpace(envValue(normalized, "GOPROXY")) == "" { - normalized = setEnvValue(normalized, "GOPROXY", "https://proxy.golang.org,direct") - } - if strings.TrimSpace(envValue(normalized, "GOSUMDB")) == "" { - normalized = setEnvValue(normalized, "GOSUMDB", "sum.golang.org") - } - return normalized -} - -func setEnvValue(env []string, key, value string) []string { - prefix := key + "=" - normalized := make([]string, 0, len(env)+1) - replaced := false - for _, entry := range env { - if strings.HasPrefix(entry, prefix) { - if !replaced { - normalized = append(normalized, prefix+value) - replaced = true + if (depth == 1 && syntax[i] == ',') || depth == 0 { + if match := attribute.FindStringIndex(syntax[start:i]); match != nil { + return strings.TrimSpace(stripStarlarkLineComments(block[start+match[1] : i])), true + } + start = i + 1 + if depth == 0 { + break } - continue - } - normalized = append(normalized, entry) - } - if !replaced { - normalized = append(normalized, prefix+value) - } - return normalized -} - -func envValue(env []string, key string) string { - prefix := key + "=" - for _, entry := range env { - if strings.HasPrefix(entry, prefix) { - return strings.TrimPrefix(entry, prefix) } } - return "" -} - -func orchestrionBootstrapEnv() []string { - env := append([]string{}, os.Environ()...) - cacheRoot := filepath.Join(os.TempDir(), sharedOrchestrionCacheDirName) - goModCache := filepath.Join(cacheRoot, "pkg", "mod") - goBuildCache := filepath.Join(cacheRoot, "cache") - - env = setEnvValue(env, "GO111MODULE", "on") - env = setEnvValue(env, "GOWORK", "off") - env = setEnvValue(env, "GOPATH", cacheRoot) - env = setEnvValue(env, "GOMODCACHE", goModCache) - env = setEnvValue(env, "GOCACHE", goBuildCache) - env = setEnvValue(env, "GOPROXY", "https://proxy.golang.org,direct") - env = setEnvValue(env, "GOSUMDB", "sum.golang.org") - // The Orchestrion and dd-trace-go pins are public modules. Do not let a - // developer's broad GOPRIVATE/GONOSUMDB settings bypass the public proxy - // and leave the isolated module cache with incomplete package metadata. - env = setEnvValue(env, "GOPRIVATE", "") - env = setEnvValue(env, "GONOSUMDB", "") - return normalizedGoEnv(env) -} - -func writeStarterOrchestrionYML(cfg config) error { - path := filepath.Join(cfg.goModuleDir, "orchestrion.yml") - if _, err := os.Stat(path); err == nil && !cfg.force { - return nil - } else if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("stat orchestrion.yml: %w", err) - } - if err := os.WriteFile(path, []byte(defaultStarterOrchestrionYML), 0o644); err != nil { - return fmt.Errorf("write orchestrion.yml: %w", err) - } - return nil + return "", false } -func maybeChangedStarterYML(goModuleDir string) string { - if _, err := os.Stat(filepath.Join(goModuleDir, "orchestrion.yml")); err == nil { - return ", orchestrion.yml" +func unquoteStarlarkLiteral(value string) (string, error) { + if strings.HasPrefix(value, "'") { + value = `"` + strings.ReplaceAll(strings.ReplaceAll(value[1:len(value)-1], `\'`, `'`), `"`, `\"`) + `"` } - return "" + return strconv.Unquote(value) } diff --git a/modules/go/tools/dd_topt_go_bootstrap/main_test.go b/modules/go/tools/dd_topt_go_bootstrap/main_test.go index 32b38885..7d61f037 100644 --- a/modules/go/tools/dd_topt_go_bootstrap/main_test.go +++ b/modules/go/tools/dd_topt_go_bootstrap/main_test.go @@ -9,13 +9,11 @@ package main import ( "bytes" "context" - "fmt" "io" "os" "os/exec" "path/filepath" "regexp" - "runtime" "strings" "testing" @@ -78,245 +76,193 @@ func TestReplaceManagedSectionAppendsWhenMissing(t *testing.T) { } } -func TestManagedModuleBlockIncludesRulesGoExtension(t *testing.T) { - cfg := config{ - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.5.0", - runtimeVersion: "1.25.0", - rulesGoRemote: "https://github.com/example/repo.git", - rulesGoCommit: "deadbeef", - } - got := managedModuleBlock(cfg) - if !strings.Contains(got, `git_override(`) { - t.Fatalf("expected rules_go override in managed block:\n%s", got) - } - if !strings.Contains(got, `strip_prefix = "third_party/rgo/v0_60_0/base"`) { - t.Fatalf("expected vendored rules_go strip_prefix in managed block:\n%s", got) - } - if !strings.Contains(got, `use_extension("@rules_go//go:extensions.bzl", "orchestrion")`) { - t.Fatalf("expected rules_go orchestrion extension in managed block:\n%s", got) - } - if !strings.Contains(got, `test_optimization_go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")`) { - t.Fatalf("expected managed Go SDK extension in managed block:\n%s", got) - } - if !strings.Contains(got, `name = "test_optimization_go_sdk"`) || - !strings.Contains(got, `version = "1.25.0"`) || - !strings.Contains(got, `go_sdk_root = "@test_optimization_go_sdk//:ROOT"`) || - !strings.Contains(got, `go_sdk_version = "1.25.0"`) { - t.Fatalf("expected managed Go SDK identity in managed block:\n%s", got) - } - if !strings.Contains(got, `orchestrion.from_source(`) { - t.Fatalf("expected orchestrion extension call in managed block:\n%s", got) - } - if !strings.Contains(got, `version = "v1.12.0"`) { - t.Fatalf("expected orchestrion version in managed block:\n%s", got) - } - if strings.Contains(got, `enabled_by_env`) { - t.Fatalf("expected managed block to rely on the config-gated extension default:\n%s", got) - } - if !strings.Contains(got, `dd_trace_go_version = "v2.5.0"`) { - t.Fatalf("expected dd-trace-go version in managed block:\n%s", got) - } - if !strings.Contains(got, `use_repo(orchestrion, "rules_go_orchestrion_tool")`) { - t.Fatalf("expected rules_go orchestrion repo wiring in managed block:\n%s", got) +func TestReplaceManagedSectionPreservesSurroundingTextOnRerun(t *testing.T) { + for _, suffix := range []string{"", "\n", "\n\n# Other settings\n"} { + block := "# BEGIN\nowned = True\n# END\n" + input := "# Existing header\n" + block + suffix + first, err := replaceManagedSection(input, "# BEGIN", "# END", block) + if err != nil || first != input { + t.Fatalf("managed newline replacement changed surrounding text: %v\n%q -> %q", err, input, first) + } } } -func TestManagedModuleBlockWithoutRuntimeVersionPreservesLegacyShape(t *testing.T) { - cfg := config{ - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.5.0", - rulesGoRemote: "https://github.com/example/repo.git", - rulesGoCommit: "deadbeef", - } - got := managedModuleBlock(cfg) - if strings.Contains(got, `"go_sdk"`) || - strings.Contains(got, `go_sdk_root`) || - strings.Contains(got, `go_sdk_version`) { - t.Fatalf("expected a non-guided block without a runtime version to preserve legacy SDK discovery:\n%s", got) +func TestTopLevelCallSpansIgnoreQuotedAndCommentedDelimiters(t *testing.T) { + for _, value := range []string{`"https://mirror.example/file)#asset"`, "\"\"\"first )\nsecond (\nend\"\"\"", `'escaped \' ) # text'`} { + input := "# λ before a call(\n" + "archive_override(\n urls = [" + value + "], # ) ( \"\n)\n" + "bazel_dep(name = \"keep\", version = \"1\")\n" + blocks := topLevelCallBlocks(input) + if len(blocks) != 2 || blocks[0].function != "archive_override" || blocks[1].function != "bazel_dep" { + t.Fatalf("incorrect call spans for %q: %#v", value, blocks) + } + if input[blocks[0].start:blocks[0].end] != "archive_override(\n urls = ["+value+"], # ) ( \"\n)\n" { + t.Fatalf("scanner did not preserve original byte offsets for %q", value) + } } } -func TestManagedModuleBlockCanSelectBaseRulesGoVariant(t *testing.T) { - cfg := config{ - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.5.0", - rulesGoRemote: "https://github.com/example/repo.git", - rulesGoCommit: "deadbeef", - rulesGoVariant: "base", - } - got := managedModuleBlock(cfg) - if !strings.Contains(got, `strip_prefix = "third_party/rgo/v0_60_0/base"`) { - t.Fatalf("expected base rules_go variant in managed block:\n%s", got) +func TestManagedModuleBlockConfiguresInstrumentedSDK(t *testing.T) { + got, err := managedModuleBlock(config{runtimeVersion: "1.25.0"}) + if err != nil { + t.Fatalf("managedModuleBlock error: %v", err) } -} - -func TestValidateRulesGoVariantRejectsUnknownVariant(t *testing.T) { - if err := validateRulesGoVariant("custom"); err == nil { - t.Fatal("expected unknown rules_go variant to fail") + want := managedBlockStart + ` +topt_go_ddtest = use_extension( + "@datadog-rules-test-optimization-go//:topt_go_ddtest_extension.bzl", + "topt_go_ddtest", +) +topt_go_ddtest.sdk(go_version = "1.25.0") +` + managedBlockEnd + "\n" + if got != want { + t.Fatalf("managedModuleBlock=\n%s\nwant\n%s", got, want) } } -func TestValidateRulesGoVariantRejectsCompleteVariant(t *testing.T) { - err := validateRulesGoVariant("complete") - if err == nil || !strings.Contains(err.Error(), `rules_go_variant "complete" is no longer supported. Use "base".`) { - t.Fatalf("validateRulesGoVariant error=%v, want complete variant rejection", err) +func TestManagedModuleBlockWithoutRuntimeVersionReadsGoMod(t *testing.T) { + dir := t.TempDir() + for goModuleDir, want := range map[string]string{ + dir: `topt_go_ddtest.sdk(go_mod = "//:go.mod")`, + filepath.Join(dir, "services", "worker"): `topt_go_ddtest.sdk(go_mod = "//services/worker:go.mod")`, + } { + if err := os.MkdirAll(goModuleDir, 0o755); err != nil { + t.Fatalf("mkdir %s: %v", goModuleDir, err) + } + if err := os.WriteFile(filepath.Join(goModuleDir, "BUILD"), nil, 0o644); err != nil { + t.Fatalf("write BUILD: %v", err) + } + got, err := managedModuleBlock(config{workspaceDir: dir, goModuleDir: goModuleDir}) + if err != nil { + t.Fatalf("managedModuleBlock(%s) error: %v", goModuleDir, err) + } + if !strings.Contains(got, want) { + t.Fatalf("managedModuleBlock(%s) missing %q:\n%s", goModuleDir, want, got) + } } -} - -func TestValidateGoRuntimeCompatibility(t *testing.T) { - tests := []struct { - name string - orchestrion string - runtime string - wantError bool - }{ - {name: "minimum supported", orchestrion: "v1.12.0", runtime: "1.25.0"}, - {name: "newer Go", orchestrion: "v1.12.1", runtime: "1.26"}, - {name: "older Orchestrion", orchestrion: "v1.11.0", runtime: "1.24.9"}, - {name: "runtime omitted", orchestrion: "v1.12.0"}, - {name: "runtime too old", orchestrion: "v1.12.0", runtime: "1.24.9", wantError: true}, - {name: "malformed runtime", orchestrion: "v1.12.0", runtime: "latest", wantError: true}, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - err := validateGoRuntimeCompatibility(config{ - orchestrionVersion: test.orchestrion, - runtimeVersion: test.runtime, - }) - if (err != nil) != test.wantError { - t.Fatalf("validateGoRuntimeCompatibility() error=%v, wantError=%v", err, test.wantError) - } - }) + if _, err := managedModuleBlock(config{workspaceDir: dir, goModuleDir: filepath.Dir(dir)}); err == nil || !strings.Contains(err.Error(), "must be inside workspace") { + t.Fatalf("managedModuleBlock error=%v, want outside-workspace rejection", err) } } -func TestRunRejectsUnsupportedGoRuntimeBeforeWriting(t *testing.T) { +func TestManagedModuleBlockWithoutRuntimeVersionNeedsGoModPackage(t *testing.T) { dir := t.TempDir() - modulePath := filepath.Join(dir, "MODULE.bazel") - const originalModule = "module(name = \"example\")\n" - if err := os.WriteFile(modulePath, []byte(originalModule), 0o644); err != nil { - t.Fatalf("write MODULE.bazel: %v", err) - } - err := run(config{ - workspaceDir: dir, - guided: true, - service: "example", - runtimeVersion: "1.24.9", - orchestrionVersion: "v1.12.0", - rulesGoUpstream: "v0_60_0", - rulesGoVariant: "base", - datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - goBinary: defaultGoBinary, - goModSync: defaultGoModSync, - }) - if err == nil || !strings.Contains(err.Error(), "requires Go 1.25.0 or newer") { - t.Fatalf("run() error=%v, want minimum Go version error", err) - } - got, readErr := os.ReadFile(modulePath) - if readErr != nil { - t.Fatalf("read MODULE.bazel: %v", readErr) + _, err := managedModuleBlock(config{workspaceDir: dir, goModuleDir: dir}) + if err == nil || !strings.Contains(err.Error(), "needs a BUILD file next to go.mod") { + t.Fatalf("managedModuleBlock error=%v, want missing BUILD file", err) } - if string(got) != originalModule { - t.Fatalf("MODULE.bazel changed before runtime validation:\n%s", got) + if _, err := managedModuleBlock(config{workspaceDir: dir, goModuleDir: dir, runtimeVersion: "1.25.0"}); err != nil { + t.Fatalf("managedModuleBlock with --runtime-version error: %v", err) } } -func TestWorkspaceSnippetSupportsMixedFetchModes(t *testing.T) { +func TestWorkspaceSnippetWiresUpstreamRulesGoAndDdtest(t *testing.T) { cfg := config{ - rulesGoRemote: "https://github.com/example/repo.git", - rtoCommit: "published-sha", - datadogFetch: "git", - rulesGoFetch: "archive", - rulesGoRepoName: "io_bazel_rules_go", - rulesGoUpstream: "v0_60_0", - rulesGoVariant: "base", - rtoArchiveURL: "https://example.test/archive.tar.gz", - rtoArchiveSHA256: strings.Repeat("0", 64), - rtoArchivePrefix: "rules_test_optimization-published-sha", - rtoArchiveType: "tar.gz", - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - runtimeVersion: "1.25.0", + rtoCommit: "published-sha", + datadogFetch: "git", + rulesGoRepoName: "io_bazel_rules_go", + runtimeVersion: "1.25.0", } got, err := workspaceSnippet(cfg) if err != nil { t.Fatalf("workspaceSnippet error: %v", err) } - for _, want := range []string{ - `git_repository(`, - `name = "datadog-rules-test-optimization"`, - `load("@datadog-rules-test-optimization//tools/go:workspace_repositories.bzl", "datadog_go_test_optimization_workspace_repositories")`, - `datadog_fetch = "git"`, - `rto_commit = "published-sha"`, - `rules_go_fetch = "archive"`, - `rules_go_upstream = "v0_60_0"`, - `rules_go_variant = "base"`, - `go_orchestrion_tool_repo(`, - `dd_trace_go_version = "v2.9.1"`, - `go_sdk_root = "@go_sdk//:ROOT"`, - `go_sdk_version = "1.25.0"`, + ordered := []string{ + `# http_archive( +# name = "io_bazel_rules_go",`, + `git_repository( + name = "datadog-rules-test-optimization", + commit = "published-sha", + remote = "https://github.com/DataDog/rules_test_optimization.git", +)`, + `datadog_go_test_optimization_workspace_repositories( + rto_commit = "published-sha", + rto_remote = "https://github.com/DataDog/rules_test_optimization.git", + datadog_fetch = "git", + rules_go_repo_name = "io_bazel_rules_go", +)`, + `load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")`, + "\ngo_rules_dependencies()\n", `go_register_toolchains(version = "1.25.0")`, - } { - if !strings.Contains(got, want) { + `load("@datadog-rules-test-optimization-go//:topt_go_ddtest_repository.bzl", "topt_go_ddtest_workspace_repositories")`, + `topt_go_ddtest_workspace_repositories(go_version = "1.25.0")`, + `dd_topt_go_workspace_sync_repositories(`, + } + last := -1 + for _, want := range ordered { + index := strings.Index(got, want) + if index < 0 { t.Fatalf("workspace snippet missing %q:\n%s", want, got) } + if index < last { + t.Fatalf("workspace snippet has %q out of order:\n%s", want, got) + } + last = index } - toolRepoCall := strings.Index(got, "\ndd_topt_go_orchestrion_tool_repo(") - dependenciesCall := strings.Index(got, "\ngo_rules_dependencies()\n") - if toolRepoCall < 0 || dependenciesCall < 0 || toolRepoCall > dependenciesCall { - t.Fatalf("workspace snippet must declare the real Orchestrion repository before rules_go installs its fallback:\n%s", got) + for _, removed := range []string{"orchestrion", "rules_go_fetch", "rules_go_upstream", "rules_go_variant", "dd_trace_go", "go_sdk_root"} { + if strings.Contains(got, removed) { + t.Fatalf("workspace snippet still contains %q:\n%s", removed, got) + } } } -func TestWorkspaceSnippetFallsBackToRulesGoCommit(t *testing.T) { +func TestWorkspaceSnippetArchiveMode(t *testing.T) { cfg := config{ - rulesGoRemote: "https://github.com/example/repo.git", - rulesGoCommit: "legacy-published-sha", - datadogFetch: "git", - rulesGoFetch: "git", - rulesGoRepoName: "io_bazel_rules_go", - rulesGoVariant: "base", - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", + datadogFetch: "archive", + rulesGoRepoName: "io_bazel_rules_go", + rtoArchiveURL: "https://example.test/archive.tar.gz", + rtoArchiveSHA256: strings.Repeat("0", 64), + rtoArchivePrefix: "rules_test_optimization-published-sha", + rtoArchiveType: "tar.gz", } got, err := workspaceSnippet(cfg) if err != nil { t.Fatalf("workspaceSnippet error: %v", err) } - if !strings.Contains(got, `rto_commit = "legacy-published-sha"`) { - t.Fatalf("workspace snippet did not fall back to rulesGoCommit:\n%s", got) + for _, want := range []string{ + `http_archive( + name = "datadog-rules-test-optimization", + urls = ["https://example.test/archive.tar.gz"],`, + `datadog_fetch = "archive"`, + `rto_archive_url = "https://example.test/archive.tar.gz"`, + `rto_archive_prefix = "rules_test_optimization-published-sha"`, + `rto_archive_type = "tar.gz"`, + `topt_go_ddtest_workspace_repositories(go_version = "")`, + } { + if !strings.Contains(got, want) { + t.Fatalf("workspace snippet missing %q:\n%s", want, got) + } + } + if strings.Contains(got, "git_repository(\n") { + t.Fatalf("archive workspace snippet declares a git_repository:\n%s", got) + } + cfg.rtoArchiveSHA256 = "" + if _, err := workspaceSnippet(cfg); err == nil || !strings.Contains(err.Error(), "--rto-archive-sha256") { + t.Fatalf("workspaceSnippet error=%v, want missing archive flag", err) + } +} + +func TestWorkspaceSnippetRequiresRTOCommitForGit(t *testing.T) { + _, err := workspaceSnippet(config{datadogFetch: "git", rulesGoRepoName: "io_bazel_rules_go"}) + if err == nil || !strings.Contains(err.Error(), "--rto-commit is required when --datadog-fetch is git") { + t.Fatalf("workspaceSnippet error=%v, want missing --rto-commit", err) } } func TestWorkspaceSnippetDoesNotRequireModuleFiles(t *testing.T) { cfg := config{ - rulesGoRemote: "https://github.com/example/repo.git", - rtoCommit: "published-sha", - datadogFetch: "git", - rulesGoFetch: "git", - rulesGoRepoName: "io_bazel_rules_go", - rulesGoVariant: "base", - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", + rtoCommit: "published-sha", + datadogFetch: "git", + rulesGoRepoName: "io_bazel_rules_go", } if _, err := workspaceSnippet(cfg); err != nil { t.Fatalf("workspaceSnippet should not inspect MODULE.bazel or go.mod: %v", err) } } -func TestWorkspaceModeSnippetIncludesSyncAndBaseVariant(t *testing.T) { +func TestWorkspaceModeSnippetIncludesSync(t *testing.T) { cfg := config{ workspaceMode: true, - rulesGoRemote: "https://github.com/example/repo.git", rtoCommit: "published-sha", datadogFetch: "git", - rulesGoFetch: "git", rulesGoRepoName: "io_bazel_rules_go", - rulesGoVariant: "base", - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", syncRepoName: "test_optimization_data_worker", service: "worker", runtimeVersion: "1.25.9", @@ -330,18 +276,14 @@ func TestWorkspaceModeSnippetIncludesSyncAndBaseVariant(t *testing.T) { t.Fatalf("workspaceSnippet error: %v", err) } for _, want := range []string{ - `rules_go_variant = "base"`, `rules_go_repo_name = "io_bazel_rules_go"`, `load("@datadog-rules-test-optimization-go//:topt_go_workspace.bzl", "dd_topt_go_workspace_sync_repositories")`, - `load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo")`, `dd_topt_go_workspace_sync_repositories(`, - `dd_topt_go_orchestrion_tool_repo(`, `name = "test_optimization_data_worker"`, `service = "worker"`, `runtime_version = "1.25.9"`, - `go_sdk_root = "@go_sdk//:ROOT"`, - `go_sdk_version = "1.25.9"`, `go_register_toolchains(version = "1.25.9")`, + `topt_go_ddtest_workspace_repositories(go_version = "1.25.9")`, `require_git_metadata = True`, } { if !strings.Contains(got, want) { @@ -356,31 +298,24 @@ func TestWorkspaceModeSnippetIncludesSyncAndBaseVariant(t *testing.T) { func TestRunWorkspaceModeWritesSelectedFilesWithoutModuleBazel(t *testing.T) { dir := t.TempDir() cfg := config{ - workspaceDir: dir, - goModuleDir: dir, - workspaceMode: true, - writeRootTargets: true, - writeOrchestrionFiles: true, - writeWrapperTemplate: true, - writeBazelrc: true, - bazelrcPath: ".bazelrc", - bazelrcConfig: "test-optimization-worker", - syncRepoName: "test_optimization_data_worker", - doctorTargetName: "dd_test_optimization_doctor", - uploaderTargetName: "dd_upload_payloads", - expectedTargets: []string{"//worker:go_default_test"}, - wrapperPackage: "rules/go", - wrapperFile: "dd_topt_go_test.bzl", - plainWrapperName: "dd_go_test", - optimizedWrapperName: "dd_topt_go_test", - rulesGoRepoName: "io_bazel_rules_go", - datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - rulesGoVariant: defaultRulesGoVariant, - ddTraceGoVersion: defaultDDTraceGoVersion, - orchestrionVersion: defaultOrchestrionVersion, - rulesGoRemote: defaultRulesGoRemote, - goModSync: defaultGoModSync, + workspaceDir: dir, + goModuleDir: dir, + workspaceMode: true, + writeRootTargets: true, + writeWrapperTemplate: true, + writeBazelrc: true, + bazelrcPath: ".bazelrc", + bazelrcConfig: "test-optimization-worker", + syncRepoName: "test_optimization_data_worker", + doctorTargetName: "dd_test_optimization_doctor", + uploaderTargetName: "dd_upload_payloads", + expectedTargets: []string{"//worker:go_default_test"}, + wrapperPackage: "rules/go", + wrapperFile: "dd_topt_go_test.bzl", + plainWrapperName: "dd_go_test", + optimizedWrapperName: "dd_topt_go_test", + rulesGoRepoName: "io_bazel_rules_go", + datadogFetch: defaultDatadogFetch, } if err := run(cfg); err != nil { t.Fatalf("run workspace mode error: %v", err) @@ -388,8 +323,6 @@ func TestRunWorkspaceModeWritesSelectedFilesWithoutModuleBazel(t *testing.T) { for _, path := range []string{ "BUILD.bazel", ".bazelrc", - "orchestrion.tool.go", - "orchestrion.yml", "rules/go/BUILD.bazel", "rules/go/dd_topt_go_test.bzl", } { @@ -397,6 +330,11 @@ func TestRunWorkspaceModeWritesSelectedFilesWithoutModuleBazel(t *testing.T) { t.Fatalf("expected %s: %v", path, err) } } + for _, path := range []string{"orchestrion.tool.go", "orchestrion.yml"} { + if _, err := os.Stat(filepath.Join(dir, path)); !os.IsNotExist(err) { + t.Fatalf("workspace mode wrote %s: %v", path, err) + } + } rootBuild, err := os.ReadFile(filepath.Join(dir, "BUILD.bazel")) if err != nil { t.Fatalf("read root BUILD.bazel: %v", err) @@ -413,8 +351,7 @@ func TestRunWorkspaceModeWritesSelectedFilesWithoutModuleBazel(t *testing.T) { `def dd_go_test(name, **kwargs):`, `dd_topt_go_test = dd_go_test`, `load("@test_optimization_data_worker//:export.bzl", "topt_data")`, - `orchestrion_mode = "test_optimization"`, - `orchestrion_pin_files = _ORCHESTRION_PIN_FILES`, + `topt_data = topt_data,`, } { if !strings.Contains(wrapperText, want) { t.Fatalf("workspace wrapper missing %q:\n%s", want, wrapperText) @@ -422,6 +359,7 @@ func TestRunWorkspaceModeWritesSelectedFilesWithoutModuleBazel(t *testing.T) { } for _, forbidden := range []string{ "consumer-internal-name", + "orchestrion", "--test_env=DD_GIT_", `load("@io_bazel_rules_go//go:def.bzl", _raw_go_test = "go_test")`, `def dd_topt_go_test(name, **kwargs):`, @@ -435,29 +373,15 @@ func TestRunWorkspaceModeWritesSelectedFilesWithoutModuleBazel(t *testing.T) { } } -func TestWorkspaceModeDoesNotRunGoModSyncByDefault(t *testing.T) { - dir := t.TempDir() - if err := run(config{ - workspaceDir: dir, - workspaceMode: true, - writeOrchestrionFiles: true, - datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - rulesGoVariant: defaultRulesGoVariant, - ddTraceGoVersion: defaultDDTraceGoVersion, - orchestrionVersion: defaultOrchestrionVersion, - rulesGoRepoName: defaultRulesGoRepoName, - rulesGoRemote: defaultRulesGoRemote, - syncRepoName: defaultSyncRepoName, - doctorTargetName: defaultDoctorTargetName, - uploaderTargetName: defaultUploaderTargetName, - wrapperPackage: defaultWrapperPackage, - wrapperFile: defaultWorkspaceWrapperFile, - plainWrapperName: defaultPlainWrapperName, - optimizedWrapperName: defaultOptimizedWrapperName, - goModSync: defaultGoModSync, - }); err != nil { - t.Fatalf("workspace mode should write Orchestrion files without go.mod by default: %v", err) +func TestWorkspaceModeRequiresAnAction(t *testing.T) { + err := run(config{ + workspaceDir: t.TempDir(), + workspaceMode: true, + datadogFetch: defaultDatadogFetch, + syncRepoName: defaultSyncRepoName, + }) + if err == nil || !strings.Contains(err.Error(), "--workspace-mode requires at least one action") { + t.Fatalf("run error=%v, want missing workspace-mode action", err) } } @@ -498,6 +422,9 @@ func TestEnsureWorkspaceWrapperTemplateIsIdempotent(t *testing.T) { t.Fatalf("workspace wrapper missing %q:\n%s", want, first) } } + if strings.Contains(string(first), "orchestrion") { + t.Fatalf("workspace wrapper still passes Orchestrion attributes:\n%s", first) + } if strings.Contains(string(first), `_raw_go_test`) { t.Fatalf("workspace wrapper must not bypass config-gated dispatch:\n%s", first) } @@ -567,12 +494,7 @@ func TestRunPrintBazelrcSnippetDoesNotRequireModuleFiles(t *testing.T) { printBazelrcSnippet: true, bazelrcConfig: "test-optimization", datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - rulesGoVariant: defaultRulesGoVariant, - ddTraceGoVersion: defaultDDTraceGoVersion, - orchestrionVersion: defaultOrchestrionVersion, rulesGoRepoName: defaultRulesGoRepoName, - rulesGoRemote: defaultRulesGoRemote, syncRepoName: defaultSyncRepoName, doctorTargetName: defaultDoctorTargetName, uploaderTargetName: defaultUploaderTargetName, @@ -594,12 +516,7 @@ func TestRunPrintBazelrcSnippetUsesSelectiveRemoteDownloads(t *testing.T) { printBazelrcSnippet: true, bazelrcConfig: "test-optimization", datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - rulesGoVariant: defaultRulesGoVariant, - ddTraceGoVersion: defaultDDTraceGoVersion, - orchestrionVersion: defaultOrchestrionVersion, rulesGoRepoName: defaultRulesGoRepoName, - rulesGoRemote: defaultRulesGoRemote, syncRepoName: defaultSyncRepoName, doctorTargetName: defaultDoctorTargetName, uploaderTargetName: defaultUploaderTargetName, @@ -631,9 +548,6 @@ func TestRunPrintPublishedPinsDoesNotRequireModuleFiles(t *testing.T) { printPublishedPins: true, rtoCommit: commit, datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - rulesGoUpstream: "v0_60_0", - rulesGoVariant: "base", publishedPinsArchiveFetcher: staticArchiveFetcher("archive bytes"), }) }); err != nil { @@ -643,15 +557,27 @@ func TestRunPrintPublishedPinsDoesNotRequireModuleFiles(t *testing.T) { for _, want := range []string{ `RTO_COMMIT="` + commit + `"`, `RTO_ARCHIVE_URL="https://codeload.github.com/DataDog/rules_test_optimization/tar.gz/` + commit + `"`, - `RULES_GO_UPSTREAM="v0_60_0"`, - `RULES_GO_VARIANT="base"`, - `DD_TRACE_GO_VERSION="` + onboardingpins.DefaultDDTraceGoVersion + `"`, - `ORCHESTRION_VERSION="` + onboardingpins.DefaultOrchestrionVersion + `"`, } { if !strings.Contains(got, want) { t.Fatalf("published pins missing %q:\n%s", want, got) } } + for _, removed := range []string{"RULES_GO_", "ORCHESTRION", "DD_TRACE_GO"} { + if strings.Contains(got, removed) { + t.Fatalf("published pins still print %q:\n%s", removed, got) + } + } +} + +func TestRunPrintPublishedPinsRequiresRTOCommit(t *testing.T) { + err := run(config{ + workspaceDir: t.TempDir(), + printPublishedPins: true, + datadogFetch: defaultDatadogFetch, + }) + if err == nil || !strings.Contains(err.Error(), "published pins require --rto-commit") { + t.Fatalf("run --print-published-pins error=%v, want missing --rto-commit", err) + } } func TestRunPrintOnboardingSummaryUsesCurrentCommit(t *testing.T) { @@ -664,8 +590,6 @@ func TestRunPrintOnboardingSummaryUsesCurrentCommit(t *testing.T) { printOnboardingSummary: true, rtoCommit: commit, datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - rulesGoVariant: "base", publishedPinsArchiveFetcher: staticArchiveFetcher("archive bytes"), }) }); err != nil { @@ -691,8 +615,6 @@ func TestWriteOnboardingSummaryPreservesUnmanagedFile(t *testing.T) { writeOnboardingSummary: "TEST_OPTIMIZATION_GUIDE.md", rtoCommit: commit, datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - rulesGoVariant: "base", publishedPinsArchiveFetcher: staticArchiveFetcher("archive bytes"), }) if err == nil || !strings.Contains(err.Error(), "is not Datadog-managed") { @@ -707,8 +629,6 @@ func TestWriteOnboardingSummaryIsIdempotent(t *testing.T) { writeOnboardingSummary: "TEST_OPTIMIZATION_GUIDE.md", rtoCommit: commit, datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - rulesGoVariant: "base", publishedPinsArchiveFetcher: staticArchiveFetcher("archive bytes"), } if err := run(cfg); err != nil { @@ -752,12 +672,7 @@ func TestValidationScriptUsesConfiguredFlowAndUploadOptIn(t *testing.T) { shutdownBazelOnExit: true, defaultJobs: 1, datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - rulesGoVariant: defaultRulesGoVariant, - ddTraceGoVersion: defaultDDTraceGoVersion, - orchestrionVersion: defaultOrchestrionVersion, rulesGoRepoName: defaultRulesGoRepoName, - rulesGoRemote: defaultRulesGoRemote, doctorTargetName: defaultDoctorTargetName, uploaderTargetName: defaultUploaderTargetName, wrapperPackage: defaultWrapperPackage, @@ -774,7 +689,6 @@ func TestValidationScriptUsesConfiguredFlowAndUploadOptIn(t *testing.T) { `SYNC_REPO='test_optimization_data_worker'`, `DOCTOR_TARGET='//:dd_test_optimization_doctor'`, `UPLOAD_TARGET='//:dd_upload_payloads'`, - `RULES_GO_ORCHESTRION_PACKAGE='@rules_go//go/private/orchestrion'`, `WORKSPACE_DIR="$(pwd -P)"`, `BEP_TMP_ROOT=""`, `BEP_JSON_DIR=""`, @@ -805,8 +719,6 @@ func TestValidationScriptUsesConfiguredFlowAndUploadOptIn(t *testing.T) { `validate ordinary no-config bootstrap`, `validate explicit disabled precedence`, `query "@${SYNC_REPO}//:test_optimization_files"`, - `"${BAZEL}" cquery \ - "${RULES_GO_ORCHESTRION_PACKAGE}:tool_binary"`, `--repo_env=DD_TEST_OPTIMIZATION_ENABLED=0`, `upload skipped; rerun with --upload`, `${BAZEL}" shutdown`, @@ -819,6 +731,8 @@ func TestValidationScriptUsesConfiguredFlowAndUploadOptIn(t *testing.T) { `--test_env=DD_GIT_`, `DD_API_KEY=`, `rm -rf`, + `RULES_GO_ORCHESTRION_PACKAGE`, + `cquery`, } { if strings.Contains(got, forbidden) { t.Fatalf("validation script contains forbidden %q:\n%s", forbidden, got) @@ -864,12 +778,7 @@ func TestRunPrintValidationScriptDoesNotRequireModuleFiles(t *testing.T) { validationUploadTarget: "//:dd_upload_payloads", expectedTargets: []string{"//pkg:go_default_test"}, datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - rulesGoVariant: defaultRulesGoVariant, - ddTraceGoVersion: defaultDDTraceGoVersion, - orchestrionVersion: defaultOrchestrionVersion, rulesGoRepoName: defaultRulesGoRepoName, - rulesGoRemote: defaultRulesGoRemote, syncRepoName: defaultSyncRepoName, doctorTargetName: defaultDoctorTargetName, uploaderTargetName: defaultUploaderTargetName, @@ -926,7 +835,6 @@ func TestValidationScriptRunsWithNoControlTargets(t *testing.T) { logText := string(logBytes) for _, want := range []string{ "query @test_optimization_data//:test_optimization_files", - "cquery @rules_go//go/private/orchestrion:tool_binary --output=files", "query --config=test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=0 @test_optimization_data//:test_optimization_files", "sync --config=test-optimization --repo_env=FETCH_SALT=", "test --config=test-optimization --build_event_json_file=", @@ -941,6 +849,9 @@ func TestValidationScriptRunsWithNoControlTargets(t *testing.T) { t.Fatalf("fake bazel log missing %q:\n%s\nscript output:\n%s", want, logText, output) } } + if strings.Contains(logText, "cquery") { + t.Fatalf("validation script still checks the Orchestrion alias:\n%s", logText) + } if strings.Contains(logText, filepath.Join(dir, ".topt", "bep")) { t.Fatalf("validation script reused the workspace .topt BEP directory:\n%s", logText) } @@ -1288,12 +1199,7 @@ func TestRunGuidedWriteBazelrcValidatesBeforeWriting(t *testing.T) { bazelrcPath: ".bazelrc", bazelrcConfig: "test-optimization", datadogFetch: defaultDatadogFetch, - rulesGoFetch: defaultRulesGoFetch, - rulesGoVariant: defaultRulesGoVariant, - ddTraceGoVersion: defaultDDTraceGoVersion, - orchestrionVersion: defaultOrchestrionVersion, rulesGoRepoName: defaultRulesGoRepoName, - rulesGoRemote: defaultRulesGoRemote, syncRepoName: defaultSyncRepoName, doctorTargetName: defaultDoctorTargetName, uploaderTargetName: defaultUploaderTargetName, @@ -1403,301 +1309,199 @@ func TestWriteBazelrcBlockAllowsAbsolutePathInsideWorkspace(t *testing.T) { } } -func TestHydrateManagedRulesGoVariantPreservesBaseVariant(t *testing.T) { - content := `module(name = "example") +// orchestrionManagedBlock is a MODULE.bazel block that the Orchestrion +// bootstrap wrote, with its fork override. +func orchestrionManagedBlock(override string) string { + return legacyManagedBlockStart + "\n" + override + ` +test_optimization_go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") +test_optimization_go_sdk.download( + name = "test_optimization_go_sdk", + version = "1.25.0", +) +use_repo(test_optimization_go_sdk, "test_optimization_go_sdk") -# BEGIN Datadog Go Orchestrion bootstrap -git_override( - module_name = "rules_go", - remote = "https://github.com/example/repo.git", - commit = "deadbeef", - strip_prefix = "third_party/rgo/v0_60_0/base", +orchestrion = use_extension("@rules_go//go:extensions.bzl", "orchestrion") +orchestrion.from_source( + version = "v1.12.0", + dd_trace_go_version = "v2.9.1", + go_sdk_root = "@test_optimization_go_sdk//:ROOT", + go_sdk_version = "1.25.0", ) -# END Datadog Go Orchestrion bootstrap -` - cfg := config{rulesGoVariant: defaultRulesGoVariant, rulesGoUpstream: "default"} - if err := hydrateManagedRulesGoVariant(&cfg, content); err != nil { - t.Fatalf("hydrateManagedRulesGoVariant error: %v", err) - } - if cfg.rulesGoUpstream != "v0_60_0" { - t.Fatalf("rulesGoUpstream=%q, want v0_60_0", cfg.rulesGoUpstream) - } - if cfg.rulesGoVariant != "base" { - t.Fatalf("rulesGoVariant=%q, want base", cfg.rulesGoVariant) - } +use_repo(orchestrion, "rules_go_orchestrion_tool") +` + legacyManagedBlockEnd + "\n" } -func TestHydrateManagedRulesGoVariantAcceptsLegacyV060BaseStripPrefix(t *testing.T) { - content := `module(name = "example") - -# BEGIN Datadog Go Orchestrion bootstrap -git_override( +const orchestrionGitOverride = `git_override( module_name = "rules_go", - remote = "https://github.com/example/repo.git", - commit = "deadbeef", - strip_prefix = "third_party/rules_go_orchestrion_base", + remote = "https://github.com/DataDog/rules_test_optimization.git", + commit = "already-published-sha", + strip_prefix = "third_party/rgo/v0_60_0/base", ) -# END Datadog Go Orchestrion bootstrap ` - cfg := config{rulesGoVariant: defaultRulesGoVariant, rulesGoUpstream: "default"} - if err := hydrateManagedRulesGoVariant(&cfg, content); err != nil { - t.Fatalf("hydrateManagedRulesGoVariant error: %v", err) + +// writeModuleFile writes MODULE.bazel, go.mod and an empty root BUILD.bazel +// into a new workspace. +func writeModuleFile(t *testing.T, content string) (string, string) { + t.Helper() + dir := t.TempDir() + moduleFile := filepath.Join(dir, "MODULE.bazel") + if err := os.WriteFile(moduleFile, []byte(content), 0o644); err != nil { + t.Fatalf("write MODULE.bazel: %v", err) } - if cfg.rulesGoUpstream != "v0_60_0" { - t.Fatalf("rulesGoUpstream=%q, want v0_60_0", cfg.rulesGoUpstream) + if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/service\n\ngo 1.25.0\n"), 0o644); err != nil { + t.Fatalf("write go.mod: %v", err) } - if cfg.rulesGoVariant != "base" { - t.Fatalf("rulesGoVariant=%q, want base", cfg.rulesGoVariant) + if err := os.WriteFile(filepath.Join(dir, "BUILD.bazel"), nil, 0o644); err != nil { + t.Fatalf("write BUILD.bazel: %v", err) } + return dir, moduleFile } -func TestHydrateManagedRulesGoVariantRejectsLegacyCompleteStripPrefix(t *testing.T) { - content := `module(name = "example") - -# BEGIN Datadog Go Orchestrion bootstrap -git_override( - module_name = "rules_go", - remote = "https://github.com/example/repo.git", - commit = "deadbeef", - strip_prefix = "third_party/rules_go_orchestrion_complete", -) -# END Datadog Go Orchestrion bootstrap -` - cfg := config{rulesGoVariant: defaultRulesGoVariant, rulesGoUpstream: "default"} - err := hydrateManagedRulesGoVariant(&cfg, content) - if err == nil || !strings.Contains(err.Error(), `rules_go_variant "complete" is no longer supported. Use "base".`) { - t.Fatalf("hydrateManagedRulesGoVariant error=%v, want complete variant rejection", err) +func readFile(t *testing.T, path string) string { + t.Helper() + content, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) } + return string(content) } -func TestManagedModuleBlockIncludesPerModuleVersions(t *testing.T) { - cfg := config{ - orchestrionVersion: "v1.12.0", - ddTraceGoVersions: map[string]string{ - "github.com/DataDog/dd-trace-go/v2": "v2.7.0-rc.4", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2": "v2.8.0-dev.0.20260316165907-0cdd3b7576b7", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2": "v2.8.0-dev.0.20260316165907-0cdd3b7576b7", - }, - rulesGoRemote: "https://github.com/example/repo.git", - rulesGoCommit: "deadbeef", +func TestPatchModuleFileAddsRulesGoAndDdtestBlock(t *testing.T) { + dir, moduleFile := writeModuleFile(t, "module(name = \"example\")\n") + cfg := config{workspaceDir: dir, goModuleDir: dir, moduleFile: moduleFile} + if err := patchModuleFile(cfg); err != nil { + t.Fatalf("patchModuleFile error: %v", err) } - got := managedModuleBlock(cfg) - if !strings.Contains(got, `dd_trace_go_versions = {`) { - t.Fatalf("expected per-module dd-trace-go versions in managed block:\n%s", got) + first := readFile(t, moduleFile) + for _, want := range []string{ + `bazel_dep(name = "rules_go", version = "0.60.0")`, + managedBlockStart, + `topt_go_ddtest.sdk(go_mod = "//:go.mod")`, + managedBlockEnd, + } { + if !strings.Contains(first, want) { + t.Fatalf("MODULE.bazel missing %q:\n%s", want, first) + } } - if !strings.Contains(got, `"github.com/DataDog/dd-trace-go/v2": "v2.7.0-rc.4"`) { - t.Fatalf("expected root tracer version in managed block:\n%s", got) + if err := patchModuleFile(cfg); err != nil { + t.Fatalf("second patchModuleFile error: %v", err) } - if strings.Contains(got, `dd_trace_go_version =`) { - t.Fatalf("expected shared dd_trace_go_version to be omitted in per-module mode:\n%s", got) + if second := readFile(t, moduleFile); second != first { + t.Fatalf("patchModuleFile is not idempotent:\n%s\nthen:\n%s", first, second) } } -func TestReplaceManagedBlockRejectsConflictingOverride(t *testing.T) { - input := `module(name = "example") -git_override( +func TestPatchModuleFileReplacesOrchestrionBlock(t *testing.T) { + archiveOverride := `archive_override( module_name = "rules_go", - remote = "https://example.com/custom.git", - commit = "deadbeef", + urls = ["https://mirror.example/source.tar.gz"], + sha256 = "` + strings.Repeat("a", 64) + `", + strip_prefix = "rules_test_optimization-1.3.0/third_party/rgo/v0_60_0/base", ) ` - cfg := config{ - rulesGoRemote: defaultRulesGoRemote, - rulesGoCommit: "deadbeef", - } - compatible, err := rulesGoOverrideCompatible(input, cfg) - if err != nil { - t.Fatalf("rulesGoOverrideCompatible error: %v", err) - } - if compatible { - t.Fatal("expected incompatible rules_go override to be rejected") + for name, override := range map[string]string{"git": orchestrionGitOverride, "archive": archiveOverride} { + t.Run(name, func(t *testing.T) { + const header = "module(name = \"example\")\n\nbazel_dep(name = \"rules_go\", version = \"0.60.0\")\n\n" + const footer = "\nbazel_dep(name = \"keep_me\", version = \"1.0.0\")\n" + dir, moduleFile := writeModuleFile(t, header+orchestrionManagedBlock(override)+footer) + cfg := config{workspaceDir: dir, goModuleDir: dir, moduleFile: moduleFile, runtimeVersion: "1.25.0"} + if err := patchModuleFile(cfg); err != nil { + t.Fatalf("patchModuleFile error: %v", err) + } + block, err := managedModuleBlock(cfg) + if err != nil { + t.Fatalf("managedModuleBlock error: %v", err) + } + got := readFile(t, moduleFile) + if got != header+block+footer { + t.Fatalf("the ddtest block did not replace the Orchestrion block in place:\n%s", got) + } + if err := patchModuleFile(cfg); err != nil { + t.Fatalf("second patchModuleFile error: %v", err) + } + if again := readFile(t, moduleFile); again != got { + t.Fatalf("migration is not idempotent:\n%s\nthen:\n%s", got, again) + } + }) } } -func TestInferDatadogRepoOverride(t *testing.T) { - input := `module(name = "example") -git_override( - module_name = "datadog-rules-test-optimization-go", - remote = "https://github.com/DataDog/rules_test_optimization.git", - commit = "cafebabe", - strip_prefix = "modules/go", -) -` - remote, commit := inferDatadogRepoOverride(input) - if remote != "https://github.com/DataDog/rules_test_optimization.git" { - t.Fatalf("unexpected remote: %q", remote) - } - if commit != "cafebabe" { - t.Fatalf("unexpected commit: %q", commit) +func TestPatchModuleFileDropsOrchestrionBlockBesideDdtestBlock(t *testing.T) { + dir, moduleFile := writeModuleFile(t, "module(name = \"example\")\n") + cfg := config{workspaceDir: dir, goModuleDir: dir, moduleFile: moduleFile, runtimeVersion: "1.25.0"} + block, err := managedModuleBlock(cfg) + if err != nil { + t.Fatalf("managedModuleBlock error: %v", err) } -} - -func TestPatchModuleFileInfersRulesGoCommitFromDatadogOverride(t *testing.T) { - dir := t.TempDir() - moduleFile := filepath.Join(dir, "MODULE.bazel") - input := `module(name = "example") - -bazel_dep(name = "datadog-rules-test-optimization-go", version = "1.2.0") -git_override( - module_name = "datadog-rules-test-optimization-go", - remote = "https://github.com/DataDog/rules_test_optimization.git", - commit = "published-main-sha", - strip_prefix = "modules/go", -) -` - if err := os.WriteFile(moduleFile, []byte(input), 0o644); err != nil { + if err := os.WriteFile(moduleFile, []byte("module(name = \"example\")\n\n"+block+"\n"+orchestrionManagedBlock(orchestrionGitOverride)), 0o644); err != nil { t.Fatalf("write MODULE.bazel: %v", err) } - - cfg := config{ - moduleFile: moduleFile, - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - rulesGoRemote: defaultRulesGoRemote, - rulesGoVariant: defaultRulesGoVariant, - rulesGoCommitSet: false, - ddTraceGoVersionSet: true, - } if err := patchModuleFile(cfg); err != nil { t.Fatalf("patchModuleFile error: %v", err) } - - content, err := os.ReadFile(moduleFile) - if err != nil { - t.Fatalf("read MODULE.bazel: %v", err) - } - text := string(content) - if !strings.Contains(text, `commit = "published-main-sha"`) { - t.Fatalf("expected inferred rules_go commit in managed block:\n%s", text) - } - if !strings.Contains(text, `strip_prefix = "third_party/rgo/v0_60_0/base"`) { - t.Fatalf("expected base variant strip_prefix in managed block:\n%s", text) + got := readFile(t, moduleFile) + if strings.Contains(got, legacyManagedBlockStart) || strings.Contains(got, "orchestrion") || strings.Count(got, managedBlockStart) != 1 { + t.Fatalf("expected only the ddtest block:\n%s", got) } } -func TestPatchModuleFilePreservesManagedRulesGoCommitOnRerun(t *testing.T) { - dir := t.TempDir() - moduleFile := filepath.Join(dir, "MODULE.bazel") - input := `module(name = "example") - -# BEGIN Datadog Go Orchestrion bootstrap -git_override( +func TestPatchModuleFileKeepsOtherRulesGoOverrides(t *testing.T) { + const override = `git_override( module_name = "rules_go", - remote = "https://github.com/DataDog/rules_test_optimization.git", - commit = "already-published-sha", - strip_prefix = "third_party/rgo/v0_60_0/base", -) - -orchestrion = use_extension("@rules_go//go:extensions.bzl", "orchestrion") -orchestrion.from_source( - version = "v1.12.0", - dd_trace_go_version = "v2.9.1", + remote = "https://example.com/custom.git", + commit = "deadbeef", ) -use_repo(orchestrion, "rules_go_orchestrion_tool") -# END Datadog Go Orchestrion bootstrap ` - if err := os.WriteFile(moduleFile, []byte(input), 0o644); err != nil { - t.Fatalf("write MODULE.bazel: %v", err) - } - - cfg := config{ - moduleFile: moduleFile, - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - rulesGoRemote: defaultRulesGoRemote, - rulesGoVariant: "base", - ddTraceGoVersionSet: true, - } - if err := patchModuleFile(cfg); err != nil { + dir, moduleFile := writeModuleFile(t, "module(name = \"example\")\n"+override) + if err := patchModuleFile(config{workspaceDir: dir, goModuleDir: dir, moduleFile: moduleFile}); err != nil { t.Fatalf("patchModuleFile error: %v", err) } - - content, err := os.ReadFile(moduleFile) - if err != nil { - t.Fatalf("read MODULE.bazel: %v", err) - } - text := string(content) - if !strings.Contains(text, `commit = "already-published-sha"`) { - t.Fatalf("expected existing managed rules_go commit to be preserved:\n%s", text) - } - if !strings.Contains(text, `strip_prefix = "third_party/rgo/v0_60_0/base"`) { - t.Fatalf("expected existing base variant to be preserved:\n%s", text) + if got := readFile(t, moduleFile); !strings.Contains(got, override) || !strings.Contains(got, managedBlockStart) { + t.Fatalf("expected the consumer's rules_go override and the ddtest block:\n%s", got) } } -func TestPatchModuleFileMigratesLegacyManagedRulesGoStripPrefixOnRerun(t *testing.T) { - dir := t.TempDir() - moduleFile := filepath.Join(dir, "MODULE.bazel") - input := `module(name = "example") - -# BEGIN Datadog Go Orchestrion bootstrap -git_override( +func TestPatchModuleFileRejectsRulesGoForkOutsideBlockWithoutWriting(t *testing.T) { + for name, override := range map[string]string{ + "git": orchestrionGitOverride, + "legacy path": strings.Replace(orchestrionGitOverride, "third_party/rgo/v0_60_0/base", "third_party/rules_go_orchestrion_base", 1), + "computed prefix": strings.Replace(orchestrionGitOverride, `"third_party/rgo/v0_60_0/base"`, `"release/" + "third_party/rgo/v0_63_0/base"`, 1), + "archive": `archive_override( module_name = "rules_go", - remote = "https://github.com/DataDog/rules_test_optimization.git", - commit = "already-published-sha", - strip_prefix = "third_party/rules_go_orchestrion_base", + urls = ["https://mirror.example/source.tar.gz"], + strip_prefix = "release/third_party/rgo/v0_60_0/base", ) - -orchestrion = use_extension("@rules_go//go:extensions.bzl", "orchestrion") -orchestrion.from_source( - version = "v1.12.0", - dd_trace_go_version = "v2.9.1", +`, + "local path": `local_path_override( + module_name = "rules_go", + path = "/src/rules_test_optimization/third_party/rgo/v0_60_0/base", ) -use_repo(orchestrion, "rules_go_orchestrion_tool") -# END Datadog Go Orchestrion bootstrap -` - if err := os.WriteFile(moduleFile, []byte(input), 0o644); err != nil { - t.Fatalf("write MODULE.bazel: %v", err) - } - - cfg := config{ - moduleFile: moduleFile, - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - rulesGoRemote: defaultRulesGoRemote, - ddTraceGoVersionSet: true, - } - if err := patchModuleFile(cfg); err != nil { - t.Fatalf("patchModuleFile error: %v", err) - } - - content, err := os.ReadFile(moduleFile) - if err != nil { - t.Fatalf("read MODULE.bazel: %v", err) - } - text := string(content) - if !strings.Contains(text, `commit = "already-published-sha"`) { - t.Fatalf("expected existing managed rules_go commit to be preserved:\n%s", text) - } - if !strings.Contains(text, `strip_prefix = "third_party/rgo/v0_60_0/base"`) { - t.Fatalf("expected legacy strip_prefix to migrate to canonical path:\n%s", text) - } - if strings.Contains(text, `third_party/rules_go_orchestrion_base`) { - t.Fatalf("expected legacy strip_prefix to be removed after rerun:\n%s", text) +`, + "windows path": `local_path_override( + module_name = "rules_go", + path = "C:\\src\\rules_test_optimization\\third_party\\rgo\\v0_60_0\\base", +) +`, + } { + t.Run(name, func(t *testing.T) { + input := "module(name = \"example\")\n" + override + dir, moduleFile := writeModuleFile(t, input) + err := patchModuleFile(config{workspaceDir: dir, goModuleDir: dir, moduleFile: moduleFile, runtimeVersion: "1.25.0"}) + if err == nil || !strings.Contains(err.Error(), "removed Datadog rules_go fork") { + t.Fatalf("patchModuleFile error=%v, want fork override rejection", err) + } + if got := readFile(t, moduleFile); got != input { + t.Fatalf("rejected MODULE.bazel changed:\n%s", got) + } + }) } } -func TestPatchModuleFileRequiresRulesGoCommitWhenNoPublishedSourceExists(t *testing.T) { - dir := t.TempDir() - moduleFile := filepath.Join(dir, "MODULE.bazel") - if err := os.WriteFile(moduleFile, []byte("module(name = \"example\")\n"), 0o644); err != nil { - t.Fatalf("write MODULE.bazel: %v", err) - } - - cfg := config{ - moduleFile: moduleFile, - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - rulesGoRemote: defaultRulesGoRemote, - rulesGoVariant: defaultRulesGoVariant, - ddTraceGoVersionSet: true, - } - err := patchModuleFile(cfg) - if err == nil { - t.Fatal("expected patchModuleFile to require a rules_go commit") - } - if !strings.Contains(err.Error(), "rules_go fork commit is required") { - t.Fatalf("unexpected error: %v", err) +func TestPatchModuleFileIgnoresCommentedForkOverride(t *testing.T) { + commented := "# " + strings.ReplaceAll(strings.TrimSuffix(orchestrionGitOverride, "\n"), "\n", "\n# ") + "\n" + dir, moduleFile := writeModuleFile(t, "module(name = \"example\")\n"+commented) + if err := patchModuleFile(config{workspaceDir: dir, goModuleDir: dir, moduleFile: moduleFile, runtimeVersion: "1.25.0"}); err != nil { + t.Fatalf("patchModuleFile error: %v", err) } } @@ -1734,1025 +1538,24 @@ func TestManagedGuidedModuleBlockIncludesModulePath(t *testing.T) { } } -func TestWriteStarterOrchestrionYML(t *testing.T) { - dir := t.TempDir() - cfg := config{goModuleDir: dir} - if err := writeStarterOrchestrionYML(cfg); err != nil { - t.Fatalf("writeStarterOrchestrionYML error: %v", err) - } - path := filepath.Join(dir, "orchestrion.yml") - content, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read orchestrion.yml: %v", err) - } - if !strings.Contains(string(content), "aspects: []") { - t.Fatalf("unexpected orchestrion.yml content:\n%s", string(content)) +func TestValidateGuidedPrerequisites(t *testing.T) { + input := `module(name = "example") +bazel_dep(name = "datadog-rules-test-optimization", version = "1.2.0") +bazel_dep(name = "datadog-rules-test-optimization-go", version = "1.2.0") +` + if err := validateGuidedPrerequisites(input); err != nil { + t.Fatalf("validateGuidedPrerequisites error: %v", err) } } -func TestWriteOrchestrionToolFileWritesManagedImports(t *testing.T) { - dir := t.TempDir() - cfg := config{goModuleDir: dir} - if err := writeOrchestrionToolFile(cfg); err != nil { - t.Fatalf("writeOrchestrionToolFile error: %v", err) +func TestValidateGuidedPrerequisitesRequiresCoreAndGo(t *testing.T) { + input := `module(name = "example") +bazel_dep(name = "datadog-rules-test-optimization", version = "1.2.0") +` + if err := validateGuidedPrerequisites(input); err == nil { + t.Fatal("expected missing Go companion prerequisite to fail") } - - content, err := os.ReadFile(filepath.Join(dir, "orchestrion.tool.go")) - if err != nil { - t.Fatalf("read orchestrion.tool.go: %v", err) - } - text := string(content) - requiredImports := []string{ - `_ "github.com/DataDog/orchestrion" // integration`, - `_ "github.com/DataDog/dd-trace-go/contrib/log/slog/v2" // integration`, - `_ "github.com/DataDog/dd-trace-go/contrib/net/http/v2" // integration`, - `_ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration`, - } - for _, importLine := range requiredImports { - if !strings.Contains(text, importLine) { - t.Fatalf("managed orchestrion.tool.go missing %q:\n%s", importLine, text) - } - } - if strings.Contains(text, `github.com/DataDog/dd-trace-go/orchestrion/all/v2`) { - t.Fatalf("managed orchestrion.tool.go should not contain orchestrion/all/v2:\n%s", text) - } -} - -func TestBootstrapSyncCommandsTargetedModeAvoidsGoModTidy(t *testing.T) { - cfg := config{ - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - goModSync: "targeted", - } - - got := bootstrapSyncCommands(cfg) - if len(got) < 3 { - t.Fatalf("bootstrapSyncCommands returned too few commands: %#v", got) - } - if strings.Join(got[0], " ") != "mod edit -require=github.com/DataDog/orchestrion@v1.12.0" { - t.Fatalf("first bootstrap sync command=%q, want orchestrion version pin", strings.Join(got[0], " ")) - } - joined := strings.Join(flattenCommands(got), "\n") - if strings.Contains(joined, "mod tidy") { - t.Fatalf("targeted bootstrap sync must not run go mod tidy:\n%s", joined) - } - if !strings.Contains(joined, "list -mod=mod -tags=tools github.com/DataDog/orchestrion") { - t.Fatalf("targeted bootstrap sync must resolve the Orchestrion tool graph:\n%s", joined) - } - if !strings.Contains(joined, "list -mod=readonly -tags=tools github.com/DataDog/orchestrion") { - t.Fatalf("targeted bootstrap sync must verify readonly module completeness:\n%s", joined) - } - for _, mode := range []string{"-mod=mod", "-mod=readonly"} { - for _, packagePath := range orchestrionToolPackages { - want := "list " + mode + " -tags=tools " + packagePath - if !strings.Contains(joined, want) { - t.Fatalf("targeted bootstrap sync missing sequential package resolution %q:\n%s", want, joined) - } - } - } - expectedDownloads := []string{ - "github.com/DataDog/orchestrion@v1.12.0", - "github.com/DataDog/dd-trace-go/v2@v2.9.1", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2@v2.9.1", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2@v2.9.1", - } - for _, moduleVersion := range expectedDownloads { - want := "mod download " + moduleVersion - if !strings.Contains(joined, want) { - t.Fatalf("targeted bootstrap sync missing exact module download %q:\n%s", want, joined) - } - } - for _, command := range got { - if len(command) > 5 && command[0] == "list" { - t.Fatalf("targeted bootstrap sync must resolve one package root per go list command: %#v", command) - } - } -} - -func TestBootstrapSyncCommandsDefaultsToTargetedMode(t *testing.T) { - cfg := config{ - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - } - - joined := strings.Join(flattenCommands(bootstrapSyncCommands(cfg)), "\n") - if strings.Contains(joined, "mod tidy") { - t.Fatalf("default bootstrap sync must not run go mod tidy:\n%s", joined) - } - if !strings.Contains(joined, "list -mod=readonly -tags=tools") { - t.Fatalf("default bootstrap sync must use targeted readonly verification:\n%s", joined) - } -} - -func TestBootstrapSyncCommandsTidyModeKeepsExplicitGoModTidy(t *testing.T) { - cfg := config{ - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - goModSync: "tidy", - } - - joined := strings.Join(flattenCommands(bootstrapSyncCommands(cfg)), "\n") - if !strings.Contains(joined, "mod tidy") { - t.Fatalf("tidy bootstrap sync must keep explicit go mod tidy:\n%s", joined) - } -} - -func TestBootstrapSyncCommandsOffModeSkipsGoCommands(t *testing.T) { - cfg := config{ - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - goModSync: "off", - } - - if got := bootstrapSyncCommands(cfg); len(got) != 0 { - t.Fatalf("off bootstrap sync returned commands: %#v", got) - } -} - -func TestValidateGoModSyncModeRejectsUnknownValue(t *testing.T) { - if err := validateGoModSyncMode("broad"); err == nil { - t.Fatal("expected unknown go module sync mode to fail") - } -} - -func TestValidateGoBinaryRejectsShellStyleCommand(t *testing.T) { - for _, value := range []string{ - "go test", - " go", - "go ", - "go\t", - "bash", - } { - if err := validateGoBinary(value); err == nil { - t.Fatalf("expected --go-binary=%q to fail validation", value) - } - } -} - -func TestValidateGoBinaryAcceptsGoExecutableName(t *testing.T) { - for _, value := range []string{"go", filepath.Join("custom", "bin", "go"), filepath.Join("custom", "bin", "go.exe")} { - if err := validateGoBinary(value); err != nil { - t.Fatalf("expected --go-binary=%q to pass validation: %v", value, err) - } - } -} - -func TestSyncDDTraceGoVersionHonorsCustomGoBinary(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("shell-script based helper test is Unix-only") - } - dir := t.TempDir() - logPath := filepath.Join(dir, "go-args.log") - goPath := writeFakeGoTool(t, `#!/bin/sh -printf '%s\n' "$*" >> "`+logPath+`" -exit 0 -`) - cfg := config{ - goBinary: goPath, - goModuleDir: dir, - goModSync: "targeted", - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - ddTraceGoVersions: nil, - ddTraceGoVersionSet: true, - } - - if err := syncDDTraceGoVersion(cfg); err != nil { - t.Fatalf("syncDDTraceGoVersion error: %v", err) - } - - content, err := os.ReadFile(logPath) - if err != nil { - t.Fatalf("read fake go log: %v", err) - } - text := string(content) - if !strings.Contains(text, "list -mod=readonly -tags=tools") { - t.Fatalf("custom go binary did not receive readonly verification command:\n%s", text) - } -} - -func TestSyncDDTraceGoVersionReadonlyFailureMentionsTargetedSync(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("shell-script based helper test is Unix-only") - } - dir := t.TempDir() - goPath := writeFakeGoTool(t, `#!/bin/sh -case "$*" in - *"list -mod=readonly -tags=tools"*) - echo "readonly module graph is incomplete" >&2 - exit 1 - ;; - *) - exit 0 - ;; -esac -`) - cfg := config{ - goBinary: goPath, - goModuleDir: dir, - goModSync: "targeted", - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - ddTraceGoVersionSet: true, - } - - err := syncDDTraceGoVersion(cfg) - if err == nil { - t.Fatal("expected readonly verification failure") - } - text := err.Error() - if !strings.Contains(text, "--go-mod-sync=targeted") { - t.Fatalf("expected targeted sync hint in error, got:\n%s", text) - } - if !strings.Contains(text, "go_repository") { - t.Fatalf("expected go_repository refresh hint in error, got:\n%s", text) - } -} - -func flattenCommands(commands [][]string) []string { - flattened := make([]string, 0, len(commands)) - for _, command := range commands { - flattened = append(flattened, strings.Join(command, " ")) - } - return flattened -} - -func TestEnsureCIVisibilityOrchestrionImport(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "orchestrion.tool.go") - original := `package tools - -import ( - _ "github.com/DataDog/orchestrion" // integration - _ "github.com/DataDog/dd-trace-go/orchestrion/all/v2" // integration -) -` - if err := os.WriteFile(path, []byte(original), 0o644); err != nil { - t.Fatalf("write orchestrion.tool.go: %v", err) - } - - cfg := config{goModuleDir: dir} - if err := ensureCIVisibilityOrchestrionImport(cfg); err != nil { - t.Fatalf("ensureCIVisibilityOrchestrionImport error: %v", err) - } - - content, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read orchestrion.tool.go: %v", err) - } - text := string(content) - if !strings.Contains(text, `_ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration`) { - t.Fatalf("expected v2 orchestrion import in orchestrion.tool.go:\n%s", text) - } - if !strings.Contains(text, `_ "github.com/DataDog/dd-trace-go/contrib/net/http/v2" // integration`) { - t.Fatalf("expected net/http integration import in orchestrion.tool.go:\n%s", text) - } - if !strings.Contains(text, `_ "github.com/DataDog/dd-trace-go/contrib/log/slog/v2" // integration`) { - t.Fatalf("expected slog integration import in orchestrion.tool.go:\n%s", text) - } - if strings.Count(text, `_ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration`) != 1 { - t.Fatalf("expected v2 orchestrion import to be added once:\n%s", text) - } -} - -func TestEnsureCIVisibilityOrchestrionImportNoopWhenPresent(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "orchestrion.tool.go") - original := `package tools - -import ( - _ "github.com/DataDog/orchestrion" // integration - _ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration - _ "github.com/DataDog/dd-trace-go/contrib/net/http/v2" // integration - _ "github.com/DataDog/dd-trace-go/contrib/log/slog/v2" // integration - _ "github.com/DataDog/dd-trace-go/orchestrion/all/v2" // integration -) -` - if err := os.WriteFile(path, []byte(original), 0o644); err != nil { - t.Fatalf("write orchestrion.tool.go: %v", err) - } - - cfg := config{goModuleDir: dir} - if err := ensureCIVisibilityOrchestrionImport(cfg); err != nil { - t.Fatalf("ensureCIVisibilityOrchestrionImport error: %v", err) - } - - content, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read orchestrion.tool.go: %v", err) - } - text := string(content) - if strings.Count(text, `_ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration`) != 1 { - t.Fatalf("expected v2 orchestrion import to remain single:\n%s", text) - } - if strings.Count(text, `_ "github.com/DataDog/dd-trace-go/contrib/net/http/v2" // integration`) != 1 { - t.Fatalf("expected net/http integration import to remain single:\n%s", text) - } - if strings.Count(text, `_ "github.com/DataDog/dd-trace-go/contrib/log/slog/v2" // integration`) != 1 { - t.Fatalf("expected slog integration import to remain single:\n%s", text) - } -} - -func TestEnsureCIVisibilityOrchestrionImportHandlesBlankLinesAroundV2Import(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "orchestrion.tool.go") - original := `package tools - -import ( - _ "github.com/DataDog/orchestrion" // integration - - _ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration - _ "github.com/DataDog/dd-trace-go/contrib/net/http/v2" // integration - _ "github.com/DataDog/dd-trace-go/contrib/log/slog/v2" // integration - - _ "github.com/DataDog/dd-trace-go/orchestrion/all/v2" // integration -) -` - if err := os.WriteFile(path, []byte(original), 0o644); err != nil { - t.Fatalf("write orchestrion.tool.go: %v", err) - } - - cfg := config{goModuleDir: dir} - if err := ensureCIVisibilityOrchestrionImport(cfg); err != nil { - t.Fatalf("ensureCIVisibilityOrchestrionImport error: %v", err) - } - - content, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read orchestrion.tool.go: %v", err) - } - text := string(content) - if !strings.Contains(text, `_ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration`) { - t.Fatalf("expected v2 orchestrion import in orchestrion.tool.go:\n%s", text) - } - if !strings.Contains(text, `_ "github.com/DataDog/dd-trace-go/contrib/net/http/v2" // integration`) { - t.Fatalf("expected net/http integration import in orchestrion.tool.go:\n%s", text) - } - if !strings.Contains(text, `_ "github.com/DataDog/dd-trace-go/contrib/log/slog/v2" // integration`) { - t.Fatalf("expected slog integration import in orchestrion.tool.go:\n%s", text) - } -} - -func TestEnsureCIVisibilityOrchestrionImportRemovesLegacyV1Import(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "orchestrion.tool.go") - original := `package tools - -import ( - _ "github.com/DataDog/orchestrion" // integration - _ "gopkg.in/DataDog/dd-trace-go.v1/civisibility" // integration - _ "github.com/DataDog/dd-trace-go/orchestrion/all/v2" // integration -) -` - if err := os.WriteFile(path, []byte(original), 0o644); err != nil { - t.Fatalf("write orchestrion.tool.go: %v", err) - } - - cfg := config{goModuleDir: dir} - if err := ensureCIVisibilityOrchestrionImport(cfg); err != nil { - t.Fatalf("ensureCIVisibilityOrchestrionImport error: %v", err) - } - - content, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read orchestrion.tool.go: %v", err) - } - text := string(content) - if strings.Contains(text, `_ "gopkg.in/DataDog/dd-trace-go.v1/civisibility" // integration`) { - t.Fatalf("expected legacy v1 import to be removed:\n%s", text) - } - if !strings.Contains(text, `_ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration`) { - t.Fatalf("expected v2 orchestrion import after legacy cleanup:\n%s", text) - } - if !strings.Contains(text, `_ "github.com/DataDog/dd-trace-go/contrib/net/http/v2" // integration`) { - t.Fatalf("expected net/http integration import after legacy cleanup:\n%s", text) - } - if !strings.Contains(text, `_ "github.com/DataDog/dd-trace-go/contrib/log/slog/v2" // integration`) { - t.Fatalf("expected slog integration import after legacy cleanup:\n%s", text) - } -} - -func TestResolveDDTraceGoVersionQueryAcceptsCanonicalTag(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("shell-script based helper test is Unix-only") - } - goPath := writeFakeGoTool(t, `#!/bin/sh -case "$*" in - *"list -m -json github.com/DataDog/dd-trace-go/v2@v2.6.0"*) - printf '{"Version":"v2.6.0"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/net/http/v2@v2.6.0"*) - printf '{"Version":"v2.6.0"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/log/slog/v2@v2.6.0"*) - printf '{"Version":"v2.6.0"}\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/v2/orchestrion"*) - printf 'github.com/DataDog/dd-trace-go/v2/orchestrion\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/net/http/v2"*) - printf 'github.com/DataDog/dd-trace-go/contrib/net/http/v2\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/log/slog/v2"*) - printf 'github.com/DataDog/dd-trace-go/contrib/log/slog/v2\n' - ;; - *) - echo "unexpected args: $*" >&2 - exit 1 - ;; -esac -`) - - got, err := resolveDDTraceGoVersionQuery("v2.6.0", goPath, []string{"PATH=" + os.Getenv("PATH")}) - if err != nil { - t.Fatalf("resolveDDTraceGoVersionQuery error: %v", err) - } - for _, modulePath := range ddTraceGoModules { - if got[modulePath] != "v2.6.0" { - t.Fatalf("resolveDDTraceGoVersionQuery[%q]=%q, want %q", modulePath, got[modulePath], "v2.6.0") - } - } -} - -func TestResolveDDTraceGoVersionQueryNormalizesCommitSHA(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("shell-script based helper test is Unix-only") - } - goPath := writeFakeGoTool(t, `#!/bin/sh -case "$*" in - *"list -m -json github.com/DataDog/dd-trace-go/v2@abc123def456"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/net/http/v2@abc123def456"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/log/slog/v2@abc123def456"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/v2/orchestrion"*) - printf 'github.com/DataDog/dd-trace-go/v2/orchestrion\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/net/http/v2"*) - printf 'github.com/DataDog/dd-trace-go/contrib/net/http/v2\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/log/slog/v2"*) - printf 'github.com/DataDog/dd-trace-go/contrib/log/slog/v2\n' - ;; - *) - echo "unexpected args: $*" >&2 - exit 1 - ;; -esac -`) - - got, err := resolveDDTraceGoVersionQuery("abc123def456", goPath, []string{"PATH=" + os.Getenv("PATH")}) - if err != nil { - t.Fatalf("resolveDDTraceGoVersionQuery error: %v", err) - } - for _, modulePath := range ddTraceGoModules { - if got[modulePath] != "v2.7.0-rc.4" { - t.Fatalf("resolveDDTraceGoVersionQuery[%q]=%q, want %q", modulePath, got[modulePath], "v2.7.0-rc.4") - } - } -} - -func TestResolveDDTraceGoVersionQuerySupportsDivergentModules(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("shell-script based helper test is Unix-only") - } - goPath := writeFakeGoTool(t, `#!/bin/sh -case "$*" in - *"list -m -json github.com/DataDog/dd-trace-go/v2@main"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/net/http/v2@main"*) - printf '{"Version":"v2.6.0"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/log/slog/v2@main"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/v2/orchestrion"*) - printf 'github.com/DataDog/dd-trace-go/v2/orchestrion\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/net/http/v2"*) - printf 'github.com/DataDog/dd-trace-go/contrib/net/http/v2\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/log/slog/v2"*) - printf 'github.com/DataDog/dd-trace-go/contrib/log/slog/v2\n' - ;; - *) - echo "unexpected args: $*" >&2 - exit 1 - ;; -esac -`) - - got, err := resolveDDTraceGoVersionQuery("main", goPath, []string{"PATH=" + os.Getenv("PATH")}) - if err != nil { - t.Fatalf("resolveDDTraceGoVersionQuery error: %v", err) - } - if got["github.com/DataDog/dd-trace-go/v2"] != "v2.7.0-rc.4" { - t.Fatalf("unexpected root tracer version: %#v", got) - } - if got["github.com/DataDog/dd-trace-go/contrib/net/http/v2"] != "v2.6.0" { - t.Fatalf("unexpected net/http tracer version: %#v", got) - } -} - -func TestResolveDDTraceGoVersionQueryRejectsPackageValidationFailure(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("shell-script based helper test is Unix-only") - } - goPath := writeFakeGoTool(t, `#!/bin/sh -case "$*" in - *"list -m -json github.com/DataDog/dd-trace-go/v2@deadbeef"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/net/http/v2@deadbeef"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/log/slog/v2@deadbeef"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/v2/orchestrion"*) - printf 'github.com/DataDog/dd-trace-go/v2/orchestrion\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/net/http/v2"*) - echo 'missing package' >&2 - exit 1 - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/log/slog/v2"*) - printf 'github.com/DataDog/dd-trace-go/contrib/log/slog/v2\n' - ;; - *) - echo "unexpected args: $*" >&2 - exit 1 - ;; -esac -`) - - _, err := resolveDDTraceGoVersionQuery("deadbeef", goPath, []string{"PATH=" + os.Getenv("PATH")}) - if err == nil { - t.Fatal("expected package validation failure") - } - if !strings.Contains(err.Error(), `package validation failed`) { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestSyntheticDDTraceGoVersionCheckModUsesConfiguredVersions(t *testing.T) { - versions := map[string]string{ - "github.com/DataDog/dd-trace-go/v2": "v2.7.0-rc.4", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2": "v2.6.0", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2": "v2.7.0-rc.4", - } - got := syntheticDDTraceGoVersionCheckMod(versions) - for modulePath, version := range versions { - want := modulePath + " " + version - if !strings.Contains(got, want) { - t.Fatalf("syntheticDDTraceGoVersionCheckMod missing %q:\n%s", want, got) - } - } -} - -func TestWarmOrchestrionModuleCacheIncludesSyntheticTestOptimizationPackages(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("shell-script based helper test is Unix-only") - } - logPath := filepath.Join(t.TempDir(), "go-calls.log") - goPath := writeFakeGoTool(t, fmt.Sprintf(`#!/bin/sh -printf '%%s\n' "$*" >> %q -exit 0 -`, logPath)) - - cfg := config{ - goBinary: goPath, - goModuleDir: t.TempDir(), - ddTraceGoVersions: map[string]string{ - "github.com/DataDog/dd-trace-go/v2": "v2.9.1", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2": "v2.9.1", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2": "v2.9.1", - }, - } - if err := warmOrchestrionModuleCache(cfg); err != nil { - t.Fatalf("warmOrchestrionModuleCache error: %v", err) - } - data, err := os.ReadFile(logPath) - if err != nil { - t.Fatalf("read go call log: %v", err) - } - text := string(data) - for _, packagePath := range []string{ - "github.com/DataDog/dd-trace-go/v2/internal/civisibility/integrations/gotesting", - "github.com/DataDog/dd-trace-go/v2/internal/civisibility/integrations/gotesting/coverage", - "github.com/DataDog/dd-trace-go/v2/internal/civisibility/integrations", - } { - want := "list -mod=mod " + packagePath - if !strings.Contains(text, want) { - t.Fatalf("warmOrchestrionModuleCache missing %q:\n%s", want, text) - } - } -} - -func TestNormalizedGoEnvForcesGoWorkOff(t *testing.T) { - got := normalizedGoEnv([]string{"GO111MODULE=off", "GOWORK=/tmp/example", "PATH=" + os.Getenv("PATH")}) - if envValue(got, "GO111MODULE") != "on" { - t.Fatalf("GO111MODULE=%q, want %q", envValue(got, "GO111MODULE"), "on") - } - if envValue(got, "GOWORK") != "off" { - t.Fatalf("GOWORK=%q, want %q", envValue(got, "GOWORK"), "off") - } -} - -func TestOrchestrionBootstrapEnvUsesPublicModuleResolution(t *testing.T) { - got := orchestrionBootstrapEnv() - cacheRoot := filepath.Join(os.TempDir(), sharedOrchestrionCacheDirName) - for key, want := range map[string]string{ - "GOPATH": cacheRoot, - "GOMODCACHE": filepath.Join(cacheRoot, "pkg", "mod"), - "GOCACHE": filepath.Join(cacheRoot, "cache"), - } { - if value := envValue(got, key); value != want { - t.Fatalf("%s=%q, want %q", key, value, want) - } - prefix := key + "=" - count := 0 - for _, entry := range got { - if strings.HasPrefix(entry, prefix) { - count++ - } - } - if count != 1 { - t.Fatalf("%s appears %d times in bootstrap environment, want exactly once", key, count) - } - } - if envValue(got, "GOPROXY") != "https://proxy.golang.org,direct" { - t.Fatalf("GOPROXY=%q, want public proxy", envValue(got, "GOPROXY")) - } - if envValue(got, "GOSUMDB") != "sum.golang.org" { - t.Fatalf("GOSUMDB=%q, want public checksum database", envValue(got, "GOSUMDB")) - } - if envValue(got, "GOPRIVATE") != "" { - t.Fatalf("GOPRIVATE=%q, want empty for public bootstrap modules", envValue(got, "GOPRIVATE")) - } - if envValue(got, "GONOSUMDB") != "" { - t.Fatalf("GONOSUMDB=%q, want empty for public bootstrap modules", envValue(got, "GONOSUMDB")) - } -} - -func TestNormalizeDDTraceGoVersionMutatesConfigBeforePersistence(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("shell-script based helper test is Unix-only") - } - goPath := writeFakeGoTool(t, `#!/bin/sh -case "$*" in - *"list -m -json github.com/DataDog/dd-trace-go/v2@feature-branch"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/net/http/v2@feature-branch"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/log/slog/v2@feature-branch"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/v2/orchestrion"*) - printf 'github.com/DataDog/dd-trace-go/v2/orchestrion\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/net/http/v2"*) - printf 'github.com/DataDog/dd-trace-go/contrib/net/http/v2\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/log/slog/v2"*) - printf 'github.com/DataDog/dd-trace-go/contrib/log/slog/v2\n' - ;; - *) - echo "unexpected args: $*" >&2 - exit 1 - ;; -esac -`) - t.Setenv("PATH", filepath.Dir(goPath)) - - cfg := config{ddTraceGoVersion: "feature-branch"} - if err := normalizeDDTraceGoVersion(&cfg); err != nil { - t.Fatalf("normalizeDDTraceGoVersion error: %v", err) - } - if cfg.ddTraceGoVersion != "v2.7.0-rc.4" { - t.Fatalf("cfg.ddTraceGoVersion=%q, want %q", cfg.ddTraceGoVersion, "v2.7.0-rc.4") - } - if !strings.Contains(managedModuleBlock(cfg), `dd_trace_go_version = "v2.7.0-rc.4"`) { - t.Fatalf("expected managed module block to use canonical version:\n%s", managedModuleBlock(cfg)) - } -} - -func TestNormalizeDDTraceGoVersionUsesPerModuleConfigWhenVersionsDiverge(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("shell-script based helper test is Unix-only") - } - goPath := writeFakeGoTool(t, `#!/bin/sh -case "$*" in - *"list -m -json github.com/DataDog/dd-trace-go/v2@feature-branch"*) - printf '{"Version":"v2.7.0-rc.4"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/net/http/v2@feature-branch"*) - printf '{"Version":"v2.8.0-dev.0.20260316165907-0cdd3b7576b7"}\n' - ;; - *"list -m -json github.com/DataDog/dd-trace-go/contrib/log/slog/v2@feature-branch"*) - printf '{"Version":"v2.8.0-dev.0.20260316165907-0cdd3b7576b7"}\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/v2/orchestrion"*) - printf 'github.com/DataDog/dd-trace-go/v2/orchestrion\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/net/http/v2"*) - printf 'github.com/DataDog/dd-trace-go/contrib/net/http/v2\n' - ;; - *"list -mod=mod github.com/DataDog/dd-trace-go/contrib/log/slog/v2"*) - printf 'github.com/DataDog/dd-trace-go/contrib/log/slog/v2\n' - ;; - *) - echo "unexpected args: $*" >&2 - exit 1 - ;; -esac -`) - t.Setenv("PATH", filepath.Dir(goPath)) - - cfg := config{ddTraceGoVersion: "feature-branch"} - if err := normalizeDDTraceGoVersion(&cfg); err != nil { - t.Fatalf("normalizeDDTraceGoVersion error: %v", err) - } - if cfg.ddTraceGoVersion != "" { - t.Fatalf("cfg.ddTraceGoVersion=%q, want empty shared version", cfg.ddTraceGoVersion) - } - if cfg.ddTraceGoVersions["github.com/DataDog/dd-trace-go/v2"] != "v2.7.0-rc.4" { - t.Fatalf("unexpected per-module root version: %#v", cfg.ddTraceGoVersions) - } - if !strings.Contains(managedModuleBlock(cfg), `dd_trace_go_versions = {`) { - t.Fatalf("expected managed module block to use per-module config:\n%s", managedModuleBlock(cfg)) - } -} - -func TestEnsureBootstrapCanManageTracerConfigRejectsManualTracerConfig(t *testing.T) { - content := `module(name = "example") -orchestrion = use_extension("@rules_go//go:extensions.bzl", "orchestrion") -orchestrion.from_source( - version = "v1.12.0", - dd_trace_go_versions = { - "github.com/DataDog/dd-trace-go/v2": "v2.7.0-rc.4", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2": "v2.8.0-dev.0.20260316165907-0cdd3b7576b7", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2": "v2.8.0-dev.0.20260316165907-0cdd3b7576b7", - }, -) -` - if err := ensureBootstrapCanManageTracerConfig(content); err == nil { - t.Fatal("expected external manual tracer config to fail") - } -} - -func TestHydrateManagedTracerConfigPreservesPerModuleConfig(t *testing.T) { - content := `module(name = "example") -# BEGIN Datadog Go Orchestrion bootstrap -git_override( - module_name = "rules_go", - remote = "https://github.com/DataDog/rules_test_optimization.git", - commit = "deadbeef", - strip_prefix = "third_party/rgo/v0_60_0/base", -) - -orchestrion = use_extension("@rules_go//go:extensions.bzl", "orchestrion") -orchestrion.from_source( - version = "v1.12.0", - dd_trace_go_versions = { - "github.com/DataDog/dd-trace-go/v2": "v2.7.0-rc.4", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2": "v2.8.0-dev.0.20260316165907-0cdd3b7576b7", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2": "v2.8.0-dev.0.20260316165907-0cdd3b7576b7", - }, -) -use_repo(orchestrion, "rules_go_orchestrion_tool") -# END Datadog Go Orchestrion bootstrap -` - var cfg config - if err := hydrateManagedTracerConfig(&cfg, content); err != nil { - t.Fatalf("hydrateManagedTracerConfig error: %v", err) - } - if cfg.ddTraceGoVersion != "" { - t.Fatalf("expected shared tracer version to be empty, got %q", cfg.ddTraceGoVersion) - } - if cfg.ddTraceGoVersions["github.com/DataDog/dd-trace-go/v2"] != "v2.7.0-rc.4" { - t.Fatalf("unexpected managed per-module versions: %#v", cfg.ddTraceGoVersions) - } -} - -func TestParseGoRepositoryDeclarations(t *testing.T) { - content := ` -go_repository( - name = "com_github_datadog_orchestrion", - importpath = "github.com/DataDog/orchestrion", - version = "v1.12.0", -) - -go_repository( - name = 'com_github_datadog_dd_trace_go_v2', - importpath = 'github.com/DataDog/dd-trace-go/v2', - version = 'v2.9.1', -) -` - got := parseGoRepositoryDeclarations(content) - if got["github.com/DataDog/orchestrion"].version != "v1.12.0" { - t.Fatalf("unexpected orchestrion declaration: %#v", got) - } - if got["github.com/DataDog/dd-trace-go/v2"].name != "com_github_datadog_dd_trace_go_v2" { - t.Fatalf("unexpected dd-trace-go declaration: %#v", got) - } -} - -func TestParseGoRepositoryDeclarationsIgnoresCommentedBlocks(t *testing.T) { - content := ` -# go_repository( -# name = "com_github_datadog_orchestrion", -# importpath = "github.com/DataDog/orchestrion", -# version = "v9.9.9", -# ) - -go_repository( - name = "com_github_datadog_orchestrion", - importpath = "github.com/DataDog/orchestrion", # active declaration - version = "v1.12.0", -) -` - got := parseGoRepositoryDeclarations(content) - if got["github.com/DataDog/orchestrion"].version != "v1.12.0" { - t.Fatalf("commented declaration should be ignored: %#v", got) - } -} - -func TestCheckGoRepositoriesAcceptsMatchingVersions(t *testing.T) { - dir := t.TempDir() - writeRepositoriesFile(t, filepath.Join(dir, "repositories.bzl"), map[string]string{ - "github.com/DataDog/orchestrion": "v1.12.0", - "github.com/DataDog/dd-trace-go/v2": "v2.9.1", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2": "v2.9.1", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2": "v2.9.1", - }) - cfg := goRepositoryDiagnosticsTestConfig(dir) - if err := checkGoRepositories(cfg, false); err != nil { - t.Fatalf("checkGoRepositories error: %v", err) - } -} - -func TestCheckGoRepositoriesRejectsStaleVersionWithActionableMessage(t *testing.T) { - dir := t.TempDir() - writeRepositoriesFile(t, filepath.Join(dir, "repositories.bzl"), map[string]string{ - "github.com/DataDog/orchestrion": "v1.8.0", - "github.com/DataDog/dd-trace-go/v2": "v2.9.1", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2": "v2.9.1", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2": "v2.9.1", - }) - cfg := goRepositoryDiagnosticsTestConfig(dir) - err := checkGoRepositories(cfg, false) - if err == nil { - t.Fatal("expected stale go_repository declaration to fail") - } - text := err.Error() - for _, want := range []string{ - "github.com/DataDog/orchestrion", - "v1.8.0", - "v1.12.0", - "--go-repositories-refresh-command", - "go_repository(", - } { - if !strings.Contains(text, want) { - t.Fatalf("diagnostics missing %q:\n%s", want, text) - } - } -} - -func TestCheckGoRepositoriesRejectsMissingModule(t *testing.T) { - dir := t.TempDir() - writeRepositoriesFile(t, filepath.Join(dir, "repositories.bzl"), map[string]string{ - "github.com/DataDog/orchestrion": "v1.12.0", - }) - cfg := goRepositoryDiagnosticsTestConfig(dir) - err := checkGoRepositories(cfg, false) - if err == nil { - t.Fatal("expected missing go_repository declarations to fail") - } - if !strings.Contains(err.Error(), "github.com/DataDog/dd-trace-go/v2 is missing") { - t.Fatalf("expected missing module diagnostic, got:\n%s", err) - } -} - -func TestCheckGoRepositoriesRefreshHookRepairsStaleFile(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("shell refresh command test is Unix-only") - } - dir := t.TempDir() - repositoriesPath := filepath.Join(dir, "repositories.bzl") - writeRepositoriesFile(t, repositoriesPath, map[string]string{ - "github.com/DataDog/orchestrion": "v1.8.0", - "github.com/DataDog/dd-trace-go/v2": "v2.8.0", - "github.com/DataDog/dd-trace-go/contrib/net/http/v2": "v2.8.0", - "github.com/DataDog/dd-trace-go/contrib/log/slog/v2": "v2.8.0", - }) - refreshPath := filepath.Join(dir, "refresh.sh") - if err := os.WriteFile(refreshPath, []byte(`#!/bin/sh -cat > repositories.bzl <<'EOF' -go_repository(name = "com_github_datadog_orchestrion", importpath = "github.com/DataDog/orchestrion", version = "v1.12.0") -go_repository(name = "com_github_datadog_dd_trace_go_v2", importpath = "github.com/DataDog/dd-trace-go/v2", version = "v2.9.1") -go_repository(name = "com_github_datadog_dd_trace_go_contrib_net_http_v2", importpath = "github.com/DataDog/dd-trace-go/contrib/net/http/v2", version = "v2.9.1") -go_repository(name = "com_github_datadog_dd_trace_go_contrib_log_slog_v2", importpath = "github.com/DataDog/dd-trace-go/contrib/log/slog/v2", version = "v2.9.1") -EOF -`), 0o755); err != nil { - t.Fatalf("write refresh hook: %v", err) - } - cfg := goRepositoryDiagnosticsTestConfig(dir) - cfg.goRepositoriesRefreshCommand = "./refresh.sh" - if err := checkGoRepositories(cfg, true); err != nil { - t.Fatalf("refresh hook should repair stale repositories: %v", err) - } -} - -func TestCheckGoRepositoriesDoesNotRunRefreshWithoutTargetedSync(t *testing.T) { - dir := t.TempDir() - writeRepositoriesFile(t, filepath.Join(dir, "repositories.bzl"), map[string]string{ - "github.com/DataDog/orchestrion": "v1.8.0", - }) - cfg := goRepositoryDiagnosticsTestConfig(dir) - cfg.goRepositoriesRefreshCommand = "echo should-not-run > marker" - err := checkGoRepositories(cfg, false) - if err == nil { - t.Fatal("expected stale repositories without targeted sync to fail") - } - if !strings.Contains(err.Error(), "only runs after a successful --go-mod-sync=targeted") { - t.Fatalf("expected targeted sync guard, got:\n%s", err) - } - if _, statErr := os.Stat(filepath.Join(dir, "marker")); !os.IsNotExist(statErr) { - t.Fatalf("refresh command should not have run, stat err=%v", statErr) - } -} - -func TestGoRepositoryNameUsesGazelleStyleReversedDomain(t *testing.T) { - got := goRepositoryName("github.com/DataDog/dd-trace-go/contrib/net/http/v2") - want := "com_github_datadog_dd_trace_go_contrib_net_http_v2" - if got != want { - t.Fatalf("goRepositoryName=%q, want %q", got, want) - } -} - -func goRepositoryDiagnosticsTestConfig(dir string) config { - return config{ - workspaceDir: dir, - goRepositoriesFile: "repositories.bzl", - checkGoRepositories: true, - orchestrionVersion: "v1.12.0", - ddTraceGoVersion: "v2.9.1", - ddTraceGoVersions: nil, - ddTraceGoVersionSet: true, - } -} - -func writeRepositoriesFile(t *testing.T, path string, versions map[string]string) { - t.Helper() - var buf strings.Builder - for _, modulePath := range requiredGoRepositoryImportpaths() { - version, ok := versions[modulePath] - if !ok { - continue - } - fmt.Fprintf(&buf, "go_repository(\n name = %q,\n importpath = %q,\n version = %q,\n)\n\n", goRepositoryName(modulePath), modulePath, version) - } - if err := os.WriteFile(path, []byte(buf.String()), 0o644); err != nil { - t.Fatalf("write repositories file: %v", err) - } -} - -func writeFakeGoTool(t *testing.T, script string) string { - t.Helper() - dir := t.TempDir() - path := filepath.Join(dir, "go") - if err := os.WriteFile(path, []byte(script), 0o755); err != nil { - t.Fatalf("write fake go tool: %v", err) - } - return path -} - -func TestValidateGuidedPrerequisites(t *testing.T) { - input := `module(name = "example") -bazel_dep(name = "datadog-rules-test-optimization", version = "1.2.0") -bazel_dep(name = "datadog-rules-test-optimization-go", version = "1.2.0") -` - if err := validateGuidedPrerequisites(input); err != nil { - t.Fatalf("validateGuidedPrerequisites error: %v", err) - } -} - -func TestValidateGuidedPrerequisitesRequiresCoreAndGo(t *testing.T) { - input := `module(name = "example") -bazel_dep(name = "datadog-rules-test-optimization", version = "1.2.0") -` - if err := validateGuidedPrerequisites(input); err == nil { - t.Fatal("expected missing Go companion prerequisite to fail") - } -} +} func TestShouldAddGuidedBlockExactSingleServiceMatch(t *testing.T) { cfg := config{ @@ -2900,8 +1703,8 @@ func TestEnsureGuidedRootBuildCreatesBuildBazel(t *testing.T) { if !strings.Contains(text, uploaderBlockStart) || !strings.Contains(text, `name = "dd_upload_payloads"`) { t.Fatalf("expected managed uploader block in BUILD.bazel:\n%s", text) } - if !strings.Contains(text, pinExportsBlockStart) || !strings.Contains(text, `"orchestrion.tool.go"`) || !strings.Contains(text, `"orchestrion.yml"`) { - t.Fatalf("expected managed pin-file exports in BUILD.bazel:\n%s", text) + if strings.Contains(text, "exports_files") || strings.Contains(text, "orchestrion") { + t.Fatalf("expected no Orchestrion pin-file exports in BUILD.bazel:\n%s", text) } } @@ -2927,12 +1730,12 @@ func TestEnsureGuidedWrapperCreatesFiles(t *testing.T) { if !strings.Contains(text, wrapperBlockStart) || !strings.Contains(text, `load("@test_optimization_data//:export.bzl", "topt_data")`) { t.Fatalf("expected managed wrapper content:\n%s", text) } - if !strings.Contains(text, `orchestrion_mode = "test_optimization"`) || !strings.Contains(text, `orchestrion_pin_files = _ORCHESTRION_PIN_FILES`) || !strings.Contains(text, `"//:orchestrion.tool.go"`) { - t.Fatalf("expected wrapper to pass root Orchestrion pin files:\n%s", text) + if !strings.Contains(text, `topt_data = topt_data,`) || strings.Contains(text, "orchestrion") { + t.Fatalf("expected wrapper to pass only topt_data to dd_topt_go_test:\n%s", text) } } -func TestEnsureGuidedWorkspaceFilesUsesGoModulePackagePinLabels(t *testing.T) { +func TestEnsureGuidedWorkspaceFilesLeavesGoModulePackageAlone(t *testing.T) { dir := t.TempDir() goModuleDir := filepath.Join(dir, "services", "worker") if err := os.MkdirAll(goModuleDir, 0o755); err != nil { @@ -2948,31 +1751,8 @@ func TestEnsureGuidedWorkspaceFilesUsesGoModulePackagePinLabels(t *testing.T) { if err := ensureGuidedWorkspaceFiles(cfg); err != nil { t.Fatalf("ensureGuidedWorkspaceFiles error: %v", err) } - - rootBuild, err := os.ReadFile(filepath.Join(dir, "BUILD.bazel")) - if err != nil { - t.Fatalf("read root BUILD.bazel: %v", err) - } - if strings.Contains(string(rootBuild), pinExportsBlockStart) { - t.Fatalf("root BUILD.bazel should not export non-root Go module pin files:\n%s", string(rootBuild)) - } - - moduleBuild, err := os.ReadFile(filepath.Join(goModuleDir, "BUILD.bazel")) - if err != nil { - t.Fatalf("read module BUILD.bazel: %v", err) - } - if !strings.Contains(string(moduleBuild), pinExportsBlockStart) || !strings.Contains(string(moduleBuild), `"orchestrion.tool.go"`) { - t.Fatalf("expected module BUILD.bazel to export Orchestrion pin files:\n%s", string(moduleBuild)) - } - - wrapperPath := filepath.Join(dir, "tools", "build", "dd_go_test.bzl") - wrapperContent, err := os.ReadFile(wrapperPath) - if err != nil { - t.Fatalf("read dd_go_test.bzl: %v", err) - } - wrapper := string(wrapperContent) - if !strings.Contains(wrapper, `"//services/worker:orchestrion.tool.go"`) || strings.Contains(wrapper, `"//:orchestrion.tool.go"`) { - t.Fatalf("expected wrapper to reference Go module package pin labels:\n%s", wrapper) + if _, err := os.Stat(filepath.Join(goModuleDir, "BUILD.bazel")); !os.IsNotExist(err) { + t.Fatalf("guided setup wrote the Go module package BUILD.bazel: %v", err) } } @@ -2995,14 +1775,57 @@ func TestEnsureGuidedWrapperRejectsUnmanagedFileWithoutForce(t *testing.T) { } } +func TestRunGuidedWritesDdtestSetup(t *testing.T) { + const goMod = "module example.com/service\n\ngo 1.25.0\n" + dir, moduleFile := writeModuleFile(t, `module(name = "example") + +bazel_dep(name = "datadog-rules-test-optimization", version = "1.4.0") +bazel_dep(name = "datadog-rules-test-optimization-go", version = "1.4.0") +`) + cfg := config{ + workspaceDir: dir, + guided: true, + service: "example-service", + runtimeVersion: "1.25.0", + datadogFetch: defaultDatadogFetch, + syncRepoName: defaultSyncRepoName, + doctorTargetName: defaultDoctorTargetName, + uploaderTargetName: defaultUploaderTargetName, + } + if err := captureStdout(&strings.Builder{}, func() error { return run(cfg) }); err != nil { + t.Fatalf("run --guided error: %v", err) + } + module := readFile(t, moduleFile) + for _, want := range []string{ + `bazel_dep(name = "rules_go", version = "0.60.0")`, + `topt_go_ddtest.sdk(go_version = "1.25.0")`, + guidedBlockStart, + `service = "example-service"`, + `module_path = "example.com/service"`, + } { + if !strings.Contains(module, want) { + t.Fatalf("MODULE.bazel missing %q:\n%s", want, module) + } + } + if strings.Contains(module, "orchestrion") || strings.Contains(module, "_override(") { + t.Fatalf("MODULE.bazel still has Orchestrion wiring:\n%s", module) + } + if wrapper := readFile(t, filepath.Join(dir, "tools", "build", "dd_go_test.bzl")); strings.Contains(wrapper, "orchestrion") { + t.Fatalf("guided wrapper still passes Orchestrion attributes:\n%s", wrapper) + } + for _, path := range []string{"orchestrion.tool.go", "orchestrion.yml"} { + if _, err := os.Stat(filepath.Join(dir, path)); !os.IsNotExist(err) { + t.Fatalf("guided bootstrap wrote %s: %v", path, err) + } + } + if got := readFile(t, filepath.Join(dir, "go.mod")); got != goMod { + t.Fatalf("guided bootstrap changed go.mod:\n%s", got) + } +} + func publishedPinsTestRepo(t *testing.T) (string, string) { t.Helper() dir := t.TempDir() - for _, variant := range []string{"base", "base"} { - if err := os.MkdirAll(filepath.Join(dir, "third_party", "rules_go_orchestrion_"+variant), 0o755); err != nil { - t.Fatalf("create variant dir: %v", err) - } - } runGitCommand(t, dir, "init", ".") runGitCommand(t, dir, "config", "user.email", "test@example.com") runGitCommand(t, dir, "config", "user.name", "Test User") diff --git a/modules/go/tools/onboardingpins/BUILD.bazel b/modules/go/tools/onboardingpins/BUILD.bazel index 09cc6c32..1e52db54 100644 --- a/modules/go/tools/onboardingpins/BUILD.bazel +++ b/modules/go/tools/onboardingpins/BUILD.bazel @@ -4,18 +4,11 @@ # This product includes software developed at Datadog # (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. -load("@rules_go//go:def.bzl", "go_library", "go_test") - -exports_files([ - "rules_go_forks_gen.go", -], visibility = ["//visibility:public"]) +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "onboardingpins", - srcs = [ - "pins.go", - "rules_go_forks_gen.go", - ], + srcs = ["pins.go"], importpath = "github.com/DataDog/rules_test_optimization/modules/go/tools/onboardingpins", visibility = ["//visibility:public"], ) diff --git a/modules/go/tools/onboardingpins/pins.go b/modules/go/tools/onboardingpins/pins.go index 66589649..6ea02fa7 100644 --- a/modules/go/tools/onboardingpins/pins.go +++ b/modules/go/tools/onboardingpins/pins.go @@ -16,9 +16,7 @@ import ( "net/http" "os" "os/exec" - "path/filepath" "regexp" - "sort" "strings" "time" ) @@ -28,14 +26,8 @@ const ( DefaultRemote = "https://github.com/DataDog/rules_test_optimization.git" // DefaultArchiveType is the archive type used by GitHub codeload tarballs. DefaultArchiveType = "tar.gz" - // DefaultDDTraceGoVersion is the Go tracer version that supports the Bazel JSON payload contract. - DefaultDDTraceGoVersion = "v2.9.1" - // DefaultOrchestrionVersion is the Orchestrion version validated by the Go onboarding fixtures. - DefaultOrchestrionVersion = "v1.12.0" // DefaultMainRef is the remote ref that published pins must be reachable from. DefaultMainRef = "origin/main" - - removedCompleteVariantError = `rules_go_variant "complete" is no longer supported. Use "base".` ) // ArchiveFetcher returns the bytes for one archive URL. @@ -46,35 +38,25 @@ type GitRunner func(ctx context.Context, workspaceDir string, args ...string) (s // Options describes the inputs needed to produce published onboarding pins. type Options struct { - // WorkspaceDir is the repository checkout used for git reachability checks and variant validation. + // WorkspaceDir is the repository checkout used for git reachability checks. WorkspaceDir string // Commit is the published rules_test_optimization commit to pin. Commit string // Remote is the repository remote used by git_repository snippets. Remote string - // RulesGoUpstream is the Datadog-managed rules_go upstream support id. - RulesGoUpstream string - // Variant is the rules_go Orchestrion variant. Only "base" is supported. - Variant string // ArchiveType is the archive type used by http_archive. ArchiveType string - // DDTraceGoVersion is the tracer version printed in the operator summary. - DDTraceGoVersion string - // OrchestrionVersion is the Orchestrion version printed in the operator summary. - OrchestrionVersion string // MainRef is the remote ref that must contain Commit when VerifyMainReachable is true. MainRef string // VerifyMainReachable requires Commit to be an ancestor of MainRef. VerifyMainReachable bool - // ValidateVariantDir requires the selected variant directory to exist in WorkspaceDir. - ValidateVariantDir bool // FetchArchive overrides archive downloads for tests. FetchArchive ArchiveFetcher // RunGit overrides git execution for tests. RunGit GitRunner } -// Pins is the complete published tuple consumers need for Go/Orchestrion onboarding. +// Pins is the complete published tuple consumers need for Go onboarding. type Pins struct { // RTOCommit is the full published commit SHA. RTOCommit string @@ -88,28 +70,6 @@ type Pins struct { RTOArchivePrefix string // RTOArchiveType is the archive type expected by http_archive. RTOArchiveType string - // RulesGoUpstream is the selected Datadog-managed rules_go upstream support id. - RulesGoUpstream string - // Variant is the selected rules_go Orchestrion variant. - Variant string - // RulesGoStripPrefix is the archive subdirectory for the selected variant. - RulesGoStripPrefix string - // DDTraceGoVersion is the tracer version used by generated onboarding snippets. - DDTraceGoVersion string - // OrchestrionVersion is the Orchestrion version used by generated onboarding snippets. - OrchestrionVersion string -} - -type rulesGoForkSelection struct { - upstream string - variant string -} - -var legacyRulesGoForkStripPrefixAliases = map[string]rulesGoForkSelection{ - "third_party/rules_go_orchestrion_base": { - upstream: "v0_60_0", - variant: "base", - }, } // Resolve computes a published pin tuple from a checkout and commit. @@ -118,18 +78,9 @@ func Resolve(ctx context.Context, opts Options) (Pins, error) { if strings.TrimSpace(opts.Commit) == "" { return Pins{}, errors.New("--commit is required and must be a published full SHA") } - rulesGoUpstream, rulesGoVariant, rulesGoStripPrefix, err := resolveRulesGoStripPrefix(opts.RulesGoUpstream, opts.Variant) - if err != nil { - return Pins{}, err - } if opts.ArchiveType != DefaultArchiveType { return Pins{}, fmt.Errorf("--archive-type must be %q for published GitHub codeload pins, got %q", DefaultArchiveType, opts.ArchiveType) } - if opts.ValidateVariantDir { - if err := validateVariantDir(opts.WorkspaceDir, rulesGoStripPrefix); err != nil { - return Pins{}, err - } - } commit := strings.TrimSpace(opts.Commit) if opts.VerifyMainReachable { var err error @@ -152,17 +103,12 @@ func Resolve(ctx context.Context, opts Options) (Pins, error) { return Pins{}, err } return Pins{ - RTOCommit: commit, - RTORemote: opts.Remote, - RTOArchiveURL: archiveURL, - RTOArchiveSHA256: archiveSHA, - RTOArchivePrefix: ArchivePrefix(opts.Remote, commit), - RTOArchiveType: opts.ArchiveType, - RulesGoUpstream: rulesGoUpstream, - Variant: rulesGoVariant, - RulesGoStripPrefix: rulesGoStripPrefix, - DDTraceGoVersion: opts.DDTraceGoVersion, - OrchestrionVersion: opts.OrchestrionVersion, + RTOCommit: commit, + RTORemote: opts.Remote, + RTOArchiveURL: archiveURL, + RTOArchiveSHA256: archiveSHA, + RTOArchivePrefix: ArchivePrefix(opts.Remote, commit), + RTOArchiveType: opts.ArchiveType, }, nil } @@ -175,11 +121,6 @@ func FormatShell(pins Pins) string { fmt.Fprintf(&buf, "RTO_ARCHIVE_SHA256=%q\n", pins.RTOArchiveSHA256) fmt.Fprintf(&buf, "RTO_ARCHIVE_PREFIX=%q\n", pins.RTOArchivePrefix) fmt.Fprintf(&buf, "RTO_ARCHIVE_TYPE=%q\n", pins.RTOArchiveType) - fmt.Fprintf(&buf, "RULES_GO_UPSTREAM=%q\n", pins.RulesGoUpstream) - fmt.Fprintf(&buf, "RULES_GO_VARIANT=%q\n", pins.Variant) - fmt.Fprintf(&buf, "RULES_GO_STRIP_PREFIX=%q\n", pins.RulesGoStripPrefix) - fmt.Fprintf(&buf, "DD_TRACE_GO_VERSION=%q\n", pins.DDTraceGoVersion) - fmt.Fprintf(&buf, "ORCHESTRION_VERSION=%q\n", pins.OrchestrionVersion) return buf.String() } @@ -187,7 +128,7 @@ func FormatShell(pins Pins) string { func FormatMarkdownSummary(pins Pins) string { var buf strings.Builder buf.WriteString("# Datadog Go Test Optimization Onboarding Pins\n\n") - buf.WriteString("Use this published tuple when wiring a WORKSPACE or Bzlmod repository to the Go/Orchestrion integration.\n\n") + buf.WriteString("Use this published tuple when wiring a WORKSPACE or Bzlmod repository to the Go integration, which builds tests with the Go SDK that `ddtest instrument` instruments.\n\n") buf.WriteString("## Published Rules Tuple\n\n") fmt.Fprintf(&buf, "- `RTO_COMMIT`: `%s`\n", pins.RTOCommit) fmt.Fprintf(&buf, "- `RTO_REMOTE`: `%s`\n", pins.RTORemote) @@ -195,11 +136,6 @@ func FormatMarkdownSummary(pins Pins) string { fmt.Fprintf(&buf, "- `RTO_ARCHIVE_SHA256`: `%s`\n", pins.RTOArchiveSHA256) fmt.Fprintf(&buf, "- `RTO_ARCHIVE_PREFIX`: `%s`\n", pins.RTOArchivePrefix) fmt.Fprintf(&buf, "- `RTO_ARCHIVE_TYPE`: `%s`\n", pins.RTOArchiveType) - fmt.Fprintf(&buf, "- `rules_go_upstream`: `%s`\n", pins.RulesGoUpstream) - fmt.Fprintf(&buf, "- `rules_go_variant`: `%s`\n", pins.Variant) - fmt.Fprintf(&buf, "- `rules_go_strip_prefix`: `%s`\n", pins.RulesGoStripPrefix) - fmt.Fprintf(&buf, "- `dd_trace_go_version`: `%s`\n", pins.DDTraceGoVersion) - fmt.Fprintf(&buf, "- `orchestrion_version`: `%s`\n", pins.OrchestrionVersion) buf.WriteString("\n") buf.WriteString("## Recommended Flow\n\n") buf.WriteString("1. Wire the published repositories using the tuple above.\n") @@ -253,21 +189,9 @@ func (opts Options) withDefaults() Options { if opts.Remote == "" { opts.Remote = DefaultRemote } - if opts.RulesGoUpstream == "" { - opts.RulesGoUpstream = "default" - } - if opts.Variant == "" { - opts.Variant = "base" - } if opts.ArchiveType == "" { opts.ArchiveType = DefaultArchiveType } - if opts.DDTraceGoVersion == "" { - opts.DDTraceGoVersion = DefaultDDTraceGoVersion - } - if opts.OrchestrionVersion == "" { - opts.OrchestrionVersion = DefaultOrchestrionVersion - } if opts.MainRef == "" { opts.MainRef = DefaultMainRef } @@ -277,85 +201,6 @@ func (opts Options) withDefaults() Options { return opts } -// RulesGoStripPrefix resolves a rules_go upstream and variant to a repository path. -func RulesGoStripPrefix(upstream string, variant string) (string, error) { - _, _, prefix, err := resolveRulesGoStripPrefix(upstream, variant) - return prefix, err -} - -// RulesGoSelectionForStripPrefix resolves a generated fork path to upstream and variant ids. -func RulesGoSelectionForStripPrefix(stripPrefix string) (string, string, error) { - for upstream, variants := range rulesGoForkStripPrefixes { - for variant, candidate := range variants { - if candidate == stripPrefix { - return upstream, variant, nil - } - } - } - if stripPrefix == "third_party/rules_go_orchestrion_complete" { - return "", "", errors.New(removedCompleteVariantError) - } - if selection, ok := legacyRulesGoForkStripPrefixAliases[stripPrefix]; ok { - return selection.upstream, selection.variant, nil - } - return "", "", fmt.Errorf("rules_go strip_prefix must be one of the generated fork paths or compatibility aliases, got %q", stripPrefix) -} - -func resolveRulesGoStripPrefix(upstream string, variant string) (string, string, string, error) { - if upstream == "" || upstream == "default" { - upstream = DefaultRulesGoUpstream - } - if variant == "" || variant == "default" { - variant = "base" - } - if variant == "complete" { - return "", "", "", errors.New(removedCompleteVariantError) - } - variants, ok := rulesGoForkStripPrefixes[upstream] - if !ok { - return "", "", "", fmt.Errorf("rules_go_upstream must be one of %v, got %q", sortedRulesGoUpstreams(), upstream) - } - prefix, ok := variants[variant] - if !ok { - return "", "", "", fmt.Errorf("rules_go_variant must be one of %v for rules_go_upstream %q, got %q", sortedRulesGoVariants(variants), upstream, variant) - } - return upstream, variant, prefix, nil -} - -func sortedRulesGoUpstreams() []string { - upstreams := make([]string, 0, len(rulesGoForkStripPrefixes)) - for upstream := range rulesGoForkStripPrefixes { - upstreams = append(upstreams, upstream) - } - sort.Strings(upstreams) - return upstreams -} - -func sortedRulesGoVariants(variants map[string]string) []string { - names := make([]string, 0, len(variants)) - for variant := range variants { - names = append(names, variant) - } - sort.Strings(names) - return names -} - -// validateVariantDir confirms the selected vendored fork path exists locally. -func validateVariantDir(workspaceDir, rulesGoStripPrefix string) error { - path := filepath.Join(workspaceDir, filepath.FromSlash(rulesGoStripPrefix)) - info, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("rules_go fork path does not exist at %s", path) - } - return fmt.Errorf("stat rules_go fork path %s: %w", path, err) - } - if !info.IsDir() { - return fmt.Errorf("rules_go fork path is not a directory: %s", path) - } - return nil -} - // resolveCommit resolves any Git revision accepted by git to a full commit SHA. func resolveCommit(ctx context.Context, opts Options) (string, error) { out, err := opts.RunGit(ctx, opts.WorkspaceDir, "rev-parse", "--verify", opts.Commit+"^{commit}") diff --git a/modules/go/tools/onboardingpins/pins_test.go b/modules/go/tools/onboardingpins/pins_test.go index b2a65eda..1b73d40c 100644 --- a/modules/go/tools/onboardingpins/pins_test.go +++ b/modules/go/tools/onboardingpins/pins_test.go @@ -22,7 +22,7 @@ import ( func TestResolveRejectsEmptyCommit(t *testing.T) { _, err := Resolve(context.Background(), Options{ - WorkspaceDir: variantWorkspace(t), + WorkspaceDir: t.TempDir(), FetchArchive: archiveFromString("archive"), }) if err == nil || !strings.Contains(err.Error(), "--commit is required") { @@ -31,7 +31,7 @@ func TestResolveRejectsEmptyCommit(t *testing.T) { } func TestResolveRejectsCommitNotReachableFromMain(t *testing.T) { - dir := variantWorkspace(t) + dir := t.TempDir() runGit(t, dir, "init", ".") runGit(t, dir, "config", "user.email", "test@example.com") runGit(t, dir, "config", "user.name", "Test User") @@ -55,8 +55,6 @@ func TestResolveRejectsCommitNotReachableFromMain(t *testing.T) { Commit: featureCommit, VerifyMainReachable: true, FetchArchive: archiveFromString("archive"), - DDTraceGoVersion: DefaultDDTraceGoVersion, - OrchestrionVersion: DefaultOrchestrionVersion, }) if err == nil || !strings.Contains(err.Error(), "is not reachable from origin/main") { t.Fatalf("Resolve error=%v, want reachability rejection", err) @@ -88,7 +86,7 @@ func TestResolveRejectsNonTarArchiveType(t *testing.T) { } func TestResolveReturnsPublishedTuple(t *testing.T) { - dir := variantWorkspace(t) + dir := t.TempDir() runGit(t, dir, "init", ".") runGit(t, dir, "config", "user.email", "test@example.com") runGit(t, dir, "config", "user.name", "Test User") @@ -103,7 +101,6 @@ func TestResolveReturnsPublishedTuple(t *testing.T) { pins, err := Resolve(context.Background(), Options{ WorkspaceDir: dir, Commit: commit, - Variant: "base", VerifyMainReachable: true, FetchArchive: archiveFromString("archive"), }) @@ -119,177 +116,18 @@ func TestResolveReturnsPublishedTuple(t *testing.T) { if pins.RTOArchivePrefix != "rules_test_optimization-"+commit { t.Fatalf("unexpected archive prefix: %s", pins.RTOArchivePrefix) } - if pins.RulesGoStripPrefix != "third_party/rgo/v0_60_0/base" { - t.Fatalf("unexpected strip prefix: %s", pins.RulesGoStripPrefix) - } -} - -func TestResolveUsesDefaultRulesGoUpstream(t *testing.T) { - commit := strings.Repeat("a", 40) - pins, err := Resolve(context.Background(), Options{ - WorkspaceDir: variantWorkspace(t), - Commit: commit, - Variant: "base", - DDTraceGoVersion: DefaultDDTraceGoVersion, - OrchestrionVersion: DefaultOrchestrionVersion, - FetchArchive: archiveFromString("archive"), - ValidateVariantDir: true, - }) - if err != nil { - t.Fatalf("Resolve error: %v", err) - } - if pins.RulesGoUpstream != DefaultRulesGoUpstream { - t.Fatalf("RulesGoUpstream=%q, want %q", pins.RulesGoUpstream, DefaultRulesGoUpstream) - } - if pins.RulesGoStripPrefix != "third_party/rgo/v0_60_0/base" { - t.Fatalf("RulesGoStripPrefix=%q", pins.RulesGoStripPrefix) - } -} - -func TestResolveNormalizesDefaultRulesGoVariant(t *testing.T) { - commit := strings.Repeat("a", 40) - pins, err := Resolve(context.Background(), Options{ - WorkspaceDir: variantWorkspace(t), - Commit: commit, - Variant: "default", - DDTraceGoVersion: DefaultDDTraceGoVersion, - OrchestrionVersion: DefaultOrchestrionVersion, - FetchArchive: archiveFromString("archive"), - ValidateVariantDir: true, - }) - if err != nil { - t.Fatalf("Resolve error: %v", err) - } - if pins.Variant != "base" { - t.Fatalf("Variant=%q, want base", pins.Variant) - } - if pins.RulesGoStripPrefix != "third_party/rgo/v0_60_0/base" { - t.Fatalf("RulesGoStripPrefix=%q", pins.RulesGoStripPrefix) - } -} - -func TestResolveRejectsUnknownRulesGoUpstream(t *testing.T) { - _, err := Resolve(context.Background(), Options{ - Commit: strings.Repeat("a", 40), - Variant: "base", - RulesGoUpstream: "v9_99_0", - FetchArchive: archiveFromString("archive"), - DDTraceGoVersion: DefaultDDTraceGoVersion, - }) - if err == nil || !strings.Contains(err.Error(), "rules_go_upstream") { - t.Fatalf("Resolve error=%v, want rules_go_upstream rejection", err) - } -} - -func TestRulesGoStripPrefixDefaultsToGeneratedBase(t *testing.T) { - prefix, err := RulesGoStripPrefix("default", "default") - if err != nil { - t.Fatalf("RulesGoStripPrefix error: %v", err) - } - if prefix != "third_party/rgo/v0_60_0/base" { - t.Fatalf("RulesGoStripPrefix=%q", prefix) - } -} - -func TestRulesGoSelectionForStripPrefixFindsBaseVariant(t *testing.T) { - upstream, variant, err := RulesGoSelectionForStripPrefix("third_party/rgo/v0_60_0/base") - if err != nil { - t.Fatalf("RulesGoSelectionForStripPrefix error: %v", err) - } - if upstream != "v0_60_0" || variant != "base" { - t.Fatalf("selection=%s/%s, want v0_60_0/base", upstream, variant) - } -} - -func TestRulesGoSelectionForStripPrefixAcceptsLegacyV060BaseAlias(t *testing.T) { - upstream, variant, err := RulesGoSelectionForStripPrefix("third_party/rules_go_orchestrion_base") - if err != nil { - t.Fatalf("RulesGoSelectionForStripPrefix error: %v", err) - } - if upstream != "v0_60_0" || variant != "base" { - t.Fatalf("selection=%s/%s, want v0_60_0/base", upstream, variant) - } -} - -func TestRulesGoSelectionForStripPrefixRejectsLegacyCompleteAlias(t *testing.T) { - _, _, err := RulesGoSelectionForStripPrefix("third_party/rules_go_orchestrion_complete") - if err == nil || !strings.Contains(err.Error(), removedCompleteVariantError) { - t.Fatalf("RulesGoSelectionForStripPrefix error=%v, want complete variant rejection", err) - } -} - -func TestResolveRejectsCompleteVariant(t *testing.T) { - _, err := Resolve(context.Background(), Options{ - WorkspaceDir: variantWorkspace(t), - Commit: strings.Repeat("a", 40), - Variant: "complete", - FetchArchive: archiveFromString("archive"), - }) - if err == nil || !strings.Contains(err.Error(), `rules_go_variant "complete" is no longer supported. Use "base".`) { - t.Fatalf("Resolve error=%v, want complete variant rejection", err) - } -} - -func TestResolveValidatesVariantDirWhenRequested(t *testing.T) { - dir := t.TempDir() - runGit(t, dir, "init", ".") - runGit(t, dir, "config", "user.email", "test@example.com") - runGit(t, dir, "config", "user.name", "Test User") - if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte("main\n"), 0o644); err != nil { - t.Fatalf("write README: %v", err) - } - runGit(t, dir, "add", "README.md") - runGit(t, dir, "commit", "-m", "main") - commit := strings.TrimSpace(runGit(t, dir, "rev-parse", "HEAD")) - - _, err := Resolve(context.Background(), Options{ - WorkspaceDir: dir, - Commit: commit, - Variant: "base", - ValidateVariantDir: true, - FetchArchive: archiveFromString("archive"), - }) - if err == nil || !strings.Contains(err.Error(), "does not exist") { - t.Fatalf("Resolve error=%v, want missing variant dir rejection", err) - } -} - -func TestResolveCanSkipVariantDirForConsumerBootstrap(t *testing.T) { - dir := t.TempDir() - runGit(t, dir, "init", ".") - runGit(t, dir, "config", "user.email", "test@example.com") - runGit(t, dir, "config", "user.name", "Test User") - if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte("main\n"), 0o644); err != nil { - t.Fatalf("write README: %v", err) - } - runGit(t, dir, "add", "README.md") - runGit(t, dir, "commit", "-m", "main") - commit := strings.TrimSpace(runGit(t, dir, "rev-parse", "HEAD")) - - if _, err := Resolve(context.Background(), Options{ - WorkspaceDir: dir, - Commit: commit, - Variant: "base", - FetchArchive: archiveFromString("archive"), - }); err != nil { - t.Fatalf("Resolve should not require local variant dirs when ValidateVariantDir is false: %v", err) - } } func TestSummaryContainsCurrentCommitOnly(t *testing.T) { commit := strings.Repeat("a", 40) staleCommit := strings.Repeat("b", 40) got := FormatMarkdownSummary(Pins{ - RTOCommit: commit, - RTORemote: DefaultRemote, - RTOArchiveURL: "https://codeload.github.com/DataDog/rules_test_optimization/tar.gz/" + commit, - RTOArchiveSHA256: strings.Repeat("1", 64), - RTOArchivePrefix: "rules_test_optimization-" + commit, - RTOArchiveType: DefaultArchiveType, - Variant: "base", - RulesGoStripPrefix: "third_party/rgo/v0_60_0/base", - DDTraceGoVersion: DefaultDDTraceGoVersion, - OrchestrionVersion: DefaultOrchestrionVersion, + RTOCommit: commit, + RTORemote: DefaultRemote, + RTOArchiveURL: "https://codeload.github.com/DataDog/rules_test_optimization/tar.gz/" + commit, + RTOArchiveSHA256: strings.Repeat("1", 64), + RTOArchivePrefix: "rules_test_optimization-" + commit, + RTOArchiveType: DefaultArchiveType, }) if !strings.Contains(got, commit) { t.Fatalf("summary missing current commit:\n%s", got) @@ -308,15 +146,6 @@ func TestSummaryContainsCurrentCommitOnly(t *testing.T) { } } -func variantWorkspace(t *testing.T) string { - t.Helper() - dir := t.TempDir() - if err := os.MkdirAll(filepath.Join(dir, "third_party", "rgo", "v0_60_0", "base"), 0o755); err != nil { - t.Fatalf("create variant dir: %v", err) - } - return dir -} - func archiveFromString(body string) ArchiveFetcher { return func(context.Context, string) (io.ReadCloser, error) { return io.NopCloser(bytes.NewBufferString(body)), nil @@ -358,18 +187,14 @@ func TestCodeloadURLRejectsNonGitHubRemote(t *testing.T) { } } -func TestFormatShellIncludesBaseTuple(t *testing.T) { +func TestFormatShellIncludesPublishedTuple(t *testing.T) { pins := Pins{ - RTOCommit: strings.Repeat("a", 40), - RTORemote: DefaultRemote, - RTOArchiveURL: "https://example.test/archive.tar.gz", - RTOArchiveSHA256: strings.Repeat("1", 64), - RTOArchivePrefix: "rules_test_optimization-" + strings.Repeat("a", 40), - RTOArchiveType: DefaultArchiveType, - Variant: "base", - RulesGoStripPrefix: "third_party/rgo/v0_60_0/base", - DDTraceGoVersion: DefaultDDTraceGoVersion, - OrchestrionVersion: DefaultOrchestrionVersion, + RTOCommit: strings.Repeat("a", 40), + RTORemote: DefaultRemote, + RTOArchiveURL: "https://example.test/archive.tar.gz", + RTOArchiveSHA256: strings.Repeat("1", 64), + RTOArchivePrefix: "rules_test_optimization-" + strings.Repeat("a", 40), + RTOArchiveType: DefaultArchiveType, } got := FormatShell(pins) for _, want := range []string{ @@ -378,10 +203,7 @@ func TestFormatShellIncludesBaseTuple(t *testing.T) { "RTO_ARCHIVE_URL=", "RTO_ARCHIVE_SHA256=", "RTO_ARCHIVE_PREFIX=", - "RULES_GO_UPSTREAM=", - "RULES_GO_VARIANT=", - "DD_TRACE_GO_VERSION=", - "ORCHESTRION_VERSION=", + "RTO_ARCHIVE_TYPE=", } { if !strings.Contains(got, want) { t.Fatalf("FormatShell missing %q:\n%s", want, got) @@ -391,17 +213,12 @@ func TestFormatShellIncludesBaseTuple(t *testing.T) { func ExampleFormatShell() { fmt.Print(FormatShell(Pins{ - RTOCommit: strings.Repeat("a", 40), - RTORemote: DefaultRemote, - RTOArchiveURL: "https://codeload.github.com/DataDog/rules_test_optimization/tar.gz/" + strings.Repeat("a", 40), - RTOArchiveSHA256: strings.Repeat("1", 64), - RTOArchivePrefix: "rules_test_optimization-" + strings.Repeat("a", 40), - RTOArchiveType: DefaultArchiveType, - RulesGoUpstream: DefaultRulesGoUpstream, - Variant: "base", - RulesGoStripPrefix: "third_party/rgo/v0_60_0/base", - DDTraceGoVersion: DefaultDDTraceGoVersion, - OrchestrionVersion: DefaultOrchestrionVersion, + RTOCommit: strings.Repeat("a", 40), + RTORemote: DefaultRemote, + RTOArchiveURL: "https://codeload.github.com/DataDog/rules_test_optimization/tar.gz/" + strings.Repeat("a", 40), + RTOArchiveSHA256: strings.Repeat("1", 64), + RTOArchivePrefix: "rules_test_optimization-" + strings.Repeat("a", 40), + RTOArchiveType: DefaultArchiveType, })) // Output: // RTO_COMMIT="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" @@ -410,9 +227,4 @@ func ExampleFormatShell() { // RTO_ARCHIVE_SHA256="1111111111111111111111111111111111111111111111111111111111111111" // RTO_ARCHIVE_PREFIX="rules_test_optimization-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" // RTO_ARCHIVE_TYPE="tar.gz" - // RULES_GO_UPSTREAM="v0_60_0" - // RULES_GO_VARIANT="base" - // RULES_GO_STRIP_PREFIX="third_party/rgo/v0_60_0/base" - // DD_TRACE_GO_VERSION="v2.9.1" - // ORCHESTRION_VERSION="v1.12.0" } diff --git a/modules/go/tools/onboardingpins/rules_go_forks_gen.go b/modules/go/tools/onboardingpins/rules_go_forks_gen.go deleted file mode 100644 index e2eabc6b..00000000 --- a/modules/go/tools/onboardingpins/rules_go_forks_gen.go +++ /dev/null @@ -1,20 +0,0 @@ -// Code generated by tools/dev/generate_rules_go_fork_maps.py; DO NOT EDIT. - -package onboardingpins - -const DefaultRulesGoUpstream = "v0_60_0" - -var rulesGoForkStripPrefixes = map[string]map[string]string{ - "v0_60_0": { - "base": "third_party/rgo/v0_60_0/base", - }, - "v0_61_1": { - "base": "third_party/rgo/v0_61_1/base", - }, - "v0_62_0": { - "base": "third_party/rgo/v0_62_0/base", - }, - "v0_63_0": { - "base": "third_party/rgo/v0_63_0/base", - }, -} diff --git a/modules/go/tools/registration/BUILD.bazel b/modules/go/tools/registration/BUILD.bazel new file mode 100644 index 00000000..aea6e9b9 --- /dev/null +++ b/modules/go/tools/registration/BUILD.bazel @@ -0,0 +1,27 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under +# the Apache 2.0 License. +# +# This product includes software developed at Datadog +# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test") + +go_library( + name = "registration_lib", + srcs = ["main.go"], + importpath = "github.com/DataDog/rules_test_optimization/modules/go/tools/registration", + visibility = ["//visibility:private"], +) + +# Writes the registration file of instrumented Go tests. +go_binary( + name = "registration", + embed = [":registration_lib"], + visibility = ["//visibility:public"], +) + +go_test( + name = "registration_test", + srcs = ["main_test.go"], + embed = [":registration_lib"], +) diff --git a/modules/go/tools/registration/main.go b/modules/go/tools/registration/main.go new file mode 100644 index 00000000..d218d61d --- /dev/null +++ b/modules/go/tools/registration/main.go @@ -0,0 +1,160 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under +// the Apache 2.0 License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +// Command registration writes the file that registers a Go test package with +// Mini, the runtime of ddtest instrument: the file that ddtest instrument +// writes as zz_dd_ci_visibility_test.go. It goes in the test's external +// package when one of its sources declares it, and in its own package +// otherwise. +// +// Usage: registration -o OUT SRC... +package main + +import ( + "errors" + "flag" + "fmt" + "go/ast" + "go/build/constraint" + "go/parser" + "go/token" + "os" + "sort" + "strings" +) + +const runtimeImport = "github.com/DataDog/ddtest/ddto/testopt" + +func main() { + out := flag.String("o", "", "the registration file to write") + flag.Parse() + name, err := testPackage(flag.Args()) + if err == nil && *out == "" { + err = errors.New("-o is required") + } + if err == nil { + err = os.WriteFile(*out, []byte(source(name)), 0o644) + } + if err != nil { + fmt.Fprintln(os.Stderr, "registration:", err) + os.Exit(1) + } +} + +func source(name string) string { + return "package " + name + "\nimport __dd_ci_runtime \"" + runtimeImport + "\"\nfunc init() { __dd_ci_runtime.RegisterTestPackage() }\n" +} + +// testPackage returns the package that the registration joins: the external +// test package when a source declares one, the package under test otherwise. +// Sources without a build constraint decide; constrained ones only decide +// when no source is unconstrained, since the configuration may exclude them. +func testPackage(files []string) (string, error) { + unconstrained := map[string]bool{} + constrained := map[string]bool{} + for _, file := range files { + if !strings.HasSuffix(file, ".go") { + continue + } + parsed, err := parser.ParseFile(token.NewFileSet(), file, nil, parser.PackageClauseOnly|parser.ParseComments) + if err != nil { + return "", err + } + expr := buildConstraint(parsed.Comments) + switch { + case expr == nil: + unconstrained[parsed.Name.Name] = true + case satisfiable(expr): + constrained[parsed.Name.Name] = true + } + } + if len(unconstrained) > 0 { + return choose(unconstrained) + } + return choose(constrained) +} + +func choose(names map[string]bool) (string, error) { + var external, internal []string + for name := range names { + if strings.HasSuffix(name, "_test") { + external = append(external, name) + } else { + internal = append(internal, name) + } + } + sort.Strings(external) + sort.Strings(internal) + switch { + case len(external) == 1: + return external[0], nil + case len(external) == 0 && len(internal) == 1: + return internal[0], nil + case len(names) == 0: + return "", errors.New("no Go sources") + } + return "", fmt.Errorf("sources declare packages %s", strings.Join(append(internal, external...), ", ")) +} + +// buildConstraint returns the file's //go:build expression, or nil. +func buildConstraint(groups []*ast.CommentGroup) constraint.Expr { + for _, group := range groups { + for _, comment := range group.List { + if !constraint.IsGoBuild(comment.Text) { + continue + } + expr, err := constraint.Parse(comment.Text) + if err != nil { + return nil + } + return expr + } + } + return nil +} + +// satisfiable reports whether some set of tags other than ignore satisfies +// expr: a constraint such as //go:build ignore on generators kept next to +// tests holds on no platform, since the go command never sets ignore, while +// one such as !windows holds on some platform, and rules_go applies it. A constraint with too many tags to enumerate counts as satisfiable. +func satisfiable(expr constraint.Expr) bool { + var tags []string + collect(expr, map[string]bool{"ignore": true}, &tags) + if len(tags) > 16 { + return true + } + for set := 0; set < 1<