From 574b745ef0b58b6f51da435e87b0314a26997bed Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 12 Sep 2026 17:30:57 -0700 Subject: [PATCH 1/7] Aggregate the seed Makefile's gates over an SDK_LANGUAGES profile --- seed/Makefile.tmpl | 76 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/seed/Makefile.tmpl b/seed/Makefile.tmpl index 9ace1bb..7c079a8 100644 --- a/seed/Makefile.tmpl +++ b/seed/Makefile.tmpl @@ -5,6 +5,34 @@ # Default: run all checks all: check +#--- +# Language profile +#--- + +# The languages this SDK ships, by Makefile prefix (go ts rb swift kt rs). Set at +# instantiation (prompts/seed-sdk.md). The aggregates -- check, check-mvp, +# check-full, generate-services, conformance, clean -- range over it, so a +# narrower profile never gates on a language it did not initialize. Every +# per-language target stays defined and still fails on its own if the language +# is absent; the profile changes what the aggregates ask for, not what exists. +SDK_LANGUAGES := go ts rb swift kt rs + +LANG_CHECKS := $(addsuffix -check,$(SDK_LANGUAGES)) + +# Provenance is embedded in the Go package and check-mvp is the Go inner loop; +# both drop out when the profile omits Go. +HAS_GO := $(filter go,$(SDK_LANGUAGES)) + +# Conformance runners are named by language, not prefix. Swift has no entry: the +# aggregate does not run its runner today (CI runs it on a macOS matrix leg), so +# the full profile keeps running exactly go, typescript, ruby, kotlin, rust. +CONFORMANCE_RUNNER_go := go +CONFORMANCE_RUNNER_ts := typescript +CONFORMANCE_RUNNER_rb := ruby +CONFORMANCE_RUNNER_kt := kotlin +CONFORMANCE_RUNNER_rs := rust +CONFORMANCE_RUNNERS := $(foreach p,$(SDK_LANGUAGES),$(CONFORMANCE_RUNNER_$(p))) + #--- # Smithy targets #--- @@ -395,14 +423,24 @@ rs-clean: # Conformance #--- -.PHONY: conformance-build conformance-go conformance-kotlin conformance-typescript conformance-ruby conformance-swift conformance-rust conformance-runner-tests-rust conformance +.PHONY: conformance-build conformance-build-go conformance-build-typescript conformance-build-kotlin conformance-go conformance-kotlin conformance-typescript conformance-ruby conformance-swift conformance-rust conformance-runner-tests-rust conformance -conformance-build: - @echo "==> Building conformance runners..." +conformance-build-go: + @echo "==> Building Go conformance runner..." cd conformance/runner/go && go build -o conformance-runner . + +conformance-build-typescript: + @echo "==> Installing TypeScript conformance runner..." cd conformance/runner/typescript && npm ci + +conformance-build-kotlin: + @echo "==> Building Kotlin conformance runner..." cd kotlin && ./gradlew :conformance:build +# The runners with a build step of their own, for the profile's languages. +conformance-build: $(addprefix conformance-build-,$(filter go typescript kotlin,$(CONFORMANCE_RUNNERS))) + @echo "==> Conformance runners built" + conformance-go: conformance-build @echo "==> Running Go conformance..." cd conformance/runner/go && ./conformance-runner ../../tests/ @@ -432,18 +470,26 @@ conformance-runner-tests-rust: @echo "==> Running Rust conformance runner unit tests..." cd conformance/runner/rust && cargo test --locked -conformance: conformance-go conformance-typescript conformance-ruby conformance-kotlin conformance-rust +conformance: $(addprefix conformance-,$(CONFORMANCE_RUNNERS)) @echo "==> All conformance tests passed" #--- -# Service generation (all languages) +# Service generation (the profile's languages) #--- -.PHONY: generate-services +.PHONY: generate-services generate-services-check -generate-services: go-generate-services ts-generate-services rb-generate-services swift-generate kt-generate-services rs-generate-services +generate-services: $(addsuffix -generate-services,$(SDK_LANGUAGES)) @echo "==> All services regenerated" +# Non-mutating: every generate target in the profile must invoke a generator +# that exists and expand to real work, through Swift's sub-Makefile included. +# Without --dry-run the script also runs each target, which is the bootstrap +# checkpoint (prompts/seed-sdk.md). +generate-services-check: + @echo "==> Checking generate targets..." + @./scripts/check-generate-targets.sh --dry-run $(SDK_LANGUAGES) + #--- # Version & Release #--- @@ -518,21 +564,24 @@ audit-check: .PHONY: check-mvp check-full -check-mvp: smithy-check behavior-model-check url-routes-check sync-api-version-check go-check +check-mvp: smithy-check behavior-model-check url-routes-check sync-api-version-check $(if $(HAS_GO),go-check) @echo "==> MVP checks passed" -check-full: check-mvp provenance-check audit-check ts-check rb-check swift-check kt-check rs-check conformance +check-full: check-mvp $(if $(HAS_GO),provenance-check) audit-check $(filter-out go-check,$(LANG_CHECKS)) conformance @echo "==> Full checks passed" -check: smithy-check behavior-model-check url-routes-check sync-api-version-check provenance-check audit-check go-check ts-check rb-check swift-check kt-check rs-check conformance +check: smithy-check behavior-model-check url-routes-check sync-api-version-check $(if $(HAS_GO),provenance-check) audit-check generate-services-check $(LANG_CHECKS) conformance @echo "==> All checks passed" -clean: smithy-clean go-clean ts-clean rb-clean swift-clean kt-clean rs-clean +clean: smithy-clean $(addsuffix -clean,$(SDK_LANGUAGES)) @echo "==> Cleaned" help: @echo "{{.AppTitle}} SDK Makefile" @echo "" + @echo "Profile (SDK_LANGUAGES): $(SDK_LANGUAGES)" + @echo " check, generate-services, conformance and clean range over these prefixes" + @echo "" @echo "Smithy:" @echo " smithy-validate Validate Smithy spec syntax" @echo " smithy-mapper Build custom OpenAPI mapper JAR" @@ -602,7 +651,7 @@ help: @echo " rs-clean Remove Rust build artifacts" @echo "" @echo "Conformance:" - @echo " conformance Run all conformance tests" + @echo " conformance Run conformance tests for the profile's languages" @echo " conformance-go Run Go conformance tests" @echo " conformance-typescript Run TypeScript conformance tests" @echo " conformance-ruby Run Ruby conformance tests" @@ -617,7 +666,8 @@ help: @echo " sync-status Show upstream changes since last spec sync" @echo "" @echo "Generation:" - @echo " generate-services Regenerate services for all languages" + @echo " generate-services Regenerate services for every language in the profile" + @echo " generate-services-check Verify each generate target in the profile invokes a real generator" @echo "" @echo "Version & Release:" @echo " bump VERSION=x.y.z Bump SDK version across all languages" From 3b096c0de166e1c34bb2df6c8a23e4a86017f568 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 12 Sep 2026 17:30:57 -0700 Subject: [PATCH 2/7] Check that every generate target in the profile invokes a real generator --- .github/workflows/ci.yml | 9 ++- hack/test/check-generate-targets.bats | 97 +++++++++++++++++++++++ seed/scripts/check-generate-targets.sh | 105 +++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 hack/test/check-generate-targets.bats create mode 100755 seed/scripts/check-generate-targets.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39a5784..a726fcf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,10 +53,11 @@ jobs: go-version: stable cache: false - - name: Install actionlint and zizmor + - name: Install actionlint, zizmor and bats run: | go install "github.com/rhysd/actionlint/cmd/actionlint@$ACTIONLINT_VERSION" pipx install "zizmor==$ZIZMOR_VERSION" + sudo apt-get install -y --no-install-recommends bats - name: Render seed/ with fixture values run: hack/render-seed.sh .rendered @@ -74,6 +75,12 @@ jobs: - name: shellcheck (rendered scripts) run: find .rendered/scripts -type f -name '*.sh' -print0 | xargs -0 -r shellcheck + # The generate-target coverage check, pinned on its own renders: the pristine + # seed fails for all six languages, stubbed generators pass, a target that + # exits 0 having done nothing fails. + - name: bats (generate-target coverage check) + run: bats hack/test + rust-scaffold: # seed/rust/ is the one scaffold that is a complete crate (AGENTS.md), so it is # the one that can be compiled before it merges: the rendered workspace must diff --git a/hack/test/check-generate-targets.bats b/hack/test/check-generate-targets.bats new file mode 100644 index 0000000..9e69def --- /dev/null +++ b/hack/test/check-generate-targets.bats @@ -0,0 +1,97 @@ +#!/usr/bin/env bats +# Pins scripts/check-generate-targets.sh against a rendered seed: the pristine seed +# must fail for every language (no generator ships in the seed), stubbed generator +# artifacts must pass, and a target that exits 0 having done nothing must fail. +# Only `make` is needed: the dry run never executes a generator, and the run-mode +# cases use a Swift sub-Makefile whose `generate` recipe is a shell one-liner. + +setup() { + tmp="$(mktemp -d)" + "$BATS_TEST_DIRNAME/../render-seed.sh" "$tmp" > /dev/null + cd "$tmp" || exit 1 +} + +teardown() { + rm -rf "$tmp" +} + +# The artifacts the rendered Makefile's recipes invoke, as the scaffold step leaves +# them; package-lock.json because ts-generate-services depends on the install stamp. +stub_generators() { + mkdir -p go/cmd/generate-services typescript/scripts ruby/scripts kotlin/generator rust/generator swift + touch go/cmd/generate-services/main.go typescript/scripts/generate-services.ts \ + typescript/package-lock.json ruby/scripts/generate-services.rb \ + kotlin/generator/build.gradle.kts rust/generator/Cargo.toml + printf 'generate:\n\tswift run FizzyGenerator --output Sources/Fizzy/Generated\n' > swift/Makefile +} + +set_profile() { + perl -pi -e "s/^SDK_LANGUAGES := .*/SDK_LANGUAGES := $1/" Makefile +} + +@test "pristine seed: every generate target in the profile is unwired" { + run scripts/check-generate-targets.sh --dry-run + [ "$status" -eq 1 ] + [[ "$output" == *"go/cmd/generate-services missing"* ]] + [[ "$output" == *"swift-generate-services does not expand"* ]] + [[ "$output" == *"ERROR: generate targets not wired: go ts rb swift kt rs"* ]] +} + +@test "make generate-services-check fails on the pristine seed" { + run make generate-services-check + [ "$status" -ne 0 ] + [[ "$output" == *"ERROR: generate targets not wired: go ts rb swift kt rs"* ]] +} + +@test "stubbed generators pass the dry run, Swift through its sub-Makefile" { + stub_generators + run scripts/check-generate-targets.sh --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"swift run FizzyGenerator"* ]] + [[ "$output" == *"generate targets wired: go ts rb swift kt rs"* ]] +} + +@test "a sub-Makefile generate target with no recipe is vacuous" { + stub_generators + printf 'generate:\n' > swift/Makefile + run scripts/check-generate-targets.sh --dry-run + [ "$status" -eq 1 ] + [[ "$output" == *"swift-generate-services expands to no command"* ]] + [[ "$output" == *"ERROR: generate targets not wired: swift"* ]] +} + +@test "a root alias that lost its prerequisite is vacuous" { + stub_generators + perl -pi -e 's/^swift-generate-services: swift-generate$/swift-generate-services:/' Makefile + run scripts/check-generate-targets.sh --dry-run swift + [ "$status" -eq 1 ] + [[ "$output" == *"swift-generate-services expands to no command"* ]] +} + +@test "a narrowed profile is checked for its own languages only" { + set_profile "go rs" + run scripts/check-generate-targets.sh --dry-run + [ "$status" -eq 1 ] + [[ "$output" == *"ERROR: generate targets not wired: go rs"* ]] + [[ "$output" != *"ts-generate-services"* ]] +} + +@test "run mode executes the target and reports its exit status" { + stub_generators + printf 'generate:\n\ttouch generated.marker\n' > swift/Makefile + run scripts/check-generate-targets.sh swift + [ "$status" -eq 0 ] + [ -f swift/generated.marker ] + [[ "$output" == *"make swift-generate-services exited 0"* ]] + + printf 'generate:\n\texit 3\n' > swift/Makefile + run scripts/check-generate-targets.sh swift + [ "$status" -eq 1 ] + [[ "$output" == *"make swift-generate-services exited non-zero"* ]] +} + +@test "an unknown prefix is rejected" { + run scripts/check-generate-targets.sh --dry-run py + [ "$status" -eq 1 ] + [[ "$output" == *"unknown language prefix 'py'"* ]] +} diff --git a/seed/scripts/check-generate-targets.sh b/seed/scripts/check-generate-targets.sh new file mode 100755 index 0000000..e25be81 --- /dev/null +++ b/seed/scripts/check-generate-targets.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# Proves each -generate-services target is wired to a real generator rather +# than exiting 0 vacuously. For every prefix it asserts that: +# 1. the generator artifact the root Makefile's recipe invokes exists; +# 2. the target expands to at least one command that is not an echo, through any +# sub-Makefile delegation (`make -n` follows $(MAKE) -C), so an alias whose +# prerequisite was tidied away, or a sub-Makefile `generate` with no recipe, +# fails here; +# 3. unless --dry-run, `make -generate-services` runs and exits 0. +# Prefixes default to SDK_LANGUAGES in the Makefile. `make generate-services-check` +# runs the dry form as part of `make check`; the full form is the bootstrap +# checkpoint after each language is scaffolded. +# +# Usage: scripts/check-generate-targets.sh [--dry-run] [prefix...] +set -euo pipefail + +dry_run=false +if [ "${1:-}" = "--dry-run" ]; then + dry_run=true + shift +fi + +cd "$(dirname "$0")/.." +make="${MAKE:-make}" + +if [ $# -gt 0 ]; then + prefixes=("$@") +else + read -ra prefixes <<< "$(sed -n 's/^SDK_LANGUAGES *:= *//p' Makefile)" + if [ ${#prefixes[@]} -eq 0 ]; then + echo "ERROR: no SDK_LANGUAGES line in Makefile; pass prefixes explicitly" >&2 + exit 2 + fi +fi + +# The artifact each root recipe invokes. Swift's recipe is `$(MAKE) -C swift +# generate`, so its artifact is the sub-Makefile and check 2 covers the target in it. +artifact_for() { + case "$1" in + go) echo "go/cmd/generate-services" ;; + ts) echo "typescript/scripts/generate-services.ts" ;; + rb) echo "ruby/scripts/generate-services.rb" ;; + swift) echo "swift/Makefile" ;; + kt) echo "kotlin/generator/build.gradle.kts" ;; + rs) echo "rust/generator/Cargo.toml" ;; + *) return 1 ;; + esac +} + +# What `make -n` would run, minus echoes, make's own recursion lines and its +# "Nothing to be done" notices. Anything left is generator work. +work_lines() { + grep -vE '^(echo |(.*/)?make(\[[0-9]+\])?[ :])' || true +} + +failed=() +for prefix in "${prefixes[@]}"; do + target="$prefix-generate-services" + ok=true + echo "==> $target" + + if ! artifact=$(artifact_for "$prefix"); then + echo " FAIL: unknown language prefix '$prefix' (expected one of go ts rb swift kt rs)" + failed+=("$prefix") + continue + fi + if [ -e "$artifact" ]; then + echo " ok: $artifact present" + else + echo " FAIL: $artifact missing; the recipe has no generator to run (see CONTRIBUTING.md)" + ok=false + fi + + if plan=$("$make" -n --no-print-directory "$target" 2>&1); then + work=$(printf '%s\n' "$plan" | work_lines) + if [ -n "$work" ]; then + echo " ok: expands to work:" + printf '%s\n' "$work" | sed 's/^/ /' + else + echo " FAIL: $target expands to no command; it exits 0 having done nothing" + ok=false + fi + else + echo " FAIL: $target does not expand:" + printf '%s\n' "$plan" | sed 's/^/ /' + ok=false + fi + + if [ "$ok" = true ] && [ "$dry_run" = false ]; then + if "$make" "$target"; then + echo " ok: make $target exited 0" + else + echo " FAIL: make $target exited non-zero" + ok=false + fi + fi + + [ "$ok" = true ] || failed+=("$prefix") +done + +if [ ${#failed[@]} -gt 0 ]; then + echo "ERROR: generate targets not wired: ${failed[*]}" + exit 1 +fi +echo " generate targets wired: ${prefixes[*]}" From 8c937a52ea95b7077ebac56a3aa2f7f8da9ffe3e Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 12 Sep 2026 17:30:57 -0700 Subject: [PATCH 3/7] Document the language profile and the generate-target check --- AGENTS.md | 26 ++++++++++++++++++--- MAKEFILE-CONVENTION.md | 40 +++++++++++++++++++++++++++++--- prompts/seed-sdk.md | 49 +++++++++++++++++++++++++-------------- seed/AGENTS.md.tmpl | 21 ++++++++--------- seed/CONTRIBUTING.md.tmpl | 24 ++++++++++--------- 5 files changed, 115 insertions(+), 45 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c56ef4e..fffd7c7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,13 +69,33 @@ inline, which is why the work sits under the unsuffixed name. `seed/Makefile.tmpl` defines the vocabulary. `actions/service-drift/action.yml`, `prompts/seed-sdk.md`, `prompts/close-gap.md`, `seed/AGENTS.md.tmpl`, `seed/CONTRIBUTING.md.tmpl`, `seed/README.md.tmpl`, -`seed/.github/workflows/release-kotlin.yml.tmpl` and -`seed/scripts/check-rust-service-drift.sh.tmpl` all restate it. Treat that list as a -starting point and grep for the target name rather than trusting it. Rename a target and every one of them has to move in the same commit — they have +`seed/.github/workflows/release-kotlin.yml.tmpl`, +`seed/scripts/check-generate-targets.sh` (which also names the generator artifact each +recipe invokes) and `seed/scripts/check-rust-service-drift.sh.tmpl` all restate it. +Treat that list as a starting point and grep for the target name rather than trusting +it. Rename a target and every one of them has to move in the same commit — they have drifted apart before. The Kotlin workflow is easy to miss: it names `kt-generate-services` inside a drift-check error message, so a stale name there ships into every SDK generated afterwards and tells users to run a target that is gone. +## The language profile is `SDK_LANGUAGES`, and the generate targets are checked + +`SDK_LANGUAGES` at the top of `seed/Makefile.tmpl` is the only place a seeded SDK's +language set lives; `check`, `generate-services`, `conformance` and `clean` range over +it (`MAKEFILE-CONVENTION.md` § Language Profile). Adding a language to the seed means +adding its prefix to that default, a `CONFORMANCE_RUNNER_` mapping if its runner +belongs in the aggregate, its generator artifact to `seed/scripts/check-generate-targets.sh`, +and a matrix entry in `seed/.github/workflows/test.yml.tmpl`. Directory presence is not +the signal: an aggregate that reads the tree would hide a scaffold step that failed. + +The seed ships no generator for any language, so on a pristine render every +`-generate-services` target fails, and nothing here can prove a seeded repo's +generators work. What `hack/test/check-generate-targets.bats` pins instead is the +detector: `scripts/check-generate-targets.sh` must fail on the pristine render for all +six prefixes, pass once the artifacts each recipe invokes exist (Swift's through its +sub-Makefile), and fail on a target that exits 0 having done nothing. Rename a generate +target or move a generator and that test is the first thing to break. + ## `seed/rust/` is a crate, not a fragment The other language scaffolds are fragments that compile only once the instantiator has diff --git a/MAKEFILE-CONVENTION.md b/MAKEFILE-CONVENTION.md index 73f2a05..72f321c 100644 --- a/MAKEFILE-CONVENTION.md +++ b/MAKEFILE-CONVENTION.md @@ -6,7 +6,7 @@ Required Makefile targets and release architecture for SDK repositories. | Target | Default? | Contract | |--------|----------|----------| -| `check` | **Yes** | CI gate. Comprehensive: smithy-check + behavior-model-check + url-routes-check + provenance-check + sync-api-version-check + {lang}-check + conformance + audit-check. Not fast — use `check-mvp` or `{lang}-check` for inner-loop. | +| `check` | **Yes** | CI gate. Comprehensive: smithy-check + behavior-model-check + url-routes-check + provenance-check + sync-api-version-check + audit-check + generate-services-check + {lang}-check + conformance, with {lang}-check and conformance ranging over `SDK_LANGUAGES` (see Language Profile). Not fast — use `check-mvp` or `{lang}-check` for inner-loop. | | `check-mvp` | | Fast iteration target: smithy-check + behavior-model-check + url-routes-check + sync-api-version-check + go-check. Skips conformance. | | `smithy-mapper` | | Build Smithy plugin to local Maven | | `smithy-build` | | Build OpenAPI from Smithy. Prerequisites: `behavior-model smithy-mapper`. Post-step: `sync-api-version`. | @@ -23,11 +23,12 @@ Required Makefile targets and release architecture for SDK repositories. | `{lang}-check` | | All checks for one language. Must include lint + test (+ typecheck where applicable). | | `{lang}-check-drift` | | Verify generated services match spec for one language | | `{lang}-generate-services` | | Generate service classes from OpenAPI | -| `conformance` | | All cross-language conformance tests | +| `conformance` | | Conformance runners for the languages in `SDK_LANGUAGES` | | `audit-check` | | Validate rubric-audit.json: exists, must-pass manual criteria pass, date within 30 days | | `bump VERSION=x.y.z` | | Atomic version bump across all languages | | `release VERSION=x.y.z` | | Sole release authority (see below) | -| `generate-services` | | Aggregate: regenerate services for all languages | +| `generate-services` | | Aggregate: `{lang}-generate-services` for every language in `SDK_LANGUAGES` | +| `generate-services-check` | | Non-mutating, part of `check`: every `{lang}-generate-services` in `SDK_LANGUAGES` invokes a generator artifact that exists and expands to real work, through sub-Makefile delegation (`scripts/check-generate-targets.sh --dry-run`) | ## Naming Conventions @@ -58,6 +59,39 @@ Required Makefile targets and release architecture for SDK repositories. | Kotlin | test (via `./gradlew :{app}-sdk:check`) | | Rust | fmt-check + clippy + test + doc + deny | +## Language Profile + +The languages an SDK ships are one variable at the top of the Makefile, by prefix: + +```makefile +SDK_LANGUAGES := go ts rb swift kt rs +``` + +It is set once, at instantiation, to the profile chosen in `prompts/seed-sdk.md`; the +full set is the seed's default. The aggregates derive from it and nothing else: + +- `check`, `check-full`: `{lang}-check` for each listed prefix +- `check-mvp`: adds `go-check` only when `go` is listed +- `provenance-check` (Go-embedded provenance): in `check`/`check-full` only when `go` is listed +- `generate-services`, `generate-services-check`, `clean`: one target per listed prefix +- `conformance`, `conformance-build`: the runners for the listed prefixes, mapped by + `CONFORMANCE_RUNNER_` (`ts` → `conformance-typescript`, and so on). Swift has + no mapping: the aggregate does not run the Swift runner today; the seeded `test.yml` + runs it on a macOS matrix leg instead + +Two things the profile deliberately does not do. It does not remove or guard the +per-language targets: `make ts-check` on a profile without `ts` still exists and still +fails, loudly, on the missing tree. And it is not inferred from which directories exist: +a scaffold step that silently failed to create `go/` must fail `go-check`, not quietly +narrow the gate. `sync-api-version-check` and `scripts/sync-version.sh` are the one +exception, skipping a language whose file is absent, because a version constant that +does not exist has nothing to be out of sync with. + +Instantiating a narrower profile therefore means: set `SDK_LANGUAGES`, and trim the +`conformance` matrix in `.github/workflows/test.yml` to the same languages. `make +release`'s version-constant guards still name all six languages and are not yet +profile-aware. + ## Release Architecture One path, no ambiguity: diff --git a/prompts/seed-sdk.md b/prompts/seed-sdk.md index 1ccd479..caf721e 100644 --- a/prompts/seed-sdk.md +++ b/prompts/seed-sdk.md @@ -21,6 +21,12 @@ Decide which languages to include based on the target audience: The profile determines which rubric criteria apply (91 for full-sdk, 76 for single-language). Choose the minimal set that covers your deployment surface, then add languages later if needed. +The Makefile records the choice as `SDK_LANGUAGES`, a list of target prefixes +(`go ts rb swift kt rs` is the full set), and `make check`, `make generate-services`, +`make conformance` and `make clean` range over it; per-language targets for languages +outside the list still exist and still fail. Set it in Phase 3, and keep the +`conformance` matrix in `.github/workflows/test.yml` to the same languages in Phase 5. + ## Template Variable Reference All `.tmpl` files use Go template syntax. Replace these placeholders before renaming: @@ -70,52 +76,59 @@ The spec drives everything downstream. ### Phase 3: Build pipeline -1. `Makefile.tmpl` -> `Makefile` +1. `Makefile.tmpl` -> `Makefile`, then set `SDK_LANGUAGES` at the top to the prefixes of + the languages chosen above 2. `scripts/` -- copy build helper scripts **Checkpoint:** `make smithy-build` produces `openapi.json`. ### Phase 4: Per-language SDKs -Initialize each language in parallel -- they are independent of each other. +Initialize each language in parallel -- they are independent of each other. The seed +ships no generator for any language: each `-generate-services` recipe names an +artifact the scaffold step has to produce, and `scripts/check-generate-targets.sh +` runs the target only after asserting that artifact exists and the target +expands to real work, so a scaffold that quietly produced nothing fails there rather +than at the first spec change. #### Go 1. Copy `seed/go/` into `go/` 2. `cd go && go mod init {{.ModulePath}}` -3. Scaffold client, error types, service base -4. `make go-generate-services` +3. Scaffold client, error types, service base, and the generator at `go/cmd/generate-services/` +4. `scripts/check-generate-targets.sh go` (runs `make go-generate-services`) **Checkpoint:** `make go-check` passes (format + vet + test). #### TypeScript 1. Copy `seed/typescript/` into `typescript/` 2. `cd typescript && npm init --scope={{.NpmScope}}` -3. Scaffold client, error types, service base -4. `make ts-generate-services` +3. Scaffold client, error types, service base, and the generator at `typescript/scripts/generate-services.ts` +4. `scripts/check-generate-targets.sh ts` (runs `make ts-generate-services`) **Checkpoint:** `make ts-check` passes (tsc + lint + test). #### Ruby 1. Copy `seed/ruby/` into `ruby/` 2. `cd ruby && bundle init` -3. Scaffold client, error types, service base -4. `make rb-generate-services` +3. Scaffold client, error types, service base, and the generator at `ruby/scripts/generate-services.rb` +4. `scripts/check-generate-targets.sh rb` (runs `make rb-generate-services`) **Checkpoint:** `make rb-check` passes (rubocop + test). #### Swift 1. Copy `seed/swift/` into `swift/` 2. Initialize `Package.swift` -3. Scaffold client, error types, service base -4. `make swift-generate-services` +3. Scaffold client, error types, service base, and `swift/Makefile` with a `generate` + target that runs the generator -- the root `swift-generate-services` delegates to it +4. `scripts/check-generate-targets.sh swift` (runs `make swift-generate-services`) **Checkpoint:** `swift build && swift test` pass. #### Kotlin 1. Copy `seed/kotlin/` into `kotlin/` 2. Initialize `build.gradle.kts` -3. Scaffold client, error types, service base -4. `make kt-generate-services` +3. Scaffold client, error types, service base, and the generator at `kotlin/generator/` +4. `scripts/check-generate-targets.sh kt` (runs `make kt-generate-services`) **Checkpoint:** `./gradlew build` passes. @@ -129,7 +142,7 @@ Initialize each language in parallel -- they are independent of each other. passes `--locked`, so the lockfile must already know the generator: one generated before the workspace gained that member fails the next step with "the lock file needs to be updated but --locked was passed". -4. `make rs-generate-services` +4. `scripts/check-generate-targets.sh rs` (runs `make rs-generate-services`) **Checkpoint:** `make rs-check` passes (fmt + clippy + tests + docs + deny + drift + publish dry-run). Before the generator exists, `cd rust && cargo test && cargo publish @@ -139,7 +152,8 @@ publish dry-run). Before the generator exists, `cd rust && cargo test && cargo p 1. Copy `conformance/` from sdk/common (tests + schema) 2. Initialize per-language conformance runners -3. Copy `.github/` workflow templates +3. Copy `.github/` workflow templates; trim the `conformance` matrix in `test.yml` to + the languages in `SDK_LANGUAGES` 4. `rubric-audit.json` -- create initial audit with profile and date **Checkpoint:** `make conformance` runs (tests may fail -- that's the gap to close). @@ -147,7 +161,7 @@ publish dry-run). Before the generator exists, `cd rust && cargo test && cargo p ### Phase 6: Full verification ```bash -make check # smithy-check + all lang checks + conformance + audit-check +make check # smithy-check + generate-services-check + the profile's lang checks + conformance + audit-check ``` All checks must pass before the first commit to main. @@ -168,8 +182,9 @@ Run these per-language to confirm the SDK is functional end-to-end: Then cross-language: ```bash -make conformance # All conformance tests -make audit-check # rubric-audit.json exists, fresh, must-pass criteria met +scripts/check-generate-targets.sh # Every generate target in the profile runs, against a real generator +make conformance # Conformance tests for the profile's languages +make audit-check # rubric-audit.json exists, fresh, must-pass criteria met ``` Finally, run the `rubric-audit` skill to establish a baseline score and identify gaps to close. diff --git a/seed/AGENTS.md.tmpl b/seed/AGENTS.md.tmpl index 5c1d9db..568322b 100644 --- a/seed/AGENTS.md.tmpl +++ b/seed/AGENTS.md.tmpl @@ -25,18 +25,17 @@ Smithy spec -> OpenAPI -> Behavior Model -> Per-language generators -> SDK code 1. Edit the Smithy spec in `spec/model/` 2. Run `make smithy-build` to regenerate OpenAPI -3. Regenerate the service layer. Name the targets for the languages this SDK actually - ships: `make go-generate-services`, `make ts-generate-services`, - `make rb-generate-services`, `make kt-generate-services`, `make swift-generate-services`, - `make rs-generate-services`. - The aggregate `make generate-services` runs all six unconditionally, so it only - works on a full six-language repo -- on a narrower profile it fails on the first - language that was never initialized. +3. Regenerate the service layer: `make generate-services` runs + `-generate-services` for every language in `SDK_LANGUAGES` (the profile at + the top of the Makefile), or name one: `make go-generate-services`, + `make ts-generate-services`, `make rb-generate-services`, `make kt-generate-services`, + `make swift-generate-services`, `make rs-generate-services`. 4. Add/update tests 5. Run `make check` 6. Commit -`make check` has the same six-language assumption as the aggregate above: it depends on -`go-check ts-check rb-check swift-check kt-check rs-check conformance` unconditionally. On a -narrower profile, trim those dependencies when you instantiate the Makefile, or the gate -fails in a language the repo never initialized. +`make check` gates on the same profile: `-check` and the conformance runners for +the languages in `SDK_LANGUAGES`, nothing else. It also runs `generate-services-check`, +which fails if any generate target in the profile has no generator behind it or expands +to no work. To add a language, add its prefix to `SDK_LANGUAGES` and wire its generator; +`make generate-services-check` says what is still missing. diff --git a/seed/CONTRIBUTING.md.tmpl b/seed/CONTRIBUTING.md.tmpl index ece3fbd..f9affdd 100644 --- a/seed/CONTRIBUTING.md.tmpl +++ b/seed/CONTRIBUTING.md.tmpl @@ -22,21 +22,23 @@ 1. Add the operation to the Smithy spec in `spec/model/` 2. Run `make smithy-build` to regenerate OpenAPI -3. Regenerate the service layer. Name the targets for the languages this SDK actually - ships: `make go-generate-services`, `make ts-generate-services`, - `make rb-generate-services`, `make kt-generate-services`, `make swift-generate-services`, - `make rs-generate-services`. - The aggregate `make generate-services` runs all six unconditionally, so it only - works on a full six-language repo -- on a narrower profile it fails on the first - language that was never initialized. +3. Regenerate the service layer: `make generate-services` runs + `-generate-services` for every language in `SDK_LANGUAGES` (the profile at + the top of the Makefile), or name one: `make go-generate-services`, + `make ts-generate-services`, `make rb-generate-services`, `make kt-generate-services`, + `make swift-generate-services`, `make rs-generate-services`. 4. Add unit tests 5. Add conformance tests if the operation has behavioral requirements 6. Run `make check` -`make check` has the same six-language assumption as the aggregate above: it depends on -`go-check ts-check rb-check swift-check kt-check rs-check conformance` unconditionally. On a -narrower profile, trim those dependencies when you instantiate the Makefile, or the gate -fails in a language the repo never initialized. +`make check` gates on the same profile: `-check` and the conformance runners for +the languages in `SDK_LANGUAGES`, nothing else. It also runs `generate-services-check`, +which fails if any generate target in the profile has no generator behind it or expands +to no work (`make -n` through Swift's sub-Makefile). `scripts/check-generate-targets.sh` +without `--dry-run` runs each generate target as well; that is the check to run after +porting or changing a generator. To add a language, add its prefix to `SDK_LANGUAGES`, +add it to the `conformance` matrix in `.github/workflows/test.yml`, and wire its +generator; `make generate-services-check` says what is still missing. ## The Rust generator From 7b69881bdc731854a43ab99f53b8e7c7a863e67a Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 12 Sep 2026 17:39:23 -0700 Subject: [PATCH 4/7] Require the generate target's plan to invoke its generator, not just do work --- AGENTS.md | 6 ++-- MAKEFILE-CONVENTION.md | 2 +- hack/test/check-generate-targets.bats | 34 ++++++++++++++++++---- prompts/seed-sdk.md | 9 +++--- seed/AGENTS.md.tmpl | 4 +-- seed/CONTRIBUTING.md.tmpl | 5 ++-- seed/Makefile.tmpl | 4 +-- seed/scripts/check-generate-targets.sh | 40 +++++++++++++++++--------- 8 files changed, 72 insertions(+), 32 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fffd7c7..22b12dd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -93,8 +93,10 @@ The seed ships no generator for any language, so on a pristine render every generators work. What `hack/test/check-generate-targets.bats` pins instead is the detector: `scripts/check-generate-targets.sh` must fail on the pristine render for all six prefixes, pass once the artifacts each recipe invokes exist (Swift's through its -sub-Makefile), and fail on a target that exits 0 having done nothing. Rename a generate -target or move a generator and that test is the first thing to break. +sub-Makefile), and fail on a target that exits 0 having done nothing -- including one +whose own recipe is gone while a prerequisite such as `ts-install` still plans work. +Rename a generate target, move a generator or change how a recipe invokes it and that +test is the first thing to break. ## `seed/rust/` is a crate, not a fragment diff --git a/MAKEFILE-CONVENTION.md b/MAKEFILE-CONVENTION.md index 72f321c..0bc233d 100644 --- a/MAKEFILE-CONVENTION.md +++ b/MAKEFILE-CONVENTION.md @@ -28,7 +28,7 @@ Required Makefile targets and release architecture for SDK repositories. | `bump VERSION=x.y.z` | | Atomic version bump across all languages | | `release VERSION=x.y.z` | | Sole release authority (see below) | | `generate-services` | | Aggregate: `{lang}-generate-services` for every language in `SDK_LANGUAGES` | -| `generate-services-check` | | Non-mutating, part of `check`: every `{lang}-generate-services` in `SDK_LANGUAGES` invokes a generator artifact that exists and expands to real work, through sub-Makefile delegation (`scripts/check-generate-targets.sh --dry-run`) | +| `generate-services-check` | | Non-mutating, part of `check`: every `{lang}-generate-services` in `SDK_LANGUAGES` names a generator artifact that exists and plans its invocation, through sub-Makefile delegation; a prerequisite's commands do not count (`scripts/check-generate-targets.sh --dry-run`) | ## Naming Conventions diff --git a/hack/test/check-generate-targets.bats b/hack/test/check-generate-targets.bats index 9e69def..93ac5d5 100644 --- a/hack/test/check-generate-targets.bats +++ b/hack/test/check-generate-targets.bats @@ -3,7 +3,7 @@ # must fail for every language (no generator ships in the seed), stubbed generator # artifacts must pass, and a target that exits 0 having done nothing must fail. # Only `make` is needed: the dry run never executes a generator, and the run-mode -# cases use a Swift sub-Makefile whose `generate` recipe is a shell one-liner. +# cases put a fake `swift` on PATH behind the Swift sub-Makefile's `swift run`. setup() { tmp="$(mktemp -d)" @@ -47,7 +47,7 @@ set_profile() { stub_generators run scripts/check-generate-targets.sh --dry-run [ "$status" -eq 0 ] - [[ "$output" == *"swift run FizzyGenerator"* ]] + [[ "$output" == *"invokes the generator: swift run FizzyGenerator"* ]] [[ "$output" == *"generate targets wired: go ts rb swift kt rs"* ]] } @@ -56,7 +56,7 @@ set_profile() { printf 'generate:\n' > swift/Makefile run scripts/check-generate-targets.sh --dry-run [ "$status" -eq 1 ] - [[ "$output" == *"swift-generate-services expands to no command"* ]] + [[ "$output" == *"swift-generate-services never invokes the generator"* ]] [[ "$output" == *"ERROR: generate targets not wired: swift"* ]] } @@ -65,7 +65,26 @@ set_profile() { perl -pi -e 's/^swift-generate-services: swift-generate$/swift-generate-services:/' Makefile run scripts/check-generate-targets.sh --dry-run swift [ "$status" -eq 1 ] - [[ "$output" == *"swift-generate-services expands to no command"* ]] + [[ "$output" == *"swift-generate-services never invokes the generator"* ]] +} + +@test "a recipe that was removed is vacuous even when its prerequisites plan work" { + stub_generators + # ts-generate-services keeps ts-install, which still plans `npm ci` and the stamp. + perl -0pi -e 's/^ts-generate-services:\n\t\@echo[^\n]*\n\tcd typescript && npx tsx scripts\/generate-services\.ts\n/ts-generate-services:\n/m' Makefile + ! grep -q 'npx tsx scripts/generate-services.ts' Makefile + run scripts/check-generate-targets.sh --dry-run ts + [ "$status" -eq 1 ] + [[ "$output" == *"npm ci"* ]] + [[ "$output" == *"ts-generate-services never invokes the generator"* ]] +} + +@test "a generator named only inside an echo does not count" { + stub_generators + printf 'generate:\n\t@echo "swift run FizzyGenerator"\n' > swift/Makefile + run scripts/check-generate-targets.sh --dry-run swift + [ "$status" -eq 1 ] + [[ "$output" == *"swift-generate-services never invokes the generator"* ]] } @test "a narrowed profile is checked for its own languages only" { @@ -78,13 +97,16 @@ set_profile() { @test "run mode executes the target and reports its exit status" { stub_generators - printf 'generate:\n\ttouch generated.marker\n' > swift/Makefile + mkdir bin + printf '#!/bin/sh\ntouch generated.marker\n' > bin/swift + chmod +x bin/swift + PATH="$PWD/bin:$PATH" run scripts/check-generate-targets.sh swift [ "$status" -eq 0 ] [ -f swift/generated.marker ] [[ "$output" == *"make swift-generate-services exited 0"* ]] - printf 'generate:\n\texit 3\n' > swift/Makefile + printf '#!/bin/sh\nexit 3\n' > bin/swift run scripts/check-generate-targets.sh swift [ "$status" -eq 1 ] [[ "$output" == *"make swift-generate-services exited non-zero"* ]] diff --git a/prompts/seed-sdk.md b/prompts/seed-sdk.md index caf721e..86dd2df 100644 --- a/prompts/seed-sdk.md +++ b/prompts/seed-sdk.md @@ -87,9 +87,9 @@ The spec drives everything downstream. Initialize each language in parallel -- they are independent of each other. The seed ships no generator for any language: each `-generate-services` recipe names an artifact the scaffold step has to produce, and `scripts/check-generate-targets.sh -` runs the target only after asserting that artifact exists and the target -expands to real work, so a scaffold that quietly produced nothing fails there rather -than at the first spec change. +` runs the target only after asserting that artifact exists and the target's +plan invokes it, so a scaffold that quietly produced nothing fails there rather than +at the first spec change. #### Go 1. Copy `seed/go/` into `go/` @@ -119,7 +119,8 @@ than at the first spec change. 1. Copy `seed/swift/` into `swift/` 2. Initialize `Package.swift` 3. Scaffold client, error types, service base, and `swift/Makefile` with a `generate` - target that runs the generator -- the root `swift-generate-services` delegates to it + target that runs the generator with `swift run` -- the root `swift-generate-services` + delegates to it 4. `scripts/check-generate-targets.sh swift` (runs `make swift-generate-services`) **Checkpoint:** `swift build && swift test` pass. diff --git a/seed/AGENTS.md.tmpl b/seed/AGENTS.md.tmpl index 568322b..9c36384 100644 --- a/seed/AGENTS.md.tmpl +++ b/seed/AGENTS.md.tmpl @@ -36,6 +36,6 @@ Smithy spec -> OpenAPI -> Behavior Model -> Per-language generators -> SDK code `make check` gates on the same profile: `-check` and the conformance runners for the languages in `SDK_LANGUAGES`, nothing else. It also runs `generate-services-check`, -which fails if any generate target in the profile has no generator behind it or expands -to no work. To add a language, add its prefix to `SDK_LANGUAGES` and wire its generator; +which fails if any generate target in the profile has no generator behind it or no +longer invokes it. To add a language, add its prefix to `SDK_LANGUAGES` and wire its generator; `make generate-services-check` says what is still missing. diff --git a/seed/CONTRIBUTING.md.tmpl b/seed/CONTRIBUTING.md.tmpl index f9affdd..adbf414 100644 --- a/seed/CONTRIBUTING.md.tmpl +++ b/seed/CONTRIBUTING.md.tmpl @@ -33,8 +33,9 @@ `make check` gates on the same profile: `-check` and the conformance runners for the languages in `SDK_LANGUAGES`, nothing else. It also runs `generate-services-check`, -which fails if any generate target in the profile has no generator behind it or expands -to no work (`make -n` through Swift's sub-Makefile). `scripts/check-generate-targets.sh` +which fails if any generate target in the profile has no generator behind it or no +longer invokes it (`make -n` through Swift's sub-Makefile; a prerequisite's commands do +not count). `scripts/check-generate-targets.sh` without `--dry-run` runs each generate target as well; that is the check to run after porting or changing a generator. To add a language, add its prefix to `SDK_LANGUAGES`, add it to the `conformance` matrix in `.github/workflows/test.yml`, and wire its diff --git a/seed/Makefile.tmpl b/seed/Makefile.tmpl index 7c079a8..0dd53d3 100644 --- a/seed/Makefile.tmpl +++ b/seed/Makefile.tmpl @@ -482,8 +482,8 @@ conformance: $(addprefix conformance-,$(CONFORMANCE_RUNNERS)) generate-services: $(addsuffix -generate-services,$(SDK_LANGUAGES)) @echo "==> All services regenerated" -# Non-mutating: every generate target in the profile must invoke a generator -# that exists and expand to real work, through Swift's sub-Makefile included. +# Non-mutating: every generate target in the profile must plan an invocation of +# a generator that exists, through Swift's sub-Makefile included. # Without --dry-run the script also runs each target, which is the bootstrap # checkpoint (prompts/seed-sdk.md). generate-services-check: diff --git a/seed/scripts/check-generate-targets.sh b/seed/scripts/check-generate-targets.sh index e25be81..29a4b5d 100755 --- a/seed/scripts/check-generate-targets.sh +++ b/seed/scripts/check-generate-targets.sh @@ -2,10 +2,11 @@ # Proves each -generate-services target is wired to a real generator rather # than exiting 0 vacuously. For every prefix it asserts that: # 1. the generator artifact the root Makefile's recipe invokes exists; -# 2. the target expands to at least one command that is not an echo, through any -# sub-Makefile delegation (`make -n` follows $(MAKE) -C), so an alias whose -# prerequisite was tidied away, or a sub-Makefile `generate` with no recipe, -# fails here; +# 2. the target's plan (`make -n`, which follows $(MAKE) -C into a sub-Makefile) +# contains the invocation of that generator. Any command from a prerequisite +# is not enough: `ts-install` still plans `npm ci` after the ts recipe is gone, +# so a recipe that was tidied away, an alias that lost its prerequisite, or a +# sub-Makefile `generate` with no recipe all fail here; # 3. unless --dry-run, `make -generate-services` runs and exits 0. # Prefixes default to SDK_LANGUAGES in the Makefile. `make generate-services-check` # runs the dry form as part of `make check`; the full form is the bootstrap @@ -34,7 +35,8 @@ else fi # The artifact each root recipe invokes. Swift's recipe is `$(MAKE) -C swift -# generate`, so its artifact is the sub-Makefile and check 2 covers the target in it. +# generate`, so its artifact is the sub-Makefile, and the invocation to find is +# the sub-Makefile's: the generator is a SwiftPM executable run with `swift run`. artifact_for() { case "$1" in go) echo "go/cmd/generate-services" ;; @@ -47,9 +49,21 @@ artifact_for() { esac } -# What `make -n` would run, minus echoes, make's own recursion lines and its -# "Nothing to be done" notices. Anything left is generator work. -work_lines() { +# The command line, as `make -n` prints it, that runs the generator (grep -E). +invocation_for() { + case "$1" in + go) echo 'go run \./cmd/generate-services' ;; + ts) echo 'scripts/generate-services\.ts' ;; + rb) echo 'ruby scripts/generate-services\.rb' ;; + swift) echo '^swift run ' ;; + kt) echo 'gradlew :generator:run' ;; + rs) echo 'cargo run .*-generator' ;; + esac +} + +# The plan `make -n` prints, minus echoes, make's own recursion lines and its +# "Nothing to be done" notices, so a generator named inside an echo does not count. +commands_of() { grep -vE '^(echo |(.*/)?make(\[[0-9]+\])?[ :])' || true } @@ -71,13 +85,13 @@ for prefix in "${prefixes[@]}"; do ok=false fi + invocation=$(invocation_for "$prefix") if plan=$("$make" -n --no-print-directory "$target" 2>&1); then - work=$(printf '%s\n' "$plan" | work_lines) - if [ -n "$work" ]; then - echo " ok: expands to work:" - printf '%s\n' "$work" | sed 's/^/ /' + if run_line=$(printf '%s\n' "$plan" | commands_of | grep -E -m1 "$invocation"); then + echo " ok: invokes the generator: $run_line" else - echo " FAIL: $target expands to no command; it exits 0 having done nothing" + echo " FAIL: $target never invokes the generator (no command matches /$invocation/); its plan is:" + printf '%s\n' "$plan" | sed 's/^/ /' ok=false fi else From 1a72ae91a919d87431fc67f1c95ac1c9d2127457 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 12 Sep 2026 18:28:09 -0700 Subject: [PATCH 5/7] Gate check-full on generate-services-check too --- seed/Makefile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/Makefile.tmpl b/seed/Makefile.tmpl index 0dd53d3..ee7cf9e 100644 --- a/seed/Makefile.tmpl +++ b/seed/Makefile.tmpl @@ -567,7 +567,7 @@ audit-check: check-mvp: smithy-check behavior-model-check url-routes-check sync-api-version-check $(if $(HAS_GO),go-check) @echo "==> MVP checks passed" -check-full: check-mvp $(if $(HAS_GO),provenance-check) audit-check $(filter-out go-check,$(LANG_CHECKS)) conformance +check-full: check-mvp $(if $(HAS_GO),provenance-check) audit-check generate-services-check $(filter-out go-check,$(LANG_CHECKS)) conformance @echo "==> Full checks passed" check: smithy-check behavior-model-check url-routes-check sync-api-version-check $(if $(HAS_GO),provenance-check) audit-check generate-services-check $(LANG_CHECKS) conformance From 017fbd6fce7df932fc52b167fde4ed478299963a Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 12 Sep 2026 19:00:51 -0700 Subject: [PATCH 6/7] Treat a commented-out recipe line as no invocation --- hack/test/check-generate-targets.bats | 9 +++++++++ seed/scripts/check-generate-targets.sh | 8 +++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/hack/test/check-generate-targets.bats b/hack/test/check-generate-targets.bats index 93ac5d5..e8690b0 100644 --- a/hack/test/check-generate-targets.bats +++ b/hack/test/check-generate-targets.bats @@ -87,6 +87,15 @@ set_profile() { [[ "$output" == *"swift-generate-services never invokes the generator"* ]] } +@test "a commented-out generator command does not count" { + stub_generators + perl -pi -e 's/^\tcd ruby && ruby scripts\/generate-services\.rb$/\t# cd ruby && ruby scripts\/generate-services.rb/' Makefile + grep -q '^ # cd ruby && ruby scripts/generate-services.rb' Makefile + run scripts/check-generate-targets.sh --dry-run rb + [ "$status" -eq 1 ] + [[ "$output" == *"rb-generate-services never invokes the generator"* ]] +} + @test "a narrowed profile is checked for its own languages only" { set_profile "go rs" run scripts/check-generate-targets.sh --dry-run diff --git a/seed/scripts/check-generate-targets.sh b/seed/scripts/check-generate-targets.sh index 29a4b5d..5068465 100755 --- a/seed/scripts/check-generate-targets.sh +++ b/seed/scripts/check-generate-targets.sh @@ -61,10 +61,12 @@ invocation_for() { esac } -# The plan `make -n` prints, minus echoes, make's own recursion lines and its -# "Nothing to be done" notices, so a generator named inside an echo does not count. +# The plan `make -n` prints, minus the lines that are not commands: echoes, shell +# comments (a commented-out recipe line is still printed), make's own recursion +# lines and its "Nothing to be done" notices. A generator named in any of those +# does not count. commands_of() { - grep -vE '^(echo |(.*/)?make(\[[0-9]+\])?[ :])' || true + grep -vE '^(#|echo |(.*/)?make(\[[0-9]+\])?[ :])' || true } failed=() From 15e14102f1110d33f34f37734cef3126753b1b7f Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 12 Sep 2026 19:34:19 -0700 Subject: [PATCH 7/7] Fail make on an SDK_LANGUAGES prefix outside the vocabulary --- hack/test/check-generate-targets.bats | 7 +++++++ seed/Makefile.tmpl | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/hack/test/check-generate-targets.bats b/hack/test/check-generate-targets.bats index e8690b0..1b0396e 100644 --- a/hack/test/check-generate-targets.bats +++ b/hack/test/check-generate-targets.bats @@ -104,6 +104,13 @@ set_profile() { [[ "$output" != *"ts-generate-services"* ]] } +@test "an unknown prefix in SDK_LANGUAGES fails make at parse time" { + set_profile "go tsx" + run make -n conformance + [ "$status" -ne 0 ] + [[ "$output" == *"SDK_LANGUAGES has unknown prefix(es): tsx"* ]] +} + @test "run mode executes the target and reports its exit status" { stub_generators mkdir bin diff --git a/seed/Makefile.tmpl b/seed/Makefile.tmpl index ee7cf9e..03399d6 100644 --- a/seed/Makefile.tmpl +++ b/seed/Makefile.tmpl @@ -17,6 +17,13 @@ all: check # is absent; the profile changes what the aggregates ask for, not what exists. SDK_LANGUAGES := go ts rb swift kt rs +# A prefix outside the vocabulary fails every make invocation here, at parse +# time, rather than being dropped from the aggregates that map it. +KNOWN_LANGUAGES := go ts rb swift kt rs +ifneq ($(filter-out $(KNOWN_LANGUAGES),$(SDK_LANGUAGES)),) +$(error SDK_LANGUAGES has unknown prefix(es): $(filter-out $(KNOWN_LANGUAGES),$(SDK_LANGUAGES)); expected a subset of "$(KNOWN_LANGUAGES)") +endif + LANG_CHECKS := $(addsuffix -check,$(SDK_LANGUAGES)) # Provenance is embedded in the Go package and check-mvp is the Go inner loop;