diff --git a/cmd/releasekit/multi_module_example_test.go b/cmd/releasekit/multi_module_example_test.go index d93639e..55e12b3 100644 --- a/cmd/releasekit/multi_module_example_test.go +++ b/cmd/releasekit/multi_module_example_test.go @@ -148,7 +148,11 @@ func TestMultiModuleLauncherRunsToolFromSelectedDependency(t *testing.T) { repository := newCLIRepository(t) copyExampleTree(t, example, repository) writeRepositoryFile(t, repository, "go.mod", "module example.com/multi-module-consumer\n\ngo 1.25.0\n\nrequire go.maximumdirect.net/releasekit v0.2.197\n") - writeRepositoryFile(t, repository, "integrations/ckconfig/go.mod", "module example.com/multi-module-consumer/integrations/ckconfig\n\ngo 1.25.0\n\nrequire go.maximumdirect.net/releasekit v0.2.198\n") + const otherModule = "example.com/other-tool" + other := t.TempDir() + writeRepositoryFile(t, other, "go.mod", "module "+otherModule+"\n\ngo 1.25.0\n") + writeRepositoryFile(t, other, "cmd/releasekit/main.go", "package main\nfunc main() { println(\"unexpected tool ran\") }\n") + writeRepositoryFile(t, repository, "integrations/ckconfig/go.mod", "module example.com/multi-module-consumer/integrations/ckconfig\n\ngo 1.25.0\n\nrequire go.maximumdirect.net/releasekit v0.2.198\nrequire "+otherModule+" v0.0.0\n\nreplace "+otherModule+" => "+strconv.Quote(other)+"\n\ntool "+otherModule+"/cmd/releasekit\n") cliGitRun(t, repository, "add", ".") cliGitRun(t, repository, "commit", "-m", "prepare selected tool module") @@ -200,6 +204,9 @@ func TestMultiModuleLauncherRunsToolFromSelectedDependency(t *testing.T) { if strings.Contains(string(output), "tool-version=v0.2.197") { t.Fatalf("launcher ran the root-selected ReleaseKit version: %s", output) } + if strings.Contains(string(output), "unexpected tool ran") { + t.Fatalf("launcher ran the other tool with the same name: %s", output) + } moduleAfter, err := os.ReadFile(filepath.Join(repository, "integrations", "ckconfig", "go.mod")) if err != nil || !bytes.Equal(moduleAfter, moduleBefore) { t.Fatalf("selected module go.mod changed: got %q want %q error=%v", moduleAfter, moduleBefore, err) diff --git a/cmd/releasekit/woodpecker_test.go b/cmd/releasekit/woodpecker_test.go index fbbe2da..ea16d0b 100644 --- a/cmd/releasekit/woodpecker_test.go +++ b/cmd/releasekit/woodpecker_test.go @@ -669,7 +669,7 @@ func assertGoSelection(t *testing.T, repository, goLog string) { if !strings.Contains(log, "GOWORK=off GOTOOLCHAIN=local") || !strings.Contains(log, list) || !strings.Contains(log, "-m "+module) { t.Fatalf("Go module selection command is missing required settings: %q", log) } - if !strings.Contains(log, tool) || !strings.Contains(log, " releasekit ci-deliver ") || strings.Contains(log, "@v0.2.0") { + if !strings.Contains(log, tool) || !strings.Contains(log, " "+module+"/cmd/releasekit ci-deliver ") || strings.Contains(log, "@v0.2.0") { t.Fatalf("Go tool did not run the module-selected ReleaseKit: %q", log) } if strings.Index(log, list) > strings.Index(log, tool) { diff --git a/docs/cli.md b/docs/cli.md index 301e039..919b5d9 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -342,11 +342,12 @@ status 2. Final delivery uses the same statuses as `deliver`: 0 for complete, state, including when cancellation prevents reconciliation. The maintained module-based tag workflow verifies and checks out the event tag -before resolving the consumer module. It runs `go tool releasekit` for the version -already selected by that tagged `go.mod`, using temporary alternate module files -outside the source checkout. This tool bootstrap is separate from `ci-deliver`'s -application dependency preparation for binary profiles. ReleaseKit's own -source workflow self-bootstraps `./cmd/releasekit`; consumers without a declared +before resolving the consumer module. It invokes the ReleaseKit tool by its full +package path for the version already selected by that tagged `go.mod`, using +temporary alternate module files outside the source checkout. This tool bootstrap +is separate from `ci-deliver`'s application dependency preparation for binary +profiles. ReleaseKit's own source workflow self-bootstraps `./cmd/releasekit`; +consumers without a declared ReleaseKit module dependency can use the separately installed-tool command. See the [delivery guide](consumers/delivery.md#woodpecker-tag-workflows) for compatible Go environments, retries, and preparation details. diff --git a/docs/consumers/adoption.md b/docs/consumers/adoption.md index 111864d..2e825d8 100644 --- a/docs/consumers/adoption.md +++ b/docs/consumers/adoption.md @@ -34,16 +34,17 @@ Go minimum. See the ### Select the Go host target -Run `go tool releasekit` from within the consumer module to use its selected -version. Go builds and caches the executable and preserves its exit status. -A launcher invoked from that module can forward commands directly: +Invoke the ReleaseKit tool by its full package path from within the consumer +module to use its selected version. Go builds and caches the executable and +preserves its exit status. The full path distinguishes tools with the same +short name. A launcher can forward commands directly: ```sh #!/bin/sh releasekit_host_os=$(go env GOHOSTOS) || exit $? releasekit_host_arch=$(go env GOHOSTARCH) || exit $? export GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch" -exec go tool releasekit "$@" +exec go tool go.maximumdirect.net/releasekit/cmd/releasekit "$@" ``` Save this launcher as `releasekit` and run `sh ./releasekit version` from the @@ -114,9 +115,9 @@ Go module identity. The Go consumer minimum remains 1.26.0. ## CI adoption The maintained [consumer tag-delivery workflow](delivery.md#woodpecker-tag-workflows) -supports both tool and ordinary package dependencies. It runs `go tool releasekit` -with temporary module metadata derived from the tagged consumer's module -selection, so the committed module files also govern CI delivery and retries. +supports both tool and ordinary package dependencies. It runs the ReleaseKit +tool by its full package path with temporary metadata derived from the tagged +module, so the committed module files also govern CI delivery and retries. Registering the CLI as a tool is sufficient for consumers that use ReleaseKit only for maintainer commands. diff --git a/docs/consumers/delivery.md b/docs/consumers/delivery.md index b06b628..090e96e 100644 --- a/docs/consumers/delivery.md +++ b/docs/consumers/delivery.md @@ -296,9 +296,10 @@ directory outside the checkout. `go list -m` confirms that the module selects ReleaseKit. The template selects the host Go target before preparing and running the CLI. The template adds its CLI tool declaration to that temporary copy and prepares tool dependencies with `go list -deps`. It then uses -`go tool -modfile=... releasekit` to build, cache, and run the selected tool with -`CI_WORKSPACE`, the resolved event commit, and `CI_COMMIT_TAG`. The tagged module -files stay unchanged, including for consumers with an ordinary package +`go tool -modfile=... go.maximumdirect.net/releasekit/cmd/releasekit` to build, +cache, and run the selected tool with `CI_WORKSPACE`, the resolved event commit, +and `CI_COMMIT_TAG`. The tagged module files stay unchanged, including for +consumers with an ordinary package dependency. A cleanup trap removes the temporary metadata; Go manages the cached executable. The command preserves the tool's exit status. diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index d6a77ce..f698170 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -235,6 +235,10 @@ once. No additional scaling issue was identified. ### Stages 1, 2, and 6: Invoke the exact registered Go tool +**Completed.** The launchers, templates, and consumer recipes use the full +package path. The real Go launcher regression verifies tool selection and +exit statuses with a second registered tool of the same short name. + **Evidence:** The [multi-module launcher](../../examples/multi-module/release.sh) and both consumer Woodpecker templates ([single-module](../../examples/woodpecker/tag-delivery.yml), diff --git a/examples/multi-module/README.md b/examples/multi-module/README.md index 2c625da..b12b78d 100644 --- a/examples/multi-module/README.md +++ b/examples/multi-module/README.md @@ -73,9 +73,9 @@ Then select a module and semantic version with the shared launcher: The launcher copies the selected module's `go.mod` and `go.sum` to temporary alternate module files outside the checkout, resolves ReleaseKit from that module's local dependency selection, adds the CLI tool declaration to that -temporary copy, and prepares its dependencies. It uses `go tool releasekit` -with the alternate module file to invoke `publish` with the repository profile -and `--expected-module` so the library can verify that the profile agrees with +temporary copy, and prepares its dependencies. It invokes the tool by its full +package path with the alternate module file to run `publish` with the repository +profile and `--expected-module` so the library can verify that the profile agrees with the selected table row. Go builds and caches the executable and preserves its exit status; the launcher removes the temporary metadata on exit. As with any `publish` invocation, commit the selected module's release note and prepare its @@ -89,9 +89,9 @@ This declaration does not infer dependency order or rerun the standard suite. The maintained [Woodpecker example](woodpecker.yml) uses the same map and selector for root, `ckconfig`, and major-version tags. It verifies and detaches the literal event tag before selecting the module, copies that module's metadata -outside the checkout, and runs the ReleaseKit version selected there through -`go tool releasekit`. It then uses `ci-deliver --prepared` with the mapped profile -and expected physical module. +outside the checkout, and runs the selected ReleaseKit tool by its full package +path. It passes `ci-deliver --prepared`, the mapped profile, and the expected +physical module to the tool. The `core-v2` profile demonstrates binary delivery; the other profiles are page-only. Before enabling the workflow, configure each selected profile's `delivery` object with `"forge": "gitea"`, its `base_url`, owner, and repository, @@ -131,21 +131,21 @@ fixture and run the source-selected tool without publishing: releasekit_host_os=$(go env GOHOSTOS) || exit $? releasekit_host_arch=$(go env GOHOSTARCH) || exit $? export GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch" - go tool releasekit check --config releasekit-root.json v1.2.3 + go tool go.maximumdirect.net/releasekit/cmd/releasekit check --config releasekit-root.json v1.2.3 ) ( cd ./integrations/ckconfig || exit $? releasekit_host_os=$(go env GOHOSTOS) || exit $? releasekit_host_arch=$(go env GOHOSTARCH) || exit $? export GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch" - go tool releasekit check --config releasekit-ckconfig.json v1.2.3 + go tool go.maximumdirect.net/releasekit/cmd/releasekit check --config releasekit-ckconfig.json v1.2.3 ) ( cd ./integrations/core/v2 || exit $? releasekit_host_os=$(go env GOHOSTOS) || exit $? releasekit_host_arch=$(go env GOHOSTARCH) || exit $? export GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch" - go tool releasekit check --config releasekit-core-v2.json v2.1.0 + go tool go.maximumdirect.net/releasekit/cmd/releasekit check --config releasekit-core-v2.json v2.1.0 ) ``` diff --git a/examples/multi-module/release.sh b/examples/multi-module/release.sh index 7d02ad3..ed880e6 100755 --- a/examples/multi-module/release.sh +++ b/examples/multi-module/release.sh @@ -42,7 +42,7 @@ releasekit_module=go.maximumdirect.net/releasekit go list -mod=mod -modfile="$bootstrap_dir/releasekit.mod" -m "$releasekit_module" go mod edit -modfile="$bootstrap_dir/releasekit.mod" -tool="$releasekit_module/cmd/releasekit" go list -mod=mod -modfile="$bootstrap_dir/releasekit.mod" -deps "$releasekit_module/cmd/releasekit" > /dev/null - go tool -modfile="$bootstrap_dir/releasekit.mod" releasekit publish \ + go tool -modfile="$bootstrap_dir/releasekit.mod" "$releasekit_module/cmd/releasekit" publish \ --repo "$repository_dir" \ --config "$profile_path" \ --expected-module "$module_dir" \ diff --git a/examples/multi-module/woodpecker.yml b/examples/multi-module/woodpecker.yml index b1f407c..b252840 100644 --- a/examples/multi-module/woodpecker.yml +++ b/examples/multi-module/woodpecker.yml @@ -58,7 +58,7 @@ steps: go list -mod=mod -modfile="$${bootstrap_dir}/releasekit.mod" -m "$${releasekit_module}" go mod edit -modfile="$${bootstrap_dir}/releasekit.mod" -tool="$${releasekit_module}/cmd/releasekit" go list -mod=mod -modfile="$${bootstrap_dir}/releasekit.mod" -deps "$${releasekit_module}/cmd/releasekit" > /dev/null - go tool -modfile="$${bootstrap_dir}/releasekit.mod" releasekit ci-deliver \ + go tool -modfile="$${bootstrap_dir}/releasekit.mod" "$${releasekit_module}/cmd/releasekit" ci-deliver \ --repo "$${CI_WORKSPACE}" \ --config "$${profile_path}" \ --expected-module "$${module_dir}" \ diff --git a/examples/woodpecker/tag-delivery.yml b/examples/woodpecker/tag-delivery.yml index ed3d20a..647401a 100644 --- a/examples/woodpecker/tag-delivery.yml +++ b/examples/woodpecker/tag-delivery.yml @@ -48,7 +48,7 @@ steps: go list -mod=mod -modfile="$${bootstrap_dir}/releasekit.mod" -m "$${releasekit_module}" go mod edit -modfile="$${bootstrap_dir}/releasekit.mod" -tool="$${releasekit_module}/cmd/releasekit" go list -mod=mod -modfile="$${bootstrap_dir}/releasekit.mod" -deps "$${releasekit_module}/cmd/releasekit" > /dev/null - go tool -modfile="$${bootstrap_dir}/releasekit.mod" releasekit ci-deliver \ + go tool -modfile="$${bootstrap_dir}/releasekit.mod" "$${releasekit_module}/cmd/releasekit" ci-deliver \ --repo "$${CI_WORKSPACE}" \ --prepared \ --commit "$${expected_commit}" \