Plan host native release launchers
This commit is contained in:
93
docs/roadmap/commandkit-feature-request.md
Normal file
93
docs/roadmap/commandkit-feature-request.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# CommandKit feature request: host-safe consumer launchers
|
||||
|
||||
Status: proposed; not an accepted contract or implementation assignment.
|
||||
|
||||
## Consumer need
|
||||
|
||||
CommandKit selects ReleaseKit v0.8.0 as a Go tool and uses a small launcher to
|
||||
publish its core and two optional integration modules. Maintainers can inherit
|
||||
`GOOS` and `GOARCH` from application cross-build work. The release tool must run
|
||||
on the maintainer's host even when those variables name another target.
|
||||
|
||||
CommandKit currently handles this by querying `go env GOHOSTOS` and
|
||||
`go env GOHOSTARCH`, then using those values when building its native ReleaseKit
|
||||
executable. Its `TestReleaseLauncherDelegatesPublication` test supplies foreign
|
||||
target variables and checks native bootstrap behavior together with argument
|
||||
and exit-status forwarding. Consumer evidence is in CommandKit's
|
||||
`scripts/release` and `scripts/release_test.go` at local commit
|
||||
`a74fcb20041cbccde93f9b9a1e3b24cb8dd131e7`.
|
||||
|
||||
Please incorporate this behavior and regression coverage into ReleaseKit's
|
||||
maintained consumer launcher example so downstream projects can reuse the
|
||||
recipe and rely on upstream tests for its generic bootstrap mechanics.
|
||||
|
||||
## Observed failure
|
||||
|
||||
Reviewed ReleaseKit v0.8.0, commit
|
||||
`23203f235cc414c4ffe0fd3941030eb0c9f9943f`. The
|
||||
[multi-module launcher](../../examples/multi-module/release.sh) invokes
|
||||
`go tool` without normalizing the inherited target environment.
|
||||
|
||||
On Linux/amd64 with Go 1.26.8, from a prepared consumer module registering
|
||||
`go.maximumdirect.net/releasekit/cmd/releasekit` at v0.8.0:
|
||||
|
||||
```sh
|
||||
GOWORK=off GOTOOLCHAIN=local GOFLAGS=-mod=readonly \
|
||||
GOPROXY=off GONOPROXY=none GOSUMDB=off \
|
||||
GOOS=windows GOARCH=arm64 go tool releasekit version
|
||||
```
|
||||
|
||||
The command built a Windows executable and failed to launch it with an
|
||||
`exec format error`. Repeating the command with `GOOS=linux GOARCH=amd64`
|
||||
succeeded and printed `releasekit v0.8.0`. These observations establish the
|
||||
tool-execution failure; the complete upstream launcher was inspected rather
|
||||
than executed in this reproduction. No publication was attempted.
|
||||
|
||||
## Requested change
|
||||
|
||||
Update the maintained launcher to select the native host target when preparing
|
||||
and executing ReleaseKit, regardless of inherited application target variables.
|
||||
Resolve the host through the selected Go toolchain rather than hard-coding a
|
||||
platform. Scope this change to running the maintainer tool; preserve the
|
||||
application targets selected through existing release profiles.
|
||||
|
||||
Retain the launcher's existing module/version selection, profile routing,
|
||||
source-file preservation, workspace/toolchain settings, and child exit-status
|
||||
behavior. Document the host-target handling in the
|
||||
[consumer adoption guidance](../consumers/adoption.md) where appropriate.
|
||||
|
||||
Prefer a small change to the supported example and its tests. This request does
|
||||
not require a new validation option, public bootstrap API, or test-helper
|
||||
package. CommandKit's choice to select one tool version from its root module
|
||||
remains consumer-owned; this request does not change ReleaseKit's example policy
|
||||
of selecting the tool from the chosen module.
|
||||
|
||||
## Acceptance evidence
|
||||
|
||||
Extend the existing
|
||||
[multi-module launcher tests](../../cmd/releasekit/multi_module_example_test.go)
|
||||
with a controlled foreign-target environment. Demonstrate that:
|
||||
|
||||
1. The selected tool executes on the host despite foreign `GOOS` and `GOARCH`.
|
||||
2. The selected module's tool version and publication arguments still reach the
|
||||
child, and child statuses 0, 1, 2, and 3 are preserved.
|
||||
3. Source module files remain unchanged and existing bootstrap cleanup behavior
|
||||
is preserved.
|
||||
|
||||
Reuse existing coverage for unaffected behavior rather than adding a parallel
|
||||
suite. A host-run test can use a synthetic local tool and needs no real release
|
||||
or external service. Synthetic module downloads must use test-owned caches as
|
||||
described in [the developer guide](../development.md#testing-synthetic-modules).
|
||||
|
||||
## Ownership and downstream follow-up
|
||||
|
||||
CommandKit's review found no locally maintained candidate validator that needs
|
||||
to become a new ReleaseKit feature. Existing formatting, Markdown, and module
|
||||
distribution options already cover their respective reusable checks; selecting
|
||||
them as CommandKit release gates is a separate consumer decision.
|
||||
|
||||
ReleaseKit already owns publication-engine tests for annotated tags, exact
|
||||
remote objects, nested tag prefixes, and endpoint preflight. CommandKit can
|
||||
narrow its local publication exercise to its launcher and profile wiring
|
||||
without upstreaming a duplicate engine test suite. Its selectors, product names,
|
||||
publication endpoint, and release order remain local responsibilities.
|
||||
@@ -16,9 +16,10 @@ and describes work to be implemented after this documentation pass.
|
||||
|
||||
## Current baseline
|
||||
|
||||
CommandKit reports that a prepared v0.8.0 tool invocation on Linux/amd64 with
|
||||
`GOOS=windows GOARCH=arm64` built a Windows executable and failed with an exec
|
||||
format error. Its local launcher already selects the Go host target before
|
||||
The [CommandKit feature request](commandkit-feature-request.md) describes a
|
||||
prepared v0.8.0 tool invocation on Linux/amd64 with `GOOS=windows GOARCH=arm64`
|
||||
that built a Windows executable and failed with an exec format error.
|
||||
CommandKit's local launcher already selects the Go host target before
|
||||
building the release executable. The report's reproduction exercised tool
|
||||
execution without attempting publication.
|
||||
|
||||
@@ -65,89 +66,8 @@ does not establish a hermetic environment, broaden hardware or platform support,
|
||||
or add release gates. Existing Go/toolchain failures remain ordinary bootstrap
|
||||
failures. No new fallback toolchain or download policy is introduced.
|
||||
|
||||
## Work needed
|
||||
|
||||
### 1. Normalize the maintained multi-module launcher
|
||||
|
||||
**Rationale:** A consumer should be able to invoke its release launcher after
|
||||
cross-build work without first repairing its shell environment.
|
||||
|
||||
**Target:** The selected ReleaseKit executable and its dependency preparation
|
||||
use the Go host target.
|
||||
|
||||
**Work:** Query the host OS and architecture through the same Go toolchain used
|
||||
for bootstrap, under the launcher's existing workspace and toolchain settings.
|
||||
Apply both values within the tool-running scope before target-dependent Go
|
||||
commands. Preserve `go tool`, temporary module metadata, selected-module version
|
||||
resolution, and existing publication arguments. Use ordinary shell error handling
|
||||
for host-query failures. Avoid hard-coded host values or a new bootstrap layer.
|
||||
|
||||
### 2. Align supported invocation recipes
|
||||
|
||||
**Rationale:** The same inherited-target failure can affect local and CI tool
|
||||
execution. Maintained recipes should give consumers consistent behavior.
|
||||
|
||||
**Target:** The adoption example, consumer and maintainer launchers, CI
|
||||
templates, and generated recovery commands select the host target when
|
||||
preparing and invoking ReleaseKit.
|
||||
|
||||
**Work:** Apply the same small normalization recipe to the adoption example and
|
||||
the single-module and multi-module CI templates. Preserve tag verification before
|
||||
bootstrap, tagged module selection, profile routing, credentials, and temporary
|
||||
metadata handling. Apply host selection to the maintainer launcher, source
|
||||
delivery template, and manual delivery retry. Update the multi-module README
|
||||
and consumer guidance to explain host selection and profile-owned application
|
||||
targets. Document that consumers adopt the corrected launcher or template; a
|
||||
module dependency upgrade alone does not rewrite their copied scripts.
|
||||
|
||||
Apply host selection to module-based recovery dependency preparation and to the
|
||||
Go invocation printed by `publish`. Preserve registered-tool selection, private
|
||||
bootstrap metadata for API-only consumers, the native executable fallback,
|
||||
explicit record selection, and recovery exit statuses.
|
||||
|
||||
### 3. Extend existing behavioral coverage
|
||||
|
||||
**Rationale:** Successful execution under a foreign target directly protects
|
||||
against the reported defect while existing fixtures protect delegation behavior.
|
||||
|
||||
**Target:** A synthetic selected tool runs on the test host despite foreign
|
||||
target variables, with the current routing and cleanup behavior intact.
|
||||
|
||||
**Work:** Extend the existing launcher fixture with a controlled target different
|
||||
from the test host. Exercise real Go tool preparation and execution; a source-text
|
||||
assertion alone cannot establish that the executable runs. Verify that the
|
||||
selected module's tool version and publication arguments reach the child and
|
||||
that statuses 0, 1, 2, and 3 propagate. Check unchanged source module files and
|
||||
existing temporary bootstrap cleanup. Reuse unaffected tests rather than adding
|
||||
a parallel launcher or publication-engine suite.
|
||||
|
||||
Verify host selection at the dependency inspection step itself, using a
|
||||
host-specific fixture or an environment assertion at `go list -deps`. Cover
|
||||
affected CI/bootstrap recipes through their existing test harnesses where
|
||||
practical. Extend the existing handoff tests to exercise recovery preparation
|
||||
under a foreign target, then run the printed command under a foreign target in
|
||||
a separate invocation. Preserve profile-selected application targets. Use
|
||||
synthetic local tools and test-owned module caches under the
|
||||
[fixture-isolation guidance](../development.md#testing-synthetic-modules).
|
||||
The regression needs no actual publication, hosted service, or external platform
|
||||
runner. Choose a representative supported foreign target instead of introducing
|
||||
a platform matrix.
|
||||
|
||||
### 4. Validate and document adoption
|
||||
|
||||
**Rationale:** Consumers should be able to copy a verified recipe and reduce
|
||||
their local tests to their own wiring.
|
||||
|
||||
**Target:** Maintained examples, guidance, and tests agree on the native bootstrap
|
||||
behavior and its scope.
|
||||
|
||||
**Work:** Run the relevant launcher and workflow tests, the normal offline suite,
|
||||
and required facade checks when implementation lands. Check shell/example syntax,
|
||||
documentation links, and whitespace. Include a concise release-note entry for
|
||||
the inherited-target fix and affected recipes. Explain that ReleaseKit owns
|
||||
generic bootstrap and publication-engine coverage while consumers verify their
|
||||
selectors and profile mapping. This does not require consumers to remove tests
|
||||
that protect their own behavior.
|
||||
The [implementation plan](implementation.md) defines the work to reach this
|
||||
state.
|
||||
|
||||
## Completion evidence
|
||||
|
||||
@@ -157,6 +77,3 @@ Preserve selected tool identity and arguments, child-status forwarding, unchange
|
||||
source metadata, and existing cleanup behavior. The maintained local and CI
|
||||
recipes and adoption guidance must use the same host-selection approach.
|
||||
Application build targets must retain their existing profile-driven behavior.
|
||||
|
||||
There are no significant open design decisions for this scope. Exact shell
|
||||
variable names and fixture arrangement can be selected during implementation.
|
||||
|
||||
181
docs/roadmap/implementation.md
Normal file
181
docs/roadmap/implementation.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Implementation plan: host-native ReleaseKit launchers
|
||||
|
||||
Follow the stages in order as one sprint. The
|
||||
[feature roadmap](host-native-launchers.md) defines the intended behavior and
|
||||
scope. The focused checks below describe regression coverage; intermediate
|
||||
stages need no separate review, release, or passing build. Run the assembled
|
||||
checks in Stage 6.
|
||||
|
||||
## Stage 1: Normalize the local multi-module launcher
|
||||
|
||||
1. In [the multi-module launcher](../../examples/multi-module/release.sh), after
|
||||
entering the selected module and exporting its existing `GOWORK=off` and
|
||||
`GOTOOLCHAIN=local` settings, insert the following before the first Go
|
||||
bootstrap command. Reuse this shell recipe in the subsequent stages:
|
||||
|
||||
```sh
|
||||
releasekit_host_os=$(go env GOHOSTOS) || exit $?
|
||||
releasekit_host_arch=$(go env GOHOSTARCH) || exit $?
|
||||
export GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch"
|
||||
```
|
||||
|
||||
Keep the checked assignments separate from `export` so a failed Go query
|
||||
stops the launcher. Keep host selection inside the existing launcher scope;
|
||||
preserve selected module metadata, dependency preparation, publication
|
||||
arguments, traps, and the child status. Application targets still come from
|
||||
release profiles.
|
||||
|
||||
2. Extend [the existing launcher tests](../../cmd/releasekit/multi_module_example_test.go).
|
||||
In `TestMultiModuleLauncherUsesSelectedModuleAndProfile`, let the Go shim
|
||||
answer the two host queries and assert the exported target at
|
||||
`go list -deps` in the selected module. In
|
||||
`TestMultiModuleLauncherRunsToolFromSelectedDependency`, supply a foreign
|
||||
target to the launcher and retain real Go tool preparation and execution.
|
||||
Keep the checks for the nested module's version, forwarded profile and
|
||||
arguments, statuses 0 through 3, unchanged source, and bootstrap cleanup.
|
||||
For these fixtures, choose `windows` unless the host OS is `windows`
|
||||
(then `linux`), and `arm64` unless the host architecture is `arm64` (then
|
||||
`amd64`). This makes both variables differ from the host pair. Keep
|
||||
synthetic versions in a test-owned `GOMODCACHE`.
|
||||
|
||||
## Stage 2: Normalize consumer Woodpecker templates
|
||||
|
||||
1. Apply the Stage 1 recipe in the
|
||||
[single-module](../../examples/woodpecker/tag-delivery.yml) and
|
||||
[multi-module](../../examples/multi-module/woodpecker.yml) tag delivery
|
||||
commands, under the existing Go settings and in the selected module. Insert
|
||||
it after tag verification and checkout, before the first Go bootstrap
|
||||
command. Write the new shell variable references as
|
||||
`$${releasekit_host_os}` and `$${releasekit_host_arch}` so Woodpecker
|
||||
passes them through. Keep the verified commit, selected tool, profile
|
||||
mapping, credentials, alternate module and sum files, cleanup, and
|
||||
`ci-deliver --prepared` invocation.
|
||||
|
||||
2. Update the existing
|
||||
[single-module](../../cmd/releasekit/woodpecker_test.go) and
|
||||
[multi-module](../../cmd/releasekit/multi_module_woodpecker_test.go) workflow
|
||||
harnesses. Teach the controlled Go shim to answer host queries before
|
||||
enforcing its alternate module file checks. In both Go shims, log the
|
||||
target at `go list -deps`. Feed a foreign target to
|
||||
`TestWoodpeckerTagDeliveryTemplate`,
|
||||
`TestWoodpeckerTagDeliveryUsesTaggedModuleVersion`,
|
||||
and `TestMultiModuleWoodpeckerDelivery` and check that dependency inspection
|
||||
uses the host pair. Retain real execution where the fixture already uses it,
|
||||
and keep the existing checks that a rejected tag or commit reaches no Go
|
||||
command.
|
||||
|
||||
## Stage 3: Normalize source-built launchers
|
||||
|
||||
1. Apply the same recipe in the [maintainer launcher](../../scripts/release)
|
||||
before building and executing ReleaseKit. Prefix the two queries with
|
||||
`GOTOOLCHAIN=local` to match its existing build. Preserve usage checking,
|
||||
output location, build settings, and the final `exec`.
|
||||
2. Apply the recipe in the
|
||||
[source delivery template](../../examples/woodpecker/releasekit-source-delivery.yml)
|
||||
after tag verification and checkout, under its existing Go settings and
|
||||
before the source build. Use the Woodpecker escaping from Stage 2. Keep
|
||||
its temporary build output, dependency preparation through the existing
|
||||
build, and prepared delivery arguments.
|
||||
Exercise `TestWoodpeckerSourceDeliveryTemplateSelfBootstraps` with a foreign
|
||||
target; log and assert the host pair at the real `go build`, retain actual
|
||||
execution, and keep the checkout, cleanup, and dependency preparation
|
||||
assertions.
|
||||
3. Update the manual delivery retry in the [maintainer runbook](../release.md).
|
||||
After fetching and checking out the tagged version, run host selection,
|
||||
dependency download, build, and delivery inside a subshell so the caller's
|
||||
target environment stays intact. Query with `GOTOOLCHAIN=local` as above;
|
||||
make the sequence stop on failure inside the subshell. Preserve the
|
||||
dependency download before the offline build, the build-only proxy and sum
|
||||
settings, the output path, and the older-tag credential example.
|
||||
|
||||
## Stage 4: Normalize module-based recovery preparation
|
||||
|
||||
1. In [the Go recovery implementation](../../internal/gotool/recovery.go),
|
||||
keep the existing registered-tool, API-only, and no-dependency selections.
|
||||
Have `RecoveryTool` return `(*RecoveryInvocation, error)` within the
|
||||
internal package. Give the descriptor `Directory string` and
|
||||
`Args []string` fields. A nil result still selects the native executable fallback.
|
||||
Set the directory to the original module for a registered tool or the
|
||||
durable bootstrap root for an API-only consumer. Put `tool -modfile=`
|
||||
and the existing full command package path in `Args`; the caller supplies
|
||||
the working directory. Preserve the selected version, local and versioned
|
||||
replacements, private module and sum files, and their existing lifetime.
|
||||
2. Immediately before the API-only branch runs `go list -deps`, query
|
||||
`go env -json GOHOSTOS GOHOSTARCH` in its private root with its existing
|
||||
`GOWORK=off` and `GOTOOLCHAIN=local` overrides. Reuse the internal
|
||||
`discover` runner with the existing context. Decode both fields, treat
|
||||
missing values as a preparation error, and add only `GOOS` and `GOARCH`
|
||||
to the environment of the dependency command. Keep the target-independent
|
||||
module-selection query and the registered-tool early return. Use the
|
||||
existing preparation error and cleanup paths; leave candidate Go settings,
|
||||
proxy policy, and fallback selection as they are.
|
||||
3. Update the direct `RecoveryTool` callers in
|
||||
[the handoff tests](../../cmd/releasekit/publication_handoff_test.go) to run
|
||||
the descriptor's arguments in its directory. In
|
||||
`TestRecoveryToolPreservesVersionedReplacement`, set a foreign target for
|
||||
the preparation call, log the target of the real `go list -deps` through a
|
||||
delegating shim, and verify the host pair there. Run the test's subsequent
|
||||
direct tool invocation with the host pair and retain its version and
|
||||
metadata assertions. Retain the source-module and vendored API-only cases.
|
||||
Update the CLI caller in Stage 5.
|
||||
|
||||
## Stage 5: Normalize the printed recovery command when it runs
|
||||
|
||||
1. In `selectedPublicationRecoveryCommand` in
|
||||
[the CLI](../../cmd/releasekit/main.go), render the descriptor as one
|
||||
copyable POSIX shell command. Use this structure, with `<dir>`,
|
||||
`<package>`, and `<record>` standing for shell-quoted operands:
|
||||
|
||||
```sh
|
||||
(export GOWORK=off GOTOOLCHAIN=local && releasekit_host_os=$(go -C <dir> env GOHOSTOS) && releasekit_host_arch=$(go -C <dir> env GOHOSTARCH) && GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch" go -C <dir> tool '-modfile=' <package> reconcile --record <record>)
|
||||
```
|
||||
|
||||
Build the common `go -C` prefix from the descriptor directory and render
|
||||
its tool arguments using the existing diagnostic redaction and shell
|
||||
quoting. Keep the explicit absolute record operand. Both host queries run
|
||||
in the invocation directory with the same Go settings as the final command.
|
||||
A failed query stops the command, and the subshell returns the final command
|
||||
status when launched. Leave the native executable command, handoff timeout
|
||||
and cancellation behavior, and existing fallback and diagnostic behavior
|
||||
intact.
|
||||
2. In `TestColdGoToolRecoverySurvivesLauncherExit`, execute the printed
|
||||
command in a separate shell with a foreign target, after the original
|
||||
launcher and its temporary files are gone. Exercise both registered and
|
||||
API-only handoffs and retain status checks for 0 through 3. For the
|
||||
alternate-record cases, replace the quoted original record operand inside
|
||||
the command instead of appending flags after the closing subshell.
|
||||
Retain the vendored API-only, native fallback, record-selection, and
|
||||
redaction checks. Exercise the printed command itself; running the Go
|
||||
descriptor directly would miss a regression in the shell recipe.
|
||||
|
||||
## Stage 6: Align consumer guidance and verify the sprint
|
||||
|
||||
1. In the [adoption guide](../consumers/adoption.md), make the minimal
|
||||
launcher use the Stage 1 recipe followed by `exec go tool releasekit "$@"`.
|
||||
Run it from the selected module, retaining that consumer's workspace and
|
||||
toolchain settings. Move the registration smoke check to follow the launcher
|
||||
example and invoke `version` through that launcher. In the
|
||||
[multi-module README](../../examples/multi-module/README.md), apply the same
|
||||
recipe to the direct `check` smoke commands: enter each selected module in
|
||||
its own subshell, select the host there, then run the existing check with
|
||||
its repository-root profile and version. Preserve the source-selected
|
||||
fixture instructions and avoid turning the smoke check into publication.
|
||||
2. Align the [consumer delivery guide](../consumers/delivery.md),
|
||||
[CLI contract](../cli.md), and [internal Go tool guide](../internal/gotool.md)
|
||||
with the maintained recipes and handoff behavior. Explain that consumers
|
||||
update copied launchers and templates separately from upgrading the module
|
||||
dependency. ReleaseKit tests generic bootstrap behavior; consumers can
|
||||
focus their own wiring tests on selectors and profile mapping. Profile
|
||||
build targets continue to select application binaries.
|
||||
3. Run the focused launcher, workflow, and recovery fixtures from the preceding
|
||||
stages with the existing local Git and module proxy fixtures. Run
|
||||
`go test ./...` and `go run ./internal/apicheck` as documented in the
|
||||
[development guide](../development.md#validation). Check shell syntax,
|
||||
including Woodpecker command blocks after interpolation, local documentation
|
||||
links, and `git diff --check`. Use the existing offline fixtures; no hosted
|
||||
delivery, live publication, or external runner is needed.
|
||||
4. Include the following entry in the next release note during release
|
||||
preparation, when its version is selected: “Maintained launchers and
|
||||
generated Go recovery commands now use the host target to prepare and run
|
||||
ReleaseKit. Update copied launchers and CI templates to get the fix;
|
||||
release profiles still select application build targets.”
|
||||
Reference in New Issue
Block a user