Compile packages for selected targets
This commit is contained in:
23
check.go
23
check.go
@@ -49,6 +49,11 @@ type GoValidationSpec struct {
|
|||||||
// FullVet runs go vet after the suite instead of the test-time vet pass.
|
// FullVet runs go vet after the suite instead of the test-time vet pass.
|
||||||
// Failures use the go-vet validation check identity.
|
// Failures use the go-vet validation check identity.
|
||||||
FullVet bool
|
FullVet bool
|
||||||
|
// CompileTargets checks that every package builds for each complete target
|
||||||
|
// pair after testing and vet. Compilation uses CGO_ENABLED=0 and does not
|
||||||
|
// produce artifacts or execute target binaries. Failures use the
|
||||||
|
// package-compile validation check identity.
|
||||||
|
CompileTargets []Target
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check validates one local, committed release candidate. It requires a clean
|
// Check validates one local, committed release candidate. It requires a clean
|
||||||
@@ -56,8 +61,11 @@ type GoValidationSpec struct {
|
|||||||
// and an unused local tag. It always runs the offline Go test suite with the
|
// and an unused local tag. It always runs the offline Go test suite with the
|
||||||
// available compatible local Go toolchain and Go host target, then calls
|
// available compatible local Go toolchain and Go host target, then calls
|
||||||
// AdditionalChecks when supplied. Validation.Go.Race runs that one suite with
|
// AdditionalChecks when supplied. Validation.Go.Race runs that one suite with
|
||||||
// the race detector, and Validation.Go.FullVet runs go vet after the suite
|
// the race detector, Validation.Go.FullVet runs go vet after the suite instead
|
||||||
// instead of test-time vet. Their zero values preserve the ordinary suite. If
|
// of test-time vet, and Validation.Go.CompileTargets compiles all packages for
|
||||||
|
// each selected GOOS/GOARCH pair after tests and vet with cgo disabled. Package
|
||||||
|
// compilation produces no artifacts and does not execute target binaries.
|
||||||
|
// Their zero values preserve the ordinary suite. If
|
||||||
// Build selects targets, Check builds them after validation in temporary
|
// Build selects targets, Check builds them after validation in temporary
|
||||||
// storage outside the worktree, then rechecks
|
// storage outside the worktree, then rechecks
|
||||||
// branch, HEAD, worktree, index, and local tag state. Success establishes local
|
// branch, HEAD, worktree, index, and local tag state. Success establishes local
|
||||||
@@ -75,18 +83,23 @@ func Check(ctx context.Context, request ReleaseRequest) (Candidate, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func candidateRequest(request ReleaseRequest) candidate.Request {
|
func candidateRequest(request ReleaseRequest) candidate.Request {
|
||||||
return candidate.Request{
|
result := candidate.Request{
|
||||||
RepositoryDir: request.RepositoryDir,
|
RepositoryDir: request.RepositoryDir,
|
||||||
Product: request.Product,
|
Product: request.Product,
|
||||||
Version: request.Version,
|
Version: request.Version,
|
||||||
Validation: validation.Settings{
|
Validation: validation.Settings{
|
||||||
Race: request.Validation.Go.Race,
|
Race: request.Validation.Go.Race,
|
||||||
FullVet: request.Validation.Go.FullVet,
|
FullVet: request.Validation.Go.FullVet,
|
||||||
|
CompileTargets: make([]validation.Target, len(request.Validation.Go.CompileTargets)),
|
||||||
},
|
},
|
||||||
AdditionalChecks: candidateChecks(request.AdditionalChecks),
|
AdditionalChecks: candidateChecks(request.AdditionalChecks),
|
||||||
AfterValidate: internalbuild.CandidateCallback(internalBuildSpec(request.Build), request.Output),
|
AfterValidate: internalbuild.CandidateCallback(internalBuildSpec(request.Build), request.Output),
|
||||||
Output: request.Output,
|
Output: request.Output,
|
||||||
}
|
}
|
||||||
|
for index, target := range request.Validation.Go.CompileTargets {
|
||||||
|
result.Validation.CompileTargets[index] = validation.Target{OS: target.OS, Arch: target.Arch}
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func candidateChecks(checks func(context.Context, Candidate) error) func(context.Context, candidate.Candidate) error {
|
func candidateChecks(checks func(context.Context, Candidate) error) func(context.Context, candidate.Candidate) error {
|
||||||
|
|||||||
@@ -130,8 +130,9 @@ type ValidationSettings struct {
|
|||||||
|
|
||||||
// GoValidationSettings selects checks performed by the Go toolchain.
|
// GoValidationSettings selects checks performed by the Go toolchain.
|
||||||
type GoValidationSettings struct {
|
type GoValidationSettings struct {
|
||||||
Race bool `json:"race,omitempty"`
|
Race bool `json:"race,omitempty"`
|
||||||
FullVet bool `json:"full_vet,omitempty"`
|
FullVet bool `json:"full_vet,omitempty"`
|
||||||
|
CompileTargets []string `json:"compile_targets,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeliverySettings selects the hosted Gitea release destination.
|
// DeliverySettings selects the hosted Gitea release destination.
|
||||||
@@ -219,6 +220,17 @@ func LoadSettings(repoDir, configPath string) (Settings, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if settings.Validation != nil {
|
||||||
|
for i, target := range settings.Validation.Go.CompileTargets {
|
||||||
|
if strings.Count(target, "/") != 1 {
|
||||||
|
return Settings{}, fmt.Errorf("profile %q: validation.go.compile_targets[%d] must be GOOS/GOARCH", configPath, i)
|
||||||
|
}
|
||||||
|
goos, goarch, _ := strings.Cut(target, "/")
|
||||||
|
if strings.TrimSpace(goos) == "" || strings.TrimSpace(goarch) == "" {
|
||||||
|
return Settings{}, fmt.Errorf("profile %q: validation.go.compile_targets[%d] must be GOOS/GOARCH", configPath, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return settings, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func TestLoadSettingsDefaultsRemoteAndConfigPath(t *testing.T) {
|
|||||||
func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
||||||
repoDir := t.TempDir()
|
repoDir := t.TempDir()
|
||||||
profilePath := filepath.Join(repoDir, "releasekit.json")
|
profilePath := filepath.Join(repoDir, "releasekit.json")
|
||||||
if err := os.WriteFile(profilePath, []byte(`{"product":"Example","validation":{"go":{"race":true,"full_vet":true}}}`), 0o600); err != nil {
|
if err := os.WriteFile(profilePath, []byte(`{"product":"Example","validation":{"go":{"race":true,"full_vet":true,"compile_targets":["linux/amd64","linux/arm64","darwin/arm64"]}}}`), 0o600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +155,9 @@ func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
|||||||
if settings.Validation == nil || !settings.Validation.Go.Race || !settings.Validation.Go.FullVet {
|
if settings.Validation == nil || !settings.Validation.Go.Race || !settings.Validation.Go.FullVet {
|
||||||
t.Fatalf("validation settings = %#v, want Go race and full vet enabled", settings.Validation)
|
t.Fatalf("validation settings = %#v, want Go race and full vet enabled", settings.Validation)
|
||||||
}
|
}
|
||||||
|
if got, want := strings.Join(settings.Validation.Go.CompileTargets, ","), "linux/amd64,linux/arm64,darwin/arm64"; got != want {
|
||||||
|
t.Fatalf("compile targets = %q, want %q", got, want)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExampleProfileLoads(t *testing.T) {
|
func TestExampleProfileLoads(t *testing.T) {
|
||||||
@@ -207,6 +210,10 @@ func TestLoadSettingsRejectsInvalidProfiles(t *testing.T) {
|
|||||||
{name: "unknown top-level field", content: `{"product":"Example","future":{}}`, want: "unknown field"},
|
{name: "unknown top-level field", content: `{"product":"Example","future":{}}`, want: "unknown field"},
|
||||||
{name: "build package required", content: `{"product":"Example","build":{"binary_name":"example","targets":["linux/amd64"]}}`, want: "build.main_package is required"},
|
{name: "build package required", content: `{"product":"Example","build":{"binary_name":"example","targets":["linux/amd64"]}}`, want: "build.main_package is required"},
|
||||||
{name: "build target format", content: `{"product":"Example","build":{"main_package":".","binary_name":"example","targets":["linux"]}}`, want: "must be GOOS/GOARCH"},
|
{name: "build target format", content: `{"product":"Example","build":{"main_package":".","binary_name":"example","targets":["linux"]}}`, want: "must be GOOS/GOARCH"},
|
||||||
|
{name: "compile target format", content: `{"product":"Example","validation":{"go":{"compile_targets":["linux"]}}}`, want: "validation.go.compile_targets[0] must be GOOS/GOARCH"},
|
||||||
|
{name: "compile target missing architecture", content: `{"product":"Example","validation":{"go":{"compile_targets":["linux/"]}}}`, want: "validation.go.compile_targets[0] must be GOOS/GOARCH"},
|
||||||
|
{name: "compile target missing operating system", content: `{"product":"Example","validation":{"go":{"compile_targets":["/amd64"]}}}`, want: "validation.go.compile_targets[0] must be GOOS/GOARCH"},
|
||||||
|
{name: "compile target repeated separator", content: `{"product":"Example","validation":{"go":{"compile_targets":["linux/amd64/arm64"]}}}`, want: "validation.go.compile_targets[0] must be GOOS/GOARCH"},
|
||||||
{name: "unsafe binary name", content: `{"product":"Example","build":{"main_package":".","binary_name":"../example","targets":["linux/amd64"]}}`, want: "filename component"},
|
{name: "unsafe binary name", content: `{"product":"Example","build":{"main_package":".","binary_name":"../example","targets":["linux/amd64"]}}`, want: "filename component"},
|
||||||
{name: "unknown command field", content: `{"product":"Example","checks":[{"args":["true"],"shell":true}]}`, want: "unknown field"},
|
{name: "unknown command field", content: `{"product":"Example","checks":[{"args":["true"],"shell":true}]}`, want: "unknown field"},
|
||||||
{name: "command is required", content: `{"product":"Example","checks":[{"args":[]}]}`, want: "must include a command"},
|
{name: "command is required", content: `{"product":"Example","checks":[{"args":[]}]}`, want: "must include a command"},
|
||||||
|
|||||||
@@ -159,12 +159,18 @@ func validationSpec(settings *cli.ValidationSettings) releasekit.ValidationSpec
|
|||||||
if settings == nil {
|
if settings == nil {
|
||||||
return releasekit.ValidationSpec{}
|
return releasekit.ValidationSpec{}
|
||||||
}
|
}
|
||||||
return releasekit.ValidationSpec{
|
result := releasekit.ValidationSpec{
|
||||||
Go: releasekit.GoValidationSpec{
|
Go: releasekit.GoValidationSpec{
|
||||||
Race: settings.Go.Race,
|
Race: settings.Go.Race,
|
||||||
FullVet: settings.Go.FullVet,
|
FullVet: settings.Go.FullVet,
|
||||||
|
CompileTargets: make([]releasekit.Target, len(settings.Go.CompileTargets)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
for index, target := range settings.Go.CompileTargets {
|
||||||
|
goos, goarch, _ := strings.Cut(target, "/")
|
||||||
|
result.Go.CompileTargets[index] = releasekit.Target{OS: goos, Arch: goarch}
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func candidateFailureText(err error) string {
|
func candidateFailureText(err error) string {
|
||||||
|
|||||||
@@ -116,12 +116,21 @@ func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
|||||||
request := candidateRequest(cli.Invocation{Repository: "repository", Version: "v1.2.3"}, cli.Settings{
|
request := candidateRequest(cli.Invocation{Repository: "repository", Version: "v1.2.3"}, cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Validation: &cli.ValidationSettings{
|
Validation: &cli.ValidationSettings{
|
||||||
Go: cli.GoValidationSettings{Race: true, FullVet: true},
|
Go: cli.GoValidationSettings{Race: true, FullVet: true, CompileTargets: []string{"linux/amd64", "darwin/arm64"}},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
if !request.Validation.Go.Race || !request.Validation.Go.FullVet {
|
if !request.Validation.Go.Race || !request.Validation.Go.FullVet {
|
||||||
t.Fatalf("candidate request validation = %#v, want Go race and full vet enabled", request.Validation)
|
t.Fatalf("candidate request validation = %#v, want Go race and full vet enabled", request.Validation)
|
||||||
}
|
}
|
||||||
|
wantTargets := []releasekit.Target{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}}
|
||||||
|
if len(request.Validation.Go.CompileTargets) != len(wantTargets) {
|
||||||
|
t.Fatalf("candidate request compile targets = %#v, want %#v", request.Validation.Go.CompileTargets, wantTargets)
|
||||||
|
}
|
||||||
|
for index := range wantTargets {
|
||||||
|
if request.Validation.Go.CompileTargets[index] != wantTargets[index] {
|
||||||
|
t.Fatalf("candidate request compile targets = %#v, want %#v", request.Validation.Go.CompileTargets, wantTargets)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCandidateCommandsPresentValidationDiagnosticsOnce(t *testing.T) {
|
func TestCandidateCommandsPresentValidationDiagnosticsOnce(t *testing.T) {
|
||||||
@@ -434,7 +443,11 @@ func TestDeliverPageOnlyCreatesAndReusesRelease(t *testing.T) {
|
|||||||
state := &deliveryServerState{}
|
state := &deliveryServerState{}
|
||||||
server := newDeliveryServer(t, state)
|
server := newDeliveryServer(t, state)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
settings := cli.Settings{Product: "Example Product", Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"}}
|
settings := cli.Settings{
|
||||||
|
Product: "Example Product",
|
||||||
|
Validation: &cli.ValidationSettings{Go: cli.GoValidationSettings{CompileTargets: []string{"linux/amd64"}}},
|
||||||
|
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
|
}
|
||||||
writeDeliveryProfile(t, repository, settings)
|
writeDeliveryProfile(t, repository, settings)
|
||||||
state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD")
|
state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD")
|
||||||
cliGitRun(t, repository, "tag", "v1.2.3") // Historical lightweight tags are valid delivery inputs.
|
cliGitRun(t, repository, "tag", "v1.2.3") // Historical lightweight tags are valid delivery inputs.
|
||||||
|
|||||||
12
docs/cli.md
12
docs/cli.md
@@ -30,6 +30,13 @@ pass after successful tests when `validation.go.full_vet` is true. It then runs
|
|||||||
configured additional checks in profile order. It does not inspect remote
|
configured additional checks in profile order. It does not inspect remote
|
||||||
availability or publish a tag.
|
availability or publish a tag.
|
||||||
|
|
||||||
|
When `validation.go.compile_targets` is selected, `check` compiles all packages
|
||||||
|
for each configured `GOOS/GOARCH` pair after tests and full vet, using static
|
||||||
|
mode with cgo disabled. This check produces no artifacts and does not execute
|
||||||
|
target binaries. Compilation failures use the `package-compile` identity and
|
||||||
|
show the selected target. Any artifact build selected by the separate `build`
|
||||||
|
setting runs after shared validation and configured checks.
|
||||||
|
|
||||||
`check` writes errors and diagnostics to standard error. It returns status 0
|
`check` writes errors and diagnostics to standard error. It returns status 0
|
||||||
after local validation, 1 after a candidate validation failure, and 2 for usage,
|
after local validation, 1 after a candidate validation failure, and 2 for usage,
|
||||||
profile, or invalid-version errors. Shared validation failures identify the
|
profile, or invalid-version errors. Shared validation failures identify the
|
||||||
@@ -59,6 +66,11 @@ bounded reconciliation determines whether the exact tag was accepted.
|
|||||||
Shared validation failures use the same check and finding presentation as
|
Shared validation failures use the same check and finding presentation as
|
||||||
`check`, alongside publication state and phase.
|
`check`, alongside publication state and phase.
|
||||||
|
|
||||||
|
When `validation.go.compile_targets` is selected, package compilation runs
|
||||||
|
after tests and full vet and before configured checks. Each failure is reported
|
||||||
|
as `package-compile` with its selected target; this does not create build
|
||||||
|
artifacts or execute target binaries.
|
||||||
|
|
||||||
The command returns status 0 for confirmed publication, 1 for a known
|
The command returns status 0 for confirmed publication, 1 for a known
|
||||||
publication failure or conflict, 2 for usage, profile, or invalid-version
|
publication failure or conflict, 2 for usage, profile, or invalid-version
|
||||||
errors, and 3 when the push outcome remains unknown. Unknown status must be
|
errors, and 3 when the push outcome remains unknown. Unknown status must be
|
||||||
|
|||||||
@@ -30,13 +30,24 @@ The optional `validation` object selects shared checks implemented by ReleaseKit
|
|||||||
`validation.go.race` enables the race detector for the single standard Go test
|
`validation.go.race` enables the race detector for the single standard Go test
|
||||||
suite. `validation.go.full_vet` runs a separate native `go vet` pass after that
|
suite. `validation.go.full_vet` runs a separate native `go vet` pass after that
|
||||||
suite succeeds; ReleaseKit disables test-time vet for that suite to avoid
|
suite succeeds; ReleaseKit disables test-time vet for that suite to avoid
|
||||||
running vet twice. When `full_vet` is omitted or false, the test command keeps
|
running vet twice. `validation.go.compile_targets` is an ordered array of
|
||||||
its existing vet behavior. Both settings are independent. For example:
|
`GOOS/GOARCH` pairs. After tests and selected full vet pass, ReleaseKit compiles
|
||||||
|
all packages for each target with cgo disabled. Each pair must include both
|
||||||
|
components; the selected Go toolchain reports unsupported complete targets.
|
||||||
|
This source compatibility check does not name or produce binaries, run a target
|
||||||
|
executable, or create an artifact manifest. Omitted or empty `compile_targets`
|
||||||
|
selects no package-compilation pass. When `full_vet` is omitted or false, the
|
||||||
|
test command keeps its existing vet behavior. These settings are independent.
|
||||||
|
For example:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"product": "Example",
|
"product": "Example",
|
||||||
"validation": {"go": {"race": true, "full_vet": true}}
|
"validation": {"go": {
|
||||||
|
"race": true,
|
||||||
|
"full_vet": true,
|
||||||
|
"compile_targets": ["linux/amd64", "linux/arm64"]
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -52,8 +63,12 @@ The `check` and `publish` commands run one
|
|||||||
access disabled, adding `-race` when `validation.go.race` is true. When
|
access disabled, adding `-race` when `validation.go.race` is true. When
|
||||||
`validation.go.full_vet` is true, they add `-vet=off` to that test command and
|
`validation.go.full_vet` is true, they add `-vet=off` to that test command and
|
||||||
run native `go vet -mod=readonly ./...` after successful tests. They then run
|
run native `go vet -mod=readonly ./...` after successful tests. They then run
|
||||||
each configured `checks` command once, in order, as an additional product
|
`go build -mod=readonly -o <os.DevNull> ./...` in static mode for each
|
||||||
check. Selected-target builds run after shared validation and product checks.
|
`validation.go.compile_targets` entry, in profile order. Compile failures use
|
||||||
|
the `package-compile` identity and include the selected target. Configured
|
||||||
|
`checks` commands run afterward, once each in order, as additional product
|
||||||
|
checks. Artifact builds selected by the separate `build` setting run after
|
||||||
|
shared validation and product checks.
|
||||||
Do not repeat the standard suite or vet in `checks`; doing so repeats work. The
|
Do not repeat the standard suite or vet in `checks`; doing so repeats work. The
|
||||||
`build` command uses the selected build settings and writes binaries to `dist` unless
|
`build` command uses the selected build settings and writes binaries to `dist` unless
|
||||||
`--output` selects another path.
|
`--output` selects another path.
|
||||||
|
|||||||
@@ -31,9 +31,13 @@ ReleaseKit runs one `go test -mod=readonly -count=1 ./...` suite with the
|
|||||||
available compatible local Go toolchain. Set `Validation.Go.Race` to run that
|
available compatible local Go toolchain. Set `Validation.Go.Race` to run that
|
||||||
suite with `-race`; set `Validation.Go.FullVet` to run
|
suite with `-race`; set `Validation.Go.FullVet` to run
|
||||||
`go vet -mod=readonly ./...` after successful tests, with test-time vet disabled
|
`go vet -mod=readonly ./...` after successful tests, with test-time vet disabled
|
||||||
for the suite. It calls `AdditionalChecks` once after shared validation when
|
for the suite. Set `Validation.Go.CompileTargets` to compile packages for each
|
||||||
supplied. Their zero values retain the ordinary native test behavior. ReleaseKit
|
complete target pair after tests and vet. Package compilation uses
|
||||||
does not download a different toolchain automatically. If race prerequisites
|
`CGO_ENABLED=0`, writes no artifact, and does not execute a target binary; its
|
||||||
|
failures use the `package-compile` identity and include the target. The zero
|
||||||
|
values retain the ordinary native test behavior and select no compilation.
|
||||||
|
ReleaseKit calls `AdditionalChecks` once after shared validation when supplied.
|
||||||
|
It does not download a different toolchain automatically. If race prerequisites
|
||||||
are unavailable, the `go-test` check fails rather than being skipped; a selected
|
are unavailable, the `go-test` check fails rather than being skipped; a selected
|
||||||
full-vet failure is reported as `go-vet`. The suite uses the Go host target,
|
full-vet failure is reported as `go-vet`. The suite uses the Go host target,
|
||||||
disables workspace and module network access, and ignores ambient `GOFLAGS`,
|
disables workspace and module network access, and ignores ambient `GOFLAGS`,
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ If race prerequisites are unavailable, the selected suite fails as `go-test`
|
|||||||
instead of silently running without the race detector. Full-vet failures are
|
instead of silently running without the race detector. Full-vet failures are
|
||||||
reported as `go-vet` and stop publication before tag creation.
|
reported as `go-vet` and stop publication before tag creation.
|
||||||
|
|
||||||
|
Set `Validation.Go.CompileTargets` to compile packages for each complete target
|
||||||
|
pair after tests and vet. This uses `CGO_ENABLED=0`, produces no artifact, and
|
||||||
|
does not execute target binaries. A failure is reported as `package-compile`
|
||||||
|
with the selected target and prevents tag creation.
|
||||||
|
|
||||||
If `Build` selects targets, ReleaseKit verifies them after the standard suite
|
If `Build` selects targets, ReleaseKit verifies them after the standard suite
|
||||||
and additional checks, before creating the tag. The operation then rechecks the
|
and additional checks, before creating the tag. The operation then rechecks the
|
||||||
candidate and remote guards, creates an annotated tag for the captured commit,
|
candidate and remote guards, creates an annotated tag for the captured commit,
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ baseline values. Effective `GOCACHE`, `GOCACHEPROG`, `GOMODCACHE`, `GOPATH`,
|
|||||||
`GOROOT`, and `GOTMPDIR` values are retained. Other process variables remain
|
`GOROOT`, and `GOTMPDIR` values are retained. Other process variables remain
|
||||||
available to child commands.
|
available to child commands.
|
||||||
|
|
||||||
Candidate validation uses native mode for the offline `go test` suite. Builds
|
Candidate validation uses native mode for the offline `go test` suite and
|
||||||
use static mode for selected-target compilation and the native version-check
|
selected full `go vet` pass. Package-compilation checks use static mode for each
|
||||||
build. The native version executable itself runs as a normal child process.
|
selected target and direct Go's output to the operating system null device, so
|
||||||
|
they leave no binary artifact. Artifact builds also use static mode for
|
||||||
|
selected-target compilation, then build the native version-check executable;
|
||||||
|
that executable runs as a normal child process.
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ these implemented packages:
|
|||||||
| `internal/tempdir` | Disposable system temporary storage outside source worktrees. |
|
| `internal/tempdir` | Disposable system temporary storage outside source worktrees. |
|
||||||
| `internal/process` | Cancellable child-process execution with per-command environment overlays and removals. |
|
| `internal/process` | Cancellable child-process execution with per-command environment overlays and removals. |
|
||||||
| `internal/preparation` | Coordination for expected-commit tag checkout and Go module dependency preparation. |
|
| `internal/preparation` | Coordination for expected-commit tag checkout and Go module dependency preparation. |
|
||||||
| `internal/validation` | Shared Go validation with optional race and full-vet selections, structured check failures, and lazy committed-file access for captured candidates; `internal/candidate` retains capture, callback sequencing, and local-state rechecks. |
|
| `internal/validation` | Shared Go validation with optional race, full-vet, and static package-compilation target selections, structured check failures, and lazy committed-file access for captured candidates; `internal/candidate` retains capture, callback sequencing, and local-state rechecks. |
|
||||||
| `internal/publish` | Publication guards, one validation run, annotated tag creation, push, and recovery state. |
|
| `internal/publish` | Publication guards, one validation run, annotated tag creation, push, and recovery state. |
|
||||||
| `internal/runtimeinfo` | Private version lookup used by the runtime facade. |
|
| `internal/runtimeinfo` | Private version lookup used by the runtime facade. |
|
||||||
| `internal/versioncontract` | Shared stable-version rules and linker-symbol identity. |
|
| `internal/versioncontract` | Shared stable-version rules and linker-symbol identity. |
|
||||||
@@ -78,7 +78,7 @@ not a separate release matrix:
|
|||||||
| Capability | Implementation owner | Existing evidence |
|
| Capability | Implementation owner | Existing evidence |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Runtime version lookup and injected application version | `buildinfo`, `internal/runtimeinfo`, `internal/versioncontract`, `internal/build` | `TestVersionInConsumerExecutable`, `TestVersion` |
|
| Runtime version lookup and injected application version | `buildinfo`, `internal/runtimeinfo`, `internal/versioncontract`, `internal/build` | `TestVersionInConsumerExecutable`, `TestVersion` |
|
||||||
| Candidate capture, shared validation, public diagnostics, additional checks, and rechecks | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `internal/validation`, `cmd/releasekit/internal/cli` | `TestRunUsesOneSuiteWithSelectedRaceSetting`, `TestRunReportsRacePrerequisiteFailureWithoutFallback`, `TestRunExecutesRaceEnabledSuiteOnHost`, `TestCandidateRequestMapsRaceValidation`, `TestLoadSettingsReadsRaceValidation`, `TestCheckUsesSelectedRaceSuiteBeforeAdditionalChecks`, `TestCheckCapturesCandidateAndCallsAdditionalChecksOnce`, `TestCheckReturnsPublicValidationError`, `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishFailedStandardSuiteSkipsAdditionalChecksAndTagCreation`, `TestPublishAdditionalCheckFailurePreservesCauseAndPreventsTagCreation`, `TestCheckCommandBindsConfiguredChecksToCapturedCandidate`, `TestPublishCommandPublishesConfiguredCandidate`, `TestCandidateCommandsPresentValidationDiagnosticsOnce` |
|
| Candidate capture, shared validation, public diagnostics, additional checks, and rechecks | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `internal/validation`, `cmd/releasekit/internal/cli` | `TestRunUsesOneSuiteWithSelectedRaceSetting`, `TestRunReportsRacePrerequisiteFailureWithoutFallback`, `TestRunExecutesRaceEnabledSuiteOnHost`, `TestRunCompilesTargetsAfterTestAndVetInSelectionOrder`, `TestRunAttributesTargetCompileFailureAndStops`, `TestRunCompilesSourceOnlyAndExecutablePackagesWithoutArtifacts`, `TestCandidateRequestMapsGoValidation`, `TestLoadSettingsReadsGoValidation`, `TestCheckUsesSelectedRaceSuiteBeforeAdditionalChecks`, `TestCheckCapturesCandidateAndCallsAdditionalChecksOnce`, `TestCheckReturnsPublicValidationError`, `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishFailedStandardSuiteSkipsAdditionalChecksAndTagCreation`, `TestPublishAdditionalCheckFailurePreservesCauseAndPreventsTagCreation`, `TestCheckCommandBindsConfiguredChecksToCapturedCandidate`, `TestPublishCommandPublishesConfiguredCandidate`, `TestCandidateCommandsPresentValidationDiagnosticsOnce`, `TestDeliverPageOnlyCreatesAndReusesRelease` |
|
||||||
| Shared Go settings for validation and builds | `internal/gotool`, `internal/process`, `internal/candidate`, `internal/build` | `TestDefaultValidationDoesNotInheritGOFLAGSRunFilter`, `TestNativeAndStaticCommandsUseOwnedBaseline`, `TestPersistentSettingsPreserveCachesAndNativeCTools`, `TestBuildIgnoresAmbientAndPersistentArchitectureTuning` |
|
| Shared Go settings for validation and builds | `internal/gotool`, `internal/process`, `internal/candidate`, `internal/build` | `TestDefaultValidationDoesNotInheritGOFLAGSRunFilter`, `TestNativeAndStaticCommandsUseOwnedBaseline`, `TestPersistentSettingsPreserveCachesAndNativeCTools`, `TestBuildIgnoresAmbientAndPersistentArchitectureTuning` |
|
||||||
| Authoritative endpoint and exact annotated tag publication | `releasekit.Publish`, `internal/publish`, `internal/git` | `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestResolveUsesPushURLAndQueriesWithoutMutation` |
|
| Authoritative endpoint and exact annotated tag publication | `releasekit.Publish`, `internal/publish`, `internal/git` | `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestResolveUsesPushURLAndQueriesWithoutMutation` |
|
||||||
| Publication partial and uncertain recovery | `internal/publish`, `internal/git` | `TestPublishRejectedPushKeepsLocalTagAndReportsNotPublished`, `TestPublishLostAcknowledgmentConfirmsExactRemoteObject`, `TestPublishLostAcknowledgmentWithChangedEndpointRemainsUnknown`, `TestPublishUnavailableReconciliationRemainsUnknownAndPreservesPushError` |
|
| Publication partial and uncertain recovery | `internal/publish`, `internal/git` | `TestPublishRejectedPushKeepsLocalTagAndReportsNotPublished`, `TestPublishLostAcknowledgmentConfirmsExactRemoteObject`, `TestPublishLostAcknowledgmentWithChangedEndpointRemainsUnknown`, `TestPublishUnavailableReconciliationRemainsUnknownAndPreservesPushError` |
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ candidate path.
|
|||||||
|
|
||||||
The executor runs one offline Go suite, optionally with the race detector when
|
The executor runs one offline Go suite, optionally with the race detector when
|
||||||
selected. When full vet is selected, it disables test-time vet for that suite
|
selected. When full vet is selected, it disables test-time vet for that suite
|
||||||
and runs native `go vet -mod=readonly ./...` after the suite succeeds, using the
|
and runs native `go vet -mod=readonly ./...` after the suite succeeds. It then
|
||||||
same discovered Go tool and native environment. Test and vet failures retain
|
runs `go build -mod=readonly -o <os.DevNull> ./...` in static mode for each
|
||||||
their separate `go-test` and `go-vet` identities and underlying causes. Its
|
selected package-compilation target, preserving target order and stopping on
|
||||||
|
the first failure. This compiles source without producing an artifact or
|
||||||
|
executing a target binary. Test, vet, and package-compilation failures retain
|
||||||
|
their `go-test`, `go-vet`, and `package-compile` identities, selected target
|
||||||
|
where applicable, and underlying causes. Its
|
||||||
request carries the captured repository directory and commit, output writer,
|
request carries the captured repository directory and commit, output writer,
|
||||||
and internal settings. `Request.ReadCandidateTree` lets a
|
and internal settings. `Request.ReadCandidateTree` lets a
|
||||||
selected file check request candidate contents; the Go-only path does not
|
selected file check request candidate contents; the Go-only path does not
|
||||||
@@ -70,13 +74,14 @@ for product behavior.
|
|||||||
## Go command environment reuse
|
## Go command environment reuse
|
||||||
|
|
||||||
The current executor uses the [shared Go command environment](gotool.md) for
|
The current executor uses the [shared Go command environment](gotool.md) for
|
||||||
its offline suite. The [roadmap coordination design](../roadmap/implementation.md#execution-and-ownership)
|
its offline suite, vet pass, and selected-target package compilation. The
|
||||||
owns the proposed test, vet, and package-compilation procedure.
|
[implementation roadmap](../roadmap/implementation.md#execution-and-ownership)
|
||||||
|
records the shared ordering across validation and later file checks.
|
||||||
|
|
||||||
Reuse `internal/gotool` for native and static environments and target handling.
|
Reuse `internal/gotool` for native and static environments and target handling.
|
||||||
Keep toolchain and module-cache discovery in `gotool`; do not reproduce its
|
Keep toolchain and module-cache discovery in `gotool`; do not reproduce its
|
||||||
environment logic in a checker. Build output names, version injection, native
|
environment logic in a checker. Artifact output names, version injection,
|
||||||
smoke checks, and artifact digests remain owned by `internal/build`.
|
native smoke checks, and artifact digests remain owned by `internal/build`.
|
||||||
|
|
||||||
## File-check coordination
|
## File-check coordination
|
||||||
|
|
||||||
|
|||||||
@@ -271,6 +271,11 @@ Protect the selected command relationship without freezing incidental flag order
|
|||||||
|
|
||||||
## Stage 3: Compile packages for selected targets
|
## Stage 3: Compile packages for selected targets
|
||||||
|
|
||||||
|
**Completion:** Complete. `Go.CompileTargets` and profile
|
||||||
|
`validation.go.compile_targets` select static package compilation after tests
|
||||||
|
and vet. Failures use `package-compile` and include the selected target;
|
||||||
|
artifact building remains separate.
|
||||||
|
|
||||||
**Outcome:** Source compatibility checks work independently of artifacts.
|
**Outcome:** Source compatibility checks work independently of artifacts.
|
||||||
|
|
||||||
1. Add `Go.CompileTargets` and string-array profile targets. Convert to internal
|
1. Add `Go.CompileTargets` and string-array profile targets. Convert to internal
|
||||||
|
|||||||
204
internal/validation/compile_test.go
Normal file
204
internal/validation/compile_test.go
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunCompilesTargetsAfterTestAndVetInSelectionOrder(t *testing.T) {
|
||||||
|
logPath := installGoShim(t, "", false)
|
||||||
|
targets := []Target{{OS: "linux", Arch: "amd64"}, {OS: "linux", Arch: "arm64"}, {OS: "darwin", Arch: "arm64"}}
|
||||||
|
err := Run(context.Background(), Request{
|
||||||
|
RepositoryDir: t.TempDir(),
|
||||||
|
Settings: Settings{FullVet: true, CompileTargets: targets},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var commands []string
|
||||||
|
for _, command := range readGoShimLog(t, logPath) {
|
||||||
|
if strings.HasPrefix(command, "test ") || strings.HasPrefix(command, "vet ") || strings.HasPrefix(command, "build ") {
|
||||||
|
commands = append(commands, command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(commands) != len(targets)+2 || !strings.HasPrefix(commands[0], "test ") || !strings.HasPrefix(commands[1], "vet ") {
|
||||||
|
t.Fatalf("validation commands = %q, want test, vet, then one build per target", commands)
|
||||||
|
}
|
||||||
|
for index, target := range targets {
|
||||||
|
command := commands[index+2]
|
||||||
|
if !strings.HasPrefix(command, "build ") {
|
||||||
|
t.Fatalf("command %d = %q, want package build", index+2, command)
|
||||||
|
}
|
||||||
|
for _, argument := range []string{"build", "-mod=readonly", "-o " + os.DevNull, "./..."} {
|
||||||
|
if !strings.Contains(" "+command+" ", " "+argument+" ") {
|
||||||
|
t.Errorf("build command %q does not contain %q", command, argument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got, want := strings.Fields(command)[0], "build"; got != want {
|
||||||
|
t.Errorf("command = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
if target.OS == "" || target.Arch == "" {
|
||||||
|
t.Fatalf("test target is incomplete: %#v", target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
environments, err := os.ReadFile(os.Getenv("RELEASEKIT_GO_ENV_LOG"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got := strings.Split(strings.TrimSpace(string(environments)), "\n")
|
||||||
|
if len(got) != len(targets) {
|
||||||
|
t.Fatalf("static build environments = %q, want %d entries", got, len(targets))
|
||||||
|
}
|
||||||
|
for index, target := range targets {
|
||||||
|
if want := fmt.Sprintf("%s/%s|0", target.OS, target.Arch); got[index] != want {
|
||||||
|
t.Errorf("static build environment %d = %q, want %q", index, got[index], want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunAttributesTargetCompileFailureAndStops(t *testing.T) {
|
||||||
|
logPath := installGoShim(t, "darwin/arm64", false)
|
||||||
|
targets := []Target{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}, {OS: "windows", Arch: "amd64"}}
|
||||||
|
err := Run(context.Background(), Request{
|
||||||
|
RepositoryDir: t.TempDir(),
|
||||||
|
Settings: Settings{FullVet: true, CompileTargets: targets},
|
||||||
|
})
|
||||||
|
var failure *Failure
|
||||||
|
if !errors.As(err, &failure) || failure.Check != PackageCompile {
|
||||||
|
t.Fatalf("Run() error = %v, want a package-compile failure", err)
|
||||||
|
}
|
||||||
|
if len(failure.Findings) != 1 || failure.Findings[0].Target != "darwin/arm64" {
|
||||||
|
t.Fatalf("package compilation findings = %#v, want target darwin/arm64", failure.Findings)
|
||||||
|
}
|
||||||
|
var exitError *exec.ExitError
|
||||||
|
if !errors.As(err, &exitError) {
|
||||||
|
t.Fatalf("Run() error = %v, want the Go command failure preserved", err)
|
||||||
|
}
|
||||||
|
var commands []string
|
||||||
|
for _, command := range readGoShimLog(t, logPath) {
|
||||||
|
if strings.HasPrefix(command, "test ") || strings.HasPrefix(command, "vet ") || strings.HasPrefix(command, "build ") {
|
||||||
|
commands = append(commands, command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(commands) != 4 || !strings.HasPrefix(commands[0], "test ") || !strings.HasPrefix(commands[1], "vet ") || !strings.HasPrefix(commands[2], "build ") || !strings.HasPrefix(commands[3], "build ") {
|
||||||
|
t.Fatalf("validation commands after target failure = %q, want test, vet, two builds", commands)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunRejectsIncompleteCompileTargets(t *testing.T) {
|
||||||
|
installGoShim(t, "", false)
|
||||||
|
for _, target := range []Target{{OS: "linux"}, {Arch: "amd64"}, {OS: " ", Arch: "amd64"}, {OS: "linux/other", Arch: "amd64"}} {
|
||||||
|
err := Run(context.Background(), Request{
|
||||||
|
RepositoryDir: t.TempDir(),
|
||||||
|
Settings: Settings{CompileTargets: []Target{target}},
|
||||||
|
})
|
||||||
|
var failure *Failure
|
||||||
|
if !errors.As(err, &failure) || failure.Check != PackageCompile {
|
||||||
|
t.Errorf("Run(%#v) error = %v, want a package-compile validation failure", target, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(failure.Findings) != 1 || failure.Findings[0].Target != target.OS+"/"+target.Arch {
|
||||||
|
t.Errorf("Run(%#v) findings = %#v, want the incomplete target", target, failure.Findings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCompilesSourceOnlyAndExecutablePackagesWithoutArtifacts(t *testing.T) {
|
||||||
|
fixtures := []struct {
|
||||||
|
name string
|
||||||
|
files map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "source library",
|
||||||
|
files: map[string]string{
|
||||||
|
"library.go": "package fixture\n\nconst Value = 1\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single main",
|
||||||
|
files: map[string]string{
|
||||||
|
"library.go": "package fixture\n\nconst Value = 1\n",
|
||||||
|
"cmd/app/main.go": "package main\n\nfunc main() {}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple mains with repeated basenames",
|
||||||
|
files: map[string]string{
|
||||||
|
"library.go": "package fixture\n\nconst Value = 1\n",
|
||||||
|
"cmd/one/tool/main.go": "package main\n\nfunc main() {}\n",
|
||||||
|
"cmd/two/tool/main.go": "package main\n\nfunc main() {}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, fixture := range fixtures {
|
||||||
|
t.Run(fixture.name, func(t *testing.T) {
|
||||||
|
module := writeCompileFixture(t, fixture.files)
|
||||||
|
before := readCompileFixture(t, module, fixture.files)
|
||||||
|
var targets []Target
|
||||||
|
if fixture.name == "source library" {
|
||||||
|
targets = []Target{{OS: "linux", Arch: "amd64"}, {OS: "linux", Arch: "arm64"}, {OS: "darwin", Arch: "arm64"}}
|
||||||
|
} else {
|
||||||
|
targets = []Target{{OS: "linux", Arch: "amd64"}}
|
||||||
|
}
|
||||||
|
if err := Run(context.Background(), Request{
|
||||||
|
RepositoryDir: module,
|
||||||
|
Settings: Settings{CompileTargets: targets},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("Run() error = %v", err)
|
||||||
|
}
|
||||||
|
after := readCompileFixture(t, module, fixture.files)
|
||||||
|
for path, contents := range before {
|
||||||
|
if string(after[path]) != string(contents) {
|
||||||
|
t.Errorf("candidate file %q changed during compilation", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCompileFixture(t *testing.T, files map[string]string) string {
|
||||||
|
t.Helper()
|
||||||
|
module := t.TempDir()
|
||||||
|
if err := os.WriteFile(filepath.Join(module, "go.mod"), []byte("module example.com/compilefixture\n\ngo 1.25.0\n"), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for path, contents := range files {
|
||||||
|
filename := filepath.Join(module, filepath.FromSlash(path))
|
||||||
|
if err := os.MkdirAll(filepath.Dir(filename), 0o700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filename, []byte(contents), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return module
|
||||||
|
}
|
||||||
|
|
||||||
|
func readCompileFixture(t *testing.T, module string, files map[string]string) map[string][]byte {
|
||||||
|
t.Helper()
|
||||||
|
contents := make(map[string][]byte, len(files)+1)
|
||||||
|
for _, path := range append([]string{"go.mod"}, fixturePaths(files)...) {
|
||||||
|
data, err := os.ReadFile(filepath.Join(module, filepath.FromSlash(path)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
contents[path] = data
|
||||||
|
}
|
||||||
|
return contents
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixturePaths(files map[string]string) []string {
|
||||||
|
paths := make([]string, 0, len(files))
|
||||||
|
for path := range files {
|
||||||
|
paths = append(paths, path)
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ const (
|
|||||||
GoTest Check = "go-test"
|
GoTest Check = "go-test"
|
||||||
// GoVet identifies the separate full vet pass.
|
// GoVet identifies the separate full vet pass.
|
||||||
GoVet Check = "go-vet"
|
GoVet Check = "go-vet"
|
||||||
|
// PackageCompile identifies selected-target package compilation.
|
||||||
|
PackageCompile Check = "package-compile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Finding describes a validation observation at an optional source location.
|
// Finding describes a validation observation at an optional source location.
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ func installGoShim(t *testing.T, failure string, delayVet bool) string {
|
|||||||
}
|
}
|
||||||
directory := t.TempDir()
|
directory := t.TempDir()
|
||||||
logPath := filepath.Join(directory, "go-commands")
|
logPath := filepath.Join(directory, "go-commands")
|
||||||
|
envLogPath := filepath.Join(directory, "go-environment")
|
||||||
settings := map[string]string{
|
settings := map[string]string{
|
||||||
"GOHOSTOS": runtime.GOOS,
|
"GOHOSTOS": runtime.GOOS,
|
||||||
"GOHOSTARCH": runtime.GOARCH,
|
"GOHOSTARCH": runtime.GOARCH,
|
||||||
@@ -250,8 +251,15 @@ if [ "${1-}" = "vet" ] && [ "%s" = "vet" ]; then
|
|||||||
echo "Go vet failed" >&2
|
echo "Go vet failed" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
if [ "${1-}" = "build" ]; then
|
||||||
|
printf '%%s/%%s|%%s\n' "${GOOS-}" "${GOARCH-}" "${CGO_ENABLED-}" >> "$RELEASEKIT_GO_ENV_LOG"
|
||||||
|
if [ "%s" = "build" ] || [ "%s" = "${GOOS-}/${GOARCH-}" ]; then
|
||||||
|
echo "Go build failed" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
%s
|
%s
|
||||||
`, failure, failure, failure, vetDelay)
|
`, failure, failure, failure, failure, failure, vetDelay)
|
||||||
script := fmt.Sprintf(`#!/bin/sh
|
script := fmt.Sprintf(`#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
printf '%%s\n' "$*" >> "$RELEASEKIT_GO_LOG"
|
printf '%%s\n' "$*" >> "$RELEASEKIT_GO_LOG"
|
||||||
@@ -272,6 +280,7 @@ exit 0
|
|||||||
}
|
}
|
||||||
t.Setenv("PATH", path)
|
t.Setenv("PATH", path)
|
||||||
t.Setenv("RELEASEKIT_GO_LOG", logPath)
|
t.Setenv("RELEASEKIT_GO_LOG", logPath)
|
||||||
|
t.Setenv("RELEASEKIT_GO_ENV_LOG", envLogPath)
|
||||||
return logPath
|
return logPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,25 @@ package validation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Settings selects shared validation behavior.
|
// Settings selects shared validation behavior.
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
Race bool
|
Race bool
|
||||||
FullVet bool
|
FullVet bool
|
||||||
|
CompileTargets []Target
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target selects one package-compilation target.
|
||||||
|
type Target struct {
|
||||||
|
OS string
|
||||||
|
Arch string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request identifies the captured source and output used by shared validation.
|
// Request identifies the captured source and output used by shared validation.
|
||||||
@@ -30,6 +40,17 @@ func (request Request) ReadCandidateTree(ctx context.Context) (*CandidateTree, e
|
|||||||
|
|
||||||
// Run executes shared validation for the captured candidate.
|
// Run executes shared validation for the captured candidate.
|
||||||
func Run(ctx context.Context, request Request) error {
|
func Run(ctx context.Context, request Request) error {
|
||||||
|
for _, target := range request.Settings.CompileTargets {
|
||||||
|
if strings.TrimSpace(target.OS) == "" || strings.TrimSpace(target.Arch) == "" || strings.Contains(target.OS, "/") || strings.Contains(target.Arch, "/") {
|
||||||
|
return &Failure{
|
||||||
|
Check: PackageCompile,
|
||||||
|
Findings: []Finding{{
|
||||||
|
Target: target.OS + "/" + target.Arch,
|
||||||
|
Message: "target must be one complete GOOS/GOARCH pair",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
tool, err := gotool.New(ctx, request.RepositoryDir)
|
tool, err := gotool.New(ctx, request.RepositoryDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Failure{Check: GoTest, Cause: err}
|
return &Failure{Check: GoTest, Cause: err}
|
||||||
@@ -52,5 +73,20 @@ func Run(ctx context.Context, request Request) error {
|
|||||||
return &Failure{Check: GoVet, Cause: err}
|
return &Failure{Check: GoVet, Cause: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, target := range request.Settings.CompileTargets {
|
||||||
|
selected := gotool.Target{OS: target.OS, Arch: target.Arch}
|
||||||
|
_, err = tool.Run(ctx, request.RepositoryDir, gotool.Static, selected, request.Output,
|
||||||
|
"build", "-mod=readonly", "-o", os.DevNull, "./...")
|
||||||
|
if err != nil {
|
||||||
|
return &Failure{
|
||||||
|
Check: PackageCompile,
|
||||||
|
Findings: []Finding{{
|
||||||
|
Target: fmt.Sprintf("%s/%s", target.OS, target.Arch),
|
||||||
|
Message: "package compilation failed",
|
||||||
|
}},
|
||||||
|
Cause: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,11 @@ type PublicationResult struct {
|
|||||||
|
|
||||||
// Publish always runs one offline Go test suite for the captured candidate.
|
// Publish always runs one offline Go test suite for the captured candidate.
|
||||||
// Validation.Go.Race enables the race detector for that suite, and
|
// Validation.Go.Race enables the race detector for that suite, and
|
||||||
// Validation.Go.FullVet runs go vet afterward instead of test-time vet. It then
|
// Validation.Go.FullVet runs go vet afterward instead of test-time vet, and
|
||||||
// calls AdditionalChecks when supplied. It optionally builds selected
|
// Validation.Go.CompileTargets compiles all packages for each selected target
|
||||||
|
// with cgo disabled after tests and vet. Compilation produces no artifacts and
|
||||||
|
// does not execute target binaries. It then calls AdditionalChecks when
|
||||||
|
// supplied. It optionally builds selected
|
||||||
// targets in temporary storage outside the worktree and pushes the exact
|
// targets in temporary storage outside the worktree and pushes the exact
|
||||||
// annotated tag object to the selected remote. It returns a result with errors: preflight
|
// annotated tag object to the selected remote. It returns a result with errors: preflight
|
||||||
// failures are not_attempted, failed authoritative guards are conflict, and
|
// failures are not_attempted, failed authoritative guards are conflict, and
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ValidationCheck identifies a shared validation check that failed.
|
// ValidationCheck identifies a shared validation check that failed.
|
||||||
// Current checks include "go-test" and "go-vet".
|
// Current checks include "go-test", "go-vet", and "package-compile".
|
||||||
type ValidationCheck string
|
type ValidationCheck string
|
||||||
|
|
||||||
// ValidationFinding describes a failed check observation at an optional
|
// ValidationFinding describes a failed check observation at an optional
|
||||||
|
|||||||
@@ -9,11 +9,23 @@ import (
|
|||||||
|
|
||||||
func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
||||||
request := candidateRequest(ReleaseRequest{
|
request := candidateRequest(ReleaseRequest{
|
||||||
Validation: ValidationSpec{Go: GoValidationSpec{Race: true, FullVet: true}},
|
Validation: ValidationSpec{Go: GoValidationSpec{
|
||||||
|
Race: true, FullVet: true,
|
||||||
|
CompileTargets: []Target{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}},
|
||||||
|
}},
|
||||||
})
|
})
|
||||||
if !request.Validation.Race || !request.Validation.FullVet {
|
if !request.Validation.Race || !request.Validation.FullVet {
|
||||||
t.Fatalf("candidate request validation = %#v, want race and full vet enabled", request.Validation)
|
t.Fatalf("candidate request validation = %#v, want race and full vet enabled", request.Validation)
|
||||||
}
|
}
|
||||||
|
want := []validation.Target{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}}
|
||||||
|
if len(request.Validation.CompileTargets) != len(want) {
|
||||||
|
t.Fatalf("candidate request compile targets = %#v, want %#v", request.Validation.CompileTargets, want)
|
||||||
|
}
|
||||||
|
for index := range want {
|
||||||
|
if request.Validation.CompileTargets[index] != want[index] {
|
||||||
|
t.Fatalf("candidate request compile targets = %#v, want %#v", request.Validation.CompileTargets, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidationErrorPreservesGoVetIdentity(t *testing.T) {
|
func TestValidationErrorPreservesGoVetIdentity(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user