Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2180aa868e | |||
| bf4e2329ec | |||
| bd1c714935 | |||
| f625674a68 | |||
| 9797f43dc4 | |||
| b5c6638538 | |||
| ec67496569 | |||
| d99fac23e9 | |||
| a3c74188be | |||
| 7dcc9a37c7 | |||
| 68ce3e5a3b | |||
| b00bc41b65 | |||
| fcec6357ff | |||
| 13e1c21386 | |||
| 80a610a5c2 | |||
| d02b9ffeb8 | |||
| 0269648429 | |||
| bd693ca31b | |||
| d8ec9389e0 | |||
| 10028c4abb | |||
| 8e897d7b3e | |||
| a8ffa89011 | |||
| bf07bed2ae | |||
| 373f7a69cd | |||
| b1ce5bf976 |
@@ -31,7 +31,9 @@ Run the offline Go test suite from the repository root with:
|
||||
go test ./...
|
||||
```
|
||||
|
||||
The version command prints ReleaseKit's version:
|
||||
The version command prints ReleaseKit's version. For the `go run` examples
|
||||
below, [select the Go host target](docs/consumers/adoption.md#select-the-go-host-target)
|
||||
if your shell is set to cross-compile.
|
||||
|
||||
```sh
|
||||
go run ./cmd/releasekit version
|
||||
|
||||
@@ -142,16 +142,17 @@ func selectedPublicationRecoveryCommand(ctx context.Context, result releasekit.P
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
command := "env GOWORK=off GOTOOLCHAIN=local go"
|
||||
for _, arg := range args {
|
||||
if args != nil {
|
||||
prefix := "env go -C " + shellQuote(git.DisplayDiagnostic(args.Directory))
|
||||
command := "(export GOWORK=off GOTOOLCHAIN=local && releasekit_host_os=$(" + prefix + " env GOHOSTOS) && releasekit_host_arch=$(" + prefix + " env GOHOSTARCH) && GOOS=\"$releasekit_host_os\" GOARCH=\"$releasekit_host_arch\" " + prefix
|
||||
for _, arg := range args.Args {
|
||||
command += " " + shellQuote(git.DisplayDiagnostic(arg))
|
||||
}
|
||||
path, err := filepath.Abs(git.DisplayDiagnostic(result.RecordPath))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return command + " reconcile --record " + shellQuote(path), nil
|
||||
return command + " reconcile --record " + shellQuote(path) + ")", nil
|
||||
}
|
||||
}
|
||||
return publicationRecoveryCommand(result.RecordPath)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -81,6 +82,11 @@ func TestMultiModuleLauncherUsesSelectedModuleAndProfile(t *testing.T) {
|
||||
shim := `#!/bin/sh
|
||||
set -eu
|
||||
printf 'cwd=%s args=%s\n' "$PWD" "$*" >> "$RELEASEKIT_TEST_GO_LOG"
|
||||
if [ "$1" = env ]; then
|
||||
case "$2" in GOHOSTOS) printf '%s\n' "$RELEASEKIT_TEST_HOST_OS" ;; GOHOSTARCH) printf '%s\n' "$RELEASEKIT_TEST_HOST_ARCH" ;; esac
|
||||
exit 0
|
||||
fi
|
||||
case " $* " in *" -deps "*) printf 'target=%s/%s\n' "$GOOS" "$GOARCH" >> "$RELEASEKIT_TEST_GO_LOG" ;; esac
|
||||
modfile=
|
||||
for argument in "$@"; do
|
||||
case "$argument" in -modfile=*) modfile=${argument#-modfile=} ;; esac
|
||||
@@ -98,6 +104,11 @@ fi
|
||||
t.Setenv("RELEASEKIT_MODULE_MAP", mapPath)
|
||||
t.Setenv("RELEASEKIT_TEST_GO_LOG", goLog)
|
||||
t.Setenv("RELEASEKIT_TEST_TOOL_ARGS", argsLog)
|
||||
t.Setenv("RELEASEKIT_TEST_HOST_OS", runtime.GOOS)
|
||||
t.Setenv("RELEASEKIT_TEST_HOST_ARCH", runtime.GOARCH)
|
||||
foreignOS, foreignArch := foreignTarget()
|
||||
t.Setenv("GOOS", foreignOS)
|
||||
t.Setenv("GOARCH", foreignArch)
|
||||
launcher := filepath.Join(repository, "release.sh")
|
||||
command := exec.Command(launcher, "with space", "v1.2.4")
|
||||
var stdout, stderr bytes.Buffer
|
||||
@@ -115,6 +126,9 @@ fi
|
||||
if !strings.Contains(string(goCommands), "cwd="+wantModule) || !strings.Contains(string(goCommands), "v0.2.299") || strings.Contains(string(goCommands), "v0.2.197") {
|
||||
t.Fatalf("tool bootstrap did not use selected module metadata: %s", goCommands)
|
||||
}
|
||||
if !strings.Contains(string(goCommands), "target="+runtime.GOOS+"/"+runtime.GOARCH) {
|
||||
t.Fatalf("dependency inspection did not use host target: %s", goCommands)
|
||||
}
|
||||
toolArgs, err := os.ReadFile(argsLog)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -134,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")
|
||||
|
||||
@@ -152,6 +170,9 @@ func TestMultiModuleLauncherRunsToolFromSelectedDependency(t *testing.T) {
|
||||
t.Setenv("GOPROXY", "file://"+filepath.ToSlash(proxy))
|
||||
t.Setenv("GOSUMDB", "off")
|
||||
t.Setenv("GOTOOLCHAIN", "local")
|
||||
foreignOS, foreignArch := foreignTarget()
|
||||
t.Setenv("GOOS", foreignOS)
|
||||
t.Setenv("GOARCH", foreignArch)
|
||||
moduleBefore, err := os.ReadFile(filepath.Join(repository, "integrations", "ckconfig", "go.mod"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -183,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)
|
||||
@@ -203,6 +227,17 @@ func TestMultiModuleLauncherRunsToolFromSelectedDependency(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func foreignTarget() (string, string) {
|
||||
osName, arch := "windows", "arm64"
|
||||
if runtime.GOOS == osName {
|
||||
osName = "linux"
|
||||
}
|
||||
if runtime.GOARCH == arch {
|
||||
arch = "amd64"
|
||||
}
|
||||
return osName, arch
|
||||
}
|
||||
|
||||
func runModuleSelector(t *testing.T, helper, mode, value, mapPath string) string {
|
||||
t.Helper()
|
||||
command := exec.Command("sh", helper, mode, value)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -11,6 +13,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -55,6 +58,7 @@ func TestMultiModuleWoodpeckerDelivery(t *testing.T) {
|
||||
if output := runWoodpeckerBlock(t, command, repository, env); !strings.Contains(output, "state complete") || !strings.Contains(output, "tool-version=v0.2.197") {
|
||||
t.Fatalf("tagged page delivery output = %q", output)
|
||||
}
|
||||
assertGoSelection(t, repository, env["RELEASEKIT_GO_LOG"])
|
||||
if delivery.snapshot().creates != 1 || delivery.snapshot().requests == 0 || delivery.snapshot().tag != tag {
|
||||
t.Fatalf("page delivery fixture = %#v", delivery.snapshot())
|
||||
}
|
||||
@@ -77,11 +81,15 @@ func TestMultiModuleWoodpeckerDelivery(t *testing.T) {
|
||||
|
||||
t.Run("binary delivery from physical major module", func(t *testing.T) {
|
||||
const tag = "integrations/core/v2.1.0"
|
||||
arch, machine := "arm64", elf.EM_AARCH64
|
||||
if runtime.GOOS == "linux" && runtime.GOARCH == arch {
|
||||
arch, machine = "amd64", elf.EM_X86_64
|
||||
}
|
||||
delivery := newMappedDeliveryFixture(t, tag)
|
||||
repository := newWorkflowRepository(t, example)
|
||||
writeMappedProfile(t, repository, "releasekit-core-v2.json", cli.Settings{
|
||||
Product: "Example Consumer Core v2", ModuleDir: "integrations/core/v2",
|
||||
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "core", Targets: []string{"linux/amd64"}},
|
||||
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "core", Targets: []string{"linux/" + arch}},
|
||||
Delivery: workflowDeliverySettings(delivery.snapshot().URL),
|
||||
})
|
||||
writeRepositoryFile(t, repository, "integrations/core/v2/docs/releases/v2.1.0.md", "# Example Consumer Core v2 v2.1.0\n\nTagged core note.\n")
|
||||
@@ -97,8 +105,17 @@ func TestMultiModuleWoodpeckerDelivery(t *testing.T) {
|
||||
if observed.creates != 1 || observed.uploads != 2 || observed.tag != tag {
|
||||
t.Fatalf("binary delivery fixture = %#v", observed)
|
||||
}
|
||||
if _, ok := observed.assets["core-v2.1.0-linux-amd64"]; !ok {
|
||||
t.Fatalf("binary asset missing: %#v", observed.assets)
|
||||
name := "core-v2.1.0-linux-" + arch
|
||||
asset, ok := observed.assets[name]
|
||||
if !ok {
|
||||
t.Fatalf("binary asset %q missing", name)
|
||||
}
|
||||
binary, err := elf.NewFile(bytes.NewReader(asset))
|
||||
if err != nil {
|
||||
t.Fatalf("read uploaded %s as ELF: %v", name, err)
|
||||
}
|
||||
if binary.Machine != machine {
|
||||
t.Fatalf("uploaded %s machine = %s, want %s", name, binary.Machine, machine)
|
||||
}
|
||||
if _, ok := observed.assets["SHA256SUMS"]; !ok {
|
||||
t.Fatalf("checksum asset missing: %#v", observed.assets)
|
||||
@@ -400,6 +417,7 @@ func multiModuleWorkflowEnvironment(t *testing.T, repository, commit, tag, binar
|
||||
t.Helper()
|
||||
goDir, goLog := installGoBootstrapShim(t)
|
||||
environment := templateEnvironment(repository, commit, goDir, goLog)
|
||||
environment["GOOS"], environment["GOARCH"] = foreignTarget()
|
||||
environment["CI_COMMIT_TAG"] = tag
|
||||
environment["RELEASEKIT_TEST_BINARY"] = binary
|
||||
useSyntheticModuleProxy(t, environment, "file://"+filepath.ToSlash(proxy))
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
|
||||
"go.maximumdirect.net/releasekit/cmd/releasekit/internal/cli"
|
||||
"go.maximumdirect.net/releasekit/internal/gotool"
|
||||
"go.maximumdirect.net/releasekit/internal/publish"
|
||||
@@ -57,8 +59,8 @@ func TestVendoredAPIOnlyPublicationRecovery(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
launcher := exec.Command(goBinary, append(launcherArgs, "publish", "--repo", repo, "v1.2.3")...)
|
||||
launcher.Dir = t.TempDir()
|
||||
launcher := exec.Command(goBinary, append(launcherArgs.Args, "publish", "--repo", repo, "v1.2.3")...)
|
||||
launcher.Dir = launcherArgs.Directory
|
||||
publicationOutput, err := launcher.CombinedOutput()
|
||||
var exitError *exec.ExitError
|
||||
if !errors.As(err, &exitError) || exitError.ExitCode() != 1 {
|
||||
@@ -98,12 +100,29 @@ func TestRecoveryToolPreservesVersionedReplacement(t *testing.T) {
|
||||
if err := os.WriteFile(filepath.Join(module, "go.mod"), []byte(data), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
shimDir := t.TempDir()
|
||||
logPath := filepath.Join(t.TempDir(), "target")
|
||||
realGo := filepath.Join(runtime.GOROOT(), "bin", "go")
|
||||
shim := "#!/bin/sh\ncase \" $* \" in *\" -deps \"*) printf '%s/%s\\n' \"$GOOS\" \"$GOARCH\" > \"$RECOVERY_TARGET_LOG\" ;; esac\nexec \"$RECOVERY_REAL_GO\" \"$@\"\n"
|
||||
if err := os.WriteFile(filepath.Join(shimDir, "go"), []byte(shim), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("RECOVERY_TARGET_LOG", logPath)
|
||||
t.Setenv("RECOVERY_REAL_GO", realGo)
|
||||
t.Setenv("PATH", shimDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
foreignOS, foreignArch := foreignTarget()
|
||||
t.Setenv("GOOS", foreignOS)
|
||||
t.Setenv("GOARCH", foreignArch)
|
||||
args, err := gotool.RecoveryTool(t.Context(), module, t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
command := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...)
|
||||
command.Dir = t.TempDir()
|
||||
if target, err := os.ReadFile(logPath); err != nil || strings.TrimSpace(string(target)) != runtime.GOOS+"/"+runtime.GOARCH {
|
||||
t.Fatalf("recovery preparation target = %q, %v", target, err)
|
||||
}
|
||||
command := exec.Command(realGo, args.Args...)
|
||||
command.Dir = args.Directory
|
||||
command.Env = setEnvironment(setEnvironment(os.Environ(), "GOOS", runtime.GOOS), "GOARCH", runtime.GOARCH)
|
||||
output, err := command.CombinedOutput()
|
||||
if err != nil || !strings.Contains(string(output), "tool-version=v0.2.198 helper=tool-only dependency") {
|
||||
t.Fatalf("versioned recovery selection: %v: %s", err, output)
|
||||
@@ -119,8 +138,8 @@ func TestRecoveryToolSupportsReleaseKitSourceModule(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
command := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), append(args, "version")...)
|
||||
command.Dir = t.TempDir()
|
||||
command := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), append(args.Args, "version")...)
|
||||
command.Dir = args.Directory
|
||||
output, err := command.CombinedOutput()
|
||||
if err != nil || !strings.HasPrefix(string(output), "releasekit ") {
|
||||
t.Fatalf("source module recovery entry point: %v: %s", err, output)
|
||||
@@ -142,16 +161,25 @@ func TestColdGoToolRecoverySurvivesLauncherExit(t *testing.T) {
|
||||
if err := os.Symlink(source, selectedSource); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !registered {
|
||||
relative, err := filepath.Rel(repo, selectedSource)
|
||||
const toolModule = "go.maximumdirect.net/releasekit"
|
||||
const toolVersion = "v0.0.0"
|
||||
module := "module example.com/cli-candidate\n\ngo 1.26.0\n\nrequire " + toolModule + " " + toolVersion + "\n"
|
||||
var wildcardSource string
|
||||
if registered {
|
||||
module += "\nreplace " + toolModule + " => " + strconv.Quote(selectedSource) + "\n"
|
||||
module += "\ntool " + toolModule + "/cmd/releasekit\n"
|
||||
} else {
|
||||
wildcardSource = t.TempDir()
|
||||
writeRepositoryFile(t, wildcardSource, "go.mod", "module "+toolModule+"\n\ngo 1.26.0\n")
|
||||
wildcard, err := filepath.Rel(repo, wildcardSource)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
selectedSource = relative
|
||||
}
|
||||
module := "module example.com/cli-candidate\n\ngo 1.26.0\n\nrequire go.maximumdirect.net/releasekit v0.0.0\n\nreplace go.maximumdirect.net/releasekit => " + strconv.Quote(selectedSource) + "\n"
|
||||
if registered {
|
||||
module += "\ntool go.maximumdirect.net/releasekit/cmd/releasekit\n"
|
||||
specific, err := filepath.Rel(repo, selectedSource)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
module += "\nreplace (\n" + toolModule + " => " + strconv.Quote(wildcard) + "\n" + toolModule + " " + toolVersion + " => " + strconv.Quote(specific) + "\n)\n"
|
||||
}
|
||||
writeRepositoryFile(t, repo, "go.mod", module)
|
||||
sums, err := os.ReadFile(filepath.Join(source, "go.sum"))
|
||||
@@ -234,12 +262,39 @@ func TestColdGoToolRecoverySurvivesLauncherExit(t *testing.T) {
|
||||
if !strings.Contains(command, "go ") || strings.Contains(command, "/exe/releasekit") {
|
||||
t.Fatalf("unstable handoff: %s", command)
|
||||
}
|
||||
if !registered && !strings.Contains(command, "publication-recovery") {
|
||||
t.Fatalf("API-only handoff lacks durable metadata: %s", command)
|
||||
if !registered {
|
||||
store := filepath.Join(repo, ".git", "releasekit", "publication-recovery")
|
||||
entries, err := os.ReadDir(store)
|
||||
if err != nil || len(entries) != 1 {
|
||||
t.Fatalf("API-only recovery modules = %v, %v", entries, err)
|
||||
}
|
||||
dir := filepath.Join(store, entries[0].Name())
|
||||
if !strings.Contains(command, shellQuote(dir)) {
|
||||
t.Fatalf("API-only handoff lacks durable metadata: %s", command)
|
||||
}
|
||||
path := filepath.Join(dir, "go.mod")
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
prepared, err := modfile.Parse(path, data, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
paths := map[string]string{}
|
||||
for _, replacement := range prepared.Replace {
|
||||
if replacement.Old.Path == toolModule {
|
||||
paths[replacement.Old.Version] = replacement.New.Path
|
||||
}
|
||||
}
|
||||
if len(paths) != 2 || paths[""] != wildcardSource || paths[toolVersion] != selectedSource {
|
||||
t.Fatalf("durable recovery lost replacement selection: %v", paths)
|
||||
}
|
||||
}
|
||||
// Execute from another directory after the original driver and the
|
||||
// API-only launcher's temporary files are gone.
|
||||
evidence, err := publish.LoadEvidence(outputLineValue(text, "publication record saved: "))
|
||||
originalRecord := outputLineValue(text, "publication record saved: ")
|
||||
evidence, err := publish.LoadEvidence(originalRecord)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -260,10 +315,13 @@ func TestColdGoToolRecoverySurvivesLauncherExit(t *testing.T) {
|
||||
}
|
||||
selected := command
|
||||
if scenario.record != "" {
|
||||
selected += " --record " + shellQuote(scenario.record)
|
||||
selected = strings.Replace(selected, shellQuote(originalRecord), shellQuote(scenario.record), 1)
|
||||
}
|
||||
recovery := exec.Command("sh", "-c", selected)
|
||||
foreignOS, foreignArch := foreignTarget()
|
||||
goFunction := "go() { GOOS=" + shellQuote(foreignOS) + " GOARCH=" + shellQuote(foreignArch) + " command go \"$@\"; }\n"
|
||||
recovery := exec.Command("sh", "-c", goFunction+selected)
|
||||
recovery.Dir = t.TempDir()
|
||||
recovery.Env = setEnvironment(setEnvironment(os.Environ(), "GOOS", foreignOS), "GOARCH", foreignArch)
|
||||
recovered, err := recovery.CombinedOutput()
|
||||
if scenario.status == 0 && err != nil || scenario.status != 0 && (!errors.As(err, &exitError) || exitError.ExitCode() != scenario.status) {
|
||||
t.Fatalf("recovery status want %d: %v: %s", scenario.status, err, recovered)
|
||||
|
||||
@@ -46,6 +46,7 @@ func TestWoodpeckerTagDeliveryTemplate(t *testing.T) {
|
||||
|
||||
goDir, goLog := installGoBootstrapShim(t)
|
||||
env := templateEnvironment(repository, commit, goDir, goLog)
|
||||
env["GOOS"], env["GOARCH"] = foreignTarget()
|
||||
moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod"))
|
||||
output := runWoodpeckerBlock(t, command, repository, env)
|
||||
if !strings.Contains(output, "state complete") {
|
||||
@@ -77,6 +78,7 @@ func TestWoodpeckerTagDeliveryTemplate(t *testing.T) {
|
||||
|
||||
goDir, goLog := installGoBootstrapShim(t)
|
||||
env := templateEnvironment(repository, commit, goDir, goLog)
|
||||
env["GOOS"], env["GOARCH"] = foreignTarget()
|
||||
moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod"))
|
||||
sumBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.sum"))
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
@@ -108,6 +110,7 @@ func TestWoodpeckerTagDeliveryTemplate(t *testing.T) {
|
||||
unrelated := cliGitOutput(t, repository, "rev-parse", "HEAD")
|
||||
goDir, goLog := installControlledGoBootstrapShim(t)
|
||||
env := templateEnvironment(repository, unrelated, goDir, goLog)
|
||||
env["GOOS"], env["GOARCH"] = foreignTarget()
|
||||
if output, err := executeWoodpeckerBlock(command, repository, env); err == nil {
|
||||
t.Fatalf("wrong tag succeeded: %s", output)
|
||||
} else if !strings.Contains(output, commit) || !strings.Contains(output, unrelated) {
|
||||
@@ -123,6 +126,7 @@ func TestWoodpeckerTagDeliveryTemplate(t *testing.T) {
|
||||
writeRepositoryFile(t, repository, "go.mod", "module example.com/different-tool-selection\n\ngo 1.25.0\n")
|
||||
goDir, goLog := installControlledGoBootstrapShim(t)
|
||||
env := templateEnvironment(repository, commit, goDir, goLog)
|
||||
env["GOOS"], env["GOARCH"] = foreignTarget()
|
||||
if output, err := executeWoodpeckerBlock(command, repository, env); err == nil {
|
||||
t.Fatalf("dirty module succeeded: %s", output)
|
||||
}
|
||||
@@ -153,6 +157,7 @@ func TestWoodpeckerTagDeliveryTemplate(t *testing.T) {
|
||||
goDir, goLog := installControlledGoBootstrapShim(t)
|
||||
toolArgs := filepath.Join(t.TempDir(), "tool-arguments")
|
||||
env := templateEnvironment(repository, strings.ToUpper(eventID), goDir, goLog)
|
||||
env["GOOS"], env["GOARCH"] = foreignTarget()
|
||||
env["CI_COMMIT_TAG"] = event.tag
|
||||
env["RELEASEKIT_TOOL_ARGS_LOG"] = toolArgs
|
||||
env["RELEASEKIT_TOOL_EXIT"] = "37"
|
||||
@@ -225,6 +230,7 @@ func TestWoodpeckerTagDeliveryUsesTaggedModuleVersion(t *testing.T) {
|
||||
moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod"))
|
||||
commit := oldCommit
|
||||
env := templateEnvironment(repository, commit, goDir, goLog)
|
||||
env["GOOS"], env["GOARCH"] = foreignTarget()
|
||||
useSyntheticModuleProxy(t, env, "file://"+filepath.ToSlash(proxy))
|
||||
sumLog := filepath.Join(t.TempDir(), "tool-only-sums")
|
||||
env["RELEASEKIT_SUM_LOG"] = sumLog
|
||||
@@ -242,6 +248,7 @@ func TestWoodpeckerTagDeliveryUsesTaggedModuleVersion(t *testing.T) {
|
||||
const module = "go.maximumdirect.net/releasekit"
|
||||
writeRepositoryFile(t, repository, "go.mod", "module example.com/cli-candidate\n\ngo 1.25.0\n\nrequire "+module+" v0.2.197\n\ntool "+module+"/cmd/releasekit\n")
|
||||
env := templateEnvironment(repository, "", goDir, goLog)
|
||||
env["GOOS"], env["GOARCH"] = foreignTarget()
|
||||
useSyntheticModuleProxy(t, env, "file://"+filepath.ToSlash(proxy))
|
||||
runWoodpeckerBlock(t, "go mod tidy\ngo mod vendor", repository, env)
|
||||
cliGitRun(t, repository, "add", "go.mod", "go.sum", "vendor")
|
||||
@@ -267,6 +274,7 @@ func TestWoodpeckerTagDeliveryUsesTaggedModuleVersion(t *testing.T) {
|
||||
commit := pushTaggedCLIRepository(t, repository)
|
||||
moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod"))
|
||||
env := templateEnvironment(repository, commit, goDir, goLog)
|
||||
env["GOOS"], env["GOARCH"] = foreignTarget()
|
||||
useSyntheticModuleProxy(t, env, "file://"+filepath.ToSlash(proxy))
|
||||
sumLog := filepath.Join(t.TempDir(), "tool-only-sums")
|
||||
env["RELEASEKIT_SUM_LOG"] = sumLog
|
||||
@@ -313,6 +321,7 @@ func TestWoodpeckerSourceDeliveryTemplateSelfBootstraps(t *testing.T) {
|
||||
commit := pushTaggedCLIRepository(t, repository)
|
||||
goDir, goLog := installGoBootstrapShim(t)
|
||||
env := templateEnvironment(repository, commit, goDir, goLog)
|
||||
env["GOOS"], env["GOARCH"] = foreignTarget()
|
||||
useSyntheticModuleProxy(t, env, proxy.URL)
|
||||
proxyRequests.Store(0)
|
||||
env["CI_COMMIT_SHA"] = strings.Repeat("0", len(commit))
|
||||
@@ -335,6 +344,9 @@ func TestWoodpeckerSourceDeliveryTemplateSelfBootstraps(t *testing.T) {
|
||||
t.Fatalf("self-bootstrapped command arguments = %q", output)
|
||||
}
|
||||
log := readWoodpeckerFixtureFile(t, goLog)
|
||||
if !strings.Contains(log, "build-target="+runtime.GOOS+"/"+runtime.GOARCH) {
|
||||
t.Fatalf("source build did not use host target: %s", log)
|
||||
}
|
||||
if !strings.Contains(log, "GOWORK=off GOTOOLCHAIN=local") || !strings.Contains(log, "build -mod=readonly -o ") || !strings.Contains(log, "./cmd/releasekit") {
|
||||
t.Fatalf("self-bootstrap Go command = %q", log)
|
||||
}
|
||||
@@ -418,6 +430,8 @@ func installGoBootstrapShim(t *testing.T) (string, string) {
|
||||
goLog := filepath.Join(shimDir, "go-commands")
|
||||
shim := `#!/bin/sh
|
||||
printf 'cwd=%s GOWORK=%s GOTOOLCHAIN=%s %s\n' "$PWD" "$GOWORK" "$GOTOOLCHAIN" "$*" >> "$RELEASEKIT_GO_LOG"
|
||||
case " $* " in *" -deps "*) printf 'target=%s/%s\n' "$GOOS" "$GOARCH" >> "$RELEASEKIT_GO_LOG" ;; esac
|
||||
if [ "$1" = build ]; then printf 'build-target=%s/%s\n' "$GOOS" "$GOARCH" >> "$RELEASEKIT_GO_LOG"; fi
|
||||
"$RELEASEKIT_REAL_GO" "$@"
|
||||
status=$?
|
||||
if [ "$status" -eq 0 ] && [ "$1" = "tool" ] && [ -n "${RELEASEKIT_SUM_LOG:-}" ]; then
|
||||
@@ -442,6 +456,8 @@ func installControlledGoBootstrapShim(t *testing.T) (string, string) {
|
||||
shim := `#!/bin/sh
|
||||
set -eu
|
||||
printf 'cwd=%s GOWORK=%s GOTOOLCHAIN=%s %s\n' "$PWD" "$GOWORK" "$GOTOOLCHAIN" "$*" >> "$RELEASEKIT_GO_LOG"
|
||||
if [ "$1" = env ]; then exec "$RELEASEKIT_REAL_GO" "$@"; fi
|
||||
case " $* " in *" -deps "*) printf 'target=%s/%s\n' "$GOOS" "$GOARCH" >> "$RELEASEKIT_GO_LOG" ;; esac
|
||||
modfile=
|
||||
for argument in "$@"; do
|
||||
case "$argument" in -modfile=*) modfile=${argument#-modfile=} ;; esac
|
||||
@@ -644,13 +660,16 @@ func assertModuleBootstrap(t *testing.T, repository, goLog, moduleBefore string,
|
||||
func assertGoSelection(t *testing.T, repository, goLog string) {
|
||||
t.Helper()
|
||||
log := readWoodpeckerFixtureFile(t, goLog)
|
||||
if !strings.Contains(log, "target="+runtime.GOOS+"/"+runtime.GOARCH) {
|
||||
t.Fatalf("dependency inspection did not use host target: %s", log)
|
||||
}
|
||||
list := "list -mod=mod -modfile="
|
||||
tool := "tool -modfile="
|
||||
module := "go.maximumdirect.net/releasekit"
|
||||
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) {
|
||||
|
||||
17
docs/cli.md
17
docs/cli.md
@@ -4,6 +4,8 @@ ReleaseKit currently provides eight commands. The exit statuses below belong
|
||||
to the native executable. `go run` examples are convenient for source execution,
|
||||
but the Go driver maps a nonzero child exit status to 1. Use the built executable
|
||||
or `./scripts/release` for publication when distinct statuses are needed.
|
||||
For the `go run` examples, [select the Go host target](consumers/adoption.md#select-the-go-host-target)
|
||||
before running from a shell set to cross-compile.
|
||||
|
||||
Print the executable version:
|
||||
|
||||
@@ -154,7 +156,9 @@ uses a persistent native executable when available. Recognized temporary Go-buil
|
||||
executables provide no native fallback after their driver exits. Command
|
||||
preparation has its own bounded context after a failed publication, so publication
|
||||
cancellation permits preparing the later invocation. Each entry point preserves
|
||||
the recovery exit status.
|
||||
the recovery exit status. Run a generated Go-tool handoff in a POSIX shell.
|
||||
When it runs, it sets `GOOS` and `GOARCH` to the selected Go toolchain's
|
||||
`GOHOSTOS` and `GOHOSTARCH`.
|
||||
If the selected entry point becomes unavailable, use another supported ReleaseKit
|
||||
CLI with the saved record.
|
||||
|
||||
@@ -338,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.
|
||||
|
||||
@@ -12,7 +12,6 @@ From the consumer module's root, select the ReleaseKit version to adopt:
|
||||
: "${RELEASEKIT_TOOL_VERSION:?Set the released ReleaseKit version to adopt}"
|
||||
go get -tool "go.maximumdirect.net/releasekit/cmd/releasekit@${RELEASEKIT_TOOL_VERSION}"
|
||||
go mod tidy
|
||||
go tool releasekit version
|
||||
```
|
||||
|
||||
Select a release that implements the API, profile, and workflow you are adopting.
|
||||
@@ -33,18 +32,32 @@ participate in dependency version selection and can raise the consumer module's
|
||||
Go minimum. See the
|
||||
[Go tool dependency reference](https://go.dev/ref/mod#go-mod-file-tool).
|
||||
|
||||
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:
|
||||
### Select the Go host target
|
||||
|
||||
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
|
||||
exec go tool releasekit "$@"
|
||||
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 go.maximumdirect.net/releasekit/cmd/releasekit "$@"
|
||||
```
|
||||
|
||||
Save this launcher as `releasekit` and run `sh ./releasekit version` from the
|
||||
selected module to verify registration. It preserves the consumer's workspace
|
||||
and toolchain settings while selecting the host for the executable.
|
||||
For a launcher callable from other directories, first change to its consumer
|
||||
module directory. Follow the [CLI contract](../cli.md) for commands and exit
|
||||
statuses, and the [configuration reference](../config.md) for the release profile.
|
||||
statuses.
|
||||
|
||||
Configured product checks inherit the launcher's host `GOOS` and `GOARCH`.
|
||||
If a check needs an application target, set it in that check's `env` map or
|
||||
command. The [configuration reference](../config.md) describes
|
||||
check environments and the separate target selection for built-in builds.
|
||||
|
||||
For consumers using vendoring, register the CLI before running `go mod vendor`
|
||||
so the vendor tree includes its dependencies. `go tool` follows Go's normal
|
||||
@@ -102,12 +115,16 @@ 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.
|
||||
|
||||
Update copied launchers and workflow templates separately when upgrading the
|
||||
module dependency. ReleaseKit tests the generic bootstrap; consumer wiring tests
|
||||
can focus on selectors and profile mapping. Profiles continue to select
|
||||
application build targets.
|
||||
Upgrade the module-selected tool and workflow together. For a tag created before
|
||||
that upgrade, retry with the workflow matching the tool recorded at that tag.
|
||||
Keep its immutable module files and tag intact. See the
|
||||
|
||||
@@ -293,11 +293,13 @@ identify an annotated tag object or a commit. The template requires the resolved
|
||||
commits to match, checks out that commit detached, and confirms the source tree
|
||||
is clean. It then copies the tagged `go.mod` and any `go.sum` to a temporary
|
||||
directory outside the checkout. `go list -m` confirms that the module selects
|
||||
ReleaseKit. The template adds its CLI tool declaration to that temporary copy
|
||||
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.
|
||||
|
||||
@@ -336,10 +338,21 @@ and application artifact compilation retain their separate environments.
|
||||
|
||||
The normal Go module and build caches are sufficient. Cache mounts may be added
|
||||
as deployment configuration when they help the runner; they are not needed by
|
||||
the template. A separately installed executable can be installed with
|
||||
`go install go.maximumdirect.net/releasekit/cmd/releasekit@PIN`, then run
|
||||
with the same arguments. With the event object available in the CI checkout,
|
||||
resolve it to its source commit before invocation:
|
||||
the template. For a standalone installation, replace `PIN` with the desired
|
||||
version and [select the Go host target](adoption.md#select-the-go-host-target)
|
||||
for `go install`:
|
||||
|
||||
```sh
|
||||
(
|
||||
releasekit_host_os=$(go env GOHOSTOS) || exit $?
|
||||
releasekit_host_arch=$(go env GOHOSTARCH) || exit $?
|
||||
export GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch"
|
||||
go install go.maximumdirect.net/releasekit/cmd/releasekit@PIN
|
||||
)
|
||||
```
|
||||
|
||||
With the event object available in the CI checkout, resolve it to its source
|
||||
commit before invoking the installed executable:
|
||||
|
||||
```sh
|
||||
expected_commit=$(git -C "$CI_WORKSPACE" rev-parse --verify "${CI_COMMIT_SHA}^{commit}")
|
||||
|
||||
@@ -12,9 +12,14 @@ For CLI publication recovery, the package selects a registered module tool or
|
||||
prepares a durable private module root for an API-only consumer. Go's module
|
||||
graph query includes transitive dependencies; preparation retains module
|
||||
requirements, versioned replacements, and checksums. Local replacements resolve
|
||||
against their original module directory before relocation. The original main
|
||||
module remains a local replacement, including when releasing ReleaseKit itself.
|
||||
against their original module directory before relocation. Each relocation
|
||||
updates the directive's destination and retains its path and version selector.
|
||||
The original main module remains a local replacement, including when releasing
|
||||
ReleaseKit itself.
|
||||
The private root provides tool resolution independently of consumer vendoring.
|
||||
API-only preparation discovers the host target for dependency inspection. The
|
||||
returned invocation carries its directory and tool arguments; the CLI's later
|
||||
shell command selects the host again when it runs.
|
||||
The CLI formats the later invocation using a separate bounded context after
|
||||
publication failure. It falls back to a persistent native executable when module
|
||||
selection is unavailable or preparation fails, and excludes recognized temporary
|
||||
|
||||
@@ -188,19 +188,35 @@ Woodpecker workflow builds the CLI from the tagged checkout, then uses
|
||||
destination and secret, copy it to `.woodpecker/releasekit-source-delivery.yml`
|
||||
and add the Woodpecker repository secret `releasekit_gitea_token`. If page
|
||||
delivery fails or is uncertain, inspect the hosted release and retry the same
|
||||
immutable version from its tag:
|
||||
immutable version from its tag. The retry requires a clean tagged checkout
|
||||
before preparing or building the tool:
|
||||
|
||||
```sh
|
||||
git fetch origin "refs/tags/vX.Y.Z:refs/tags/vX.Y.Z"
|
||||
git checkout --detach "vX.Y.Z"
|
||||
GOTOOLCHAIN=local go mod download
|
||||
mkdir -p .releasekit/bin
|
||||
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go build -o .releasekit/bin/releasekit ./cmd/releasekit
|
||||
.releasekit/bin/releasekit deliver "vX.Y.Z"
|
||||
(
|
||||
set -e
|
||||
version=vX.Y.Z
|
||||
tag_ref="refs/tags/$version"
|
||||
git fetch origin "$tag_ref:$tag_ref"
|
||||
tag_commit=$(git rev-parse --verify "$tag_ref^{commit}")
|
||||
git checkout --detach "$tag_commit"
|
||||
worktree_status=$(git status --porcelain --untracked-files=all)
|
||||
if [ -n "$worktree_status" ]; then
|
||||
printf '%s\n' 'Delivery retry requires a clean tagged checkout.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
releasekit_host_os=$(GOTOOLCHAIN=local go env GOHOSTOS)
|
||||
releasekit_host_arch=$(GOTOOLCHAIN=local go env GOHOSTARCH)
|
||||
export GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch"
|
||||
GOTOOLCHAIN=local go mod download
|
||||
mkdir -p .releasekit/bin
|
||||
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go build -o .releasekit/bin/releasekit ./cmd/releasekit
|
||||
.releasekit/bin/releasekit deliver "$version"
|
||||
)
|
||||
```
|
||||
|
||||
For an older tag whose CLI predates `token_file`, supply the token through its
|
||||
environment variable when retrying that tagged tool:
|
||||
For an older tag whose CLI predates `token_file`, build the tagged executable
|
||||
as above, then supply the token through its environment variable when retrying:
|
||||
|
||||
```sh
|
||||
RELEASEKIT_GITEA_TOKEN="$(cat .releasekit/gitea-token)" \
|
||||
|
||||
41
docs/releases/v0.9.0.md
Normal file
41
docs/releases/v0.9.0.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# ReleaseKit v0.9.0
|
||||
|
||||
ReleaseKit's maintained launchers and generated Go recovery commands now
|
||||
prepare and run the release tool for the Go host when inherited `GOOS` and
|
||||
`GOARCH` select another target. Release profiles still select the targets for
|
||||
application builds.
|
||||
|
||||
## Launcher, delivery, and recovery fixes
|
||||
|
||||
- Local launchers and Woodpecker templates select the host before preparing
|
||||
tool dependencies or compiling the CLI. Module-based launchers invoke
|
||||
ReleaseKit by its full Go package path, so another registered tool named
|
||||
`releasekit` does not make the invocation ambiguous.
|
||||
- Generated Go recovery commands select the host when run in a POSIX shell
|
||||
and find the Go executable through `PATH`. Recovery for API-only consumers
|
||||
preserves both wildcard and version-specific module replacements when
|
||||
relocating relative paths, fixing a panic after a failed publication.
|
||||
- The manual hosted-page retry stops if fetching or checking out the tag fails,
|
||||
or if local edits remain. It checks the tagged checkout before downloading
|
||||
dependencies or building the CLI.
|
||||
|
||||
## Compatibility and upgrade
|
||||
|
||||
The module path remains `go.maximumdirect.net/releasekit` and the Go consumer
|
||||
minimum remains 1.26.0. Public Go APIs, release profiles, and publication
|
||||
record formats are unchanged.
|
||||
|
||||
Update copied launchers and CI templates to get the host and tool selection
|
||||
fixes. Updating the module dependency alone does not update those files.
|
||||
Configured product checks inherit the launcher's host target; if a check
|
||||
needs an application target, set it explicitly in the check's `env` map
|
||||
or command.
|
||||
|
||||
See the [adoption guide](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/docs/consumers/adoption.md),
|
||||
[delivery guide](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/docs/consumers/delivery.md),
|
||||
and [maintainer runbook](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/docs/release.md)
|
||||
for updated recipes.
|
||||
|
||||
[COPYRIGHT](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/COPYRIGHT) |
|
||||
[LICENSE](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/LICENSE) |
|
||||
[ATTRIBUTION](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/docs/oss/attribution.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,
|
||||
@@ -126,9 +126,27 @@ source and each selected product's module-local release note, then commit the
|
||||
fixture and run the source-selected tool without publishing:
|
||||
|
||||
```sh
|
||||
go tool releasekit check --config releasekit-root.json v1.2.3
|
||||
go -C integrations/ckconfig tool releasekit check --config releasekit-ckconfig.json v1.2.3
|
||||
go -C integrations/core/v2 tool releasekit check --config releasekit-core-v2.json v2.1.0
|
||||
(
|
||||
cd . || 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 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 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 go.maximumdirect.net/releasekit/cmd/releasekit check --config releasekit-core-v2.json v2.1.0
|
||||
)
|
||||
```
|
||||
|
||||
These commands use the repository-root profile paths even when invoked from a
|
||||
|
||||
@@ -36,10 +36,13 @@ releasekit_module=go.maximumdirect.net/releasekit
|
||||
cd "$module_path"
|
||||
export GOWORK=off
|
||||
export GOTOOLCHAIN=local
|
||||
releasekit_host_os=$(go env GOHOSTOS) || exit $?
|
||||
releasekit_host_arch=$(go env GOHOSTARCH) || exit $?
|
||||
export GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch"
|
||||
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" \
|
||||
|
||||
@@ -52,10 +52,13 @@ steps:
|
||||
releasekit_module=go.maximumdirect.net/releasekit
|
||||
(
|
||||
cd "$${module_path}"
|
||||
releasekit_host_os=$(go env GOHOSTOS) || exit $?
|
||||
releasekit_host_arch=$(go env GOHOSTARCH) || exit $?
|
||||
export GOOS="$${releasekit_host_os}" GOARCH="$${releasekit_host_arch}"
|
||||
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}" \
|
||||
|
||||
@@ -30,6 +30,9 @@ steps:
|
||||
trap 'rm -rf "$${bootstrap_dir}"' EXIT
|
||||
export GOWORK=off
|
||||
export GOTOOLCHAIN=local
|
||||
releasekit_host_os=$(go env GOHOSTOS) || exit $?
|
||||
releasekit_host_arch=$(go env GOHOSTARCH) || exit $?
|
||||
export GOOS="$${releasekit_host_os}" GOARCH="$${releasekit_host_arch}"
|
||||
go build -mod=readonly -o "$${bootstrap_dir}/releasekit" ./cmd/releasekit
|
||||
"$${bootstrap_dir}/releasekit" ci-deliver \
|
||||
--repo "$${CI_WORKSPACE}" \
|
||||
|
||||
@@ -41,11 +41,14 @@ steps:
|
||||
|
||||
export GOWORK=off
|
||||
export GOTOOLCHAIN=local
|
||||
releasekit_host_os=$(go env GOHOSTOS) || exit $?
|
||||
releasekit_host_arch=$(go env GOHOSTARCH) || exit $?
|
||||
export GOOS="$${releasekit_host_os}" GOARCH="$${releasekit_host_arch}"
|
||||
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 ci-deliver \
|
||||
go tool -modfile="$${bootstrap_dir}/releasekit.mod" "$${releasekit_module}/cmd/releasekit" ci-deliver \
|
||||
--repo "$${CI_WORKSPACE}" \
|
||||
--prepared \
|
||||
--commit "$${expected_commit}" \
|
||||
|
||||
@@ -43,10 +43,16 @@ func TemporaryExecutable(path string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// RecoveryTool returns module-selected Go arguments for a later invocation.
|
||||
// An empty result means this module does not select ReleaseKit. API-only modules
|
||||
// RecoveryInvocation describes a module-selected Go tool invocation.
|
||||
type RecoveryInvocation struct {
|
||||
Directory string
|
||||
Args []string
|
||||
}
|
||||
|
||||
// RecoveryTool returns a module-selected Go invocation for later use.
|
||||
// A nil result means this module does not select ReleaseKit. API-only modules
|
||||
// receive a private module root that outlives launcher-owned bootstraps.
|
||||
func RecoveryTool(ctx context.Context, moduleDir, recordDir string) ([]string, error) {
|
||||
func RecoveryTool(ctx context.Context, moduleDir, recordDir string) (*RecoveryInvocation, error) {
|
||||
path := filepath.Join(moduleDir, "go.mod")
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -58,7 +64,7 @@ func RecoveryTool(ctx context.Context, moduleDir, recordDir string) ([]string, e
|
||||
}
|
||||
for _, tool := range module.Tool {
|
||||
if tool.Path == releaseKitCommand {
|
||||
return []string{"-C", moduleDir, "tool", "-modfile=", releaseKitCommand}, nil
|
||||
return &RecoveryInvocation{Directory: moduleDir, Args: []string{"tool", "-modfile=", releaseKitCommand}}, nil
|
||||
}
|
||||
}
|
||||
// Ask Go about the selected module graph, including transitive dependencies.
|
||||
@@ -94,9 +100,11 @@ func RecoveryTool(ctx context.Context, moduleDir, recordDir string) ([]string, e
|
||||
// replacements relative to the directory that originally owned go.mod.
|
||||
for _, replacement := range module.Replace {
|
||||
if replacement.New.Version == "" && !filepath.IsAbs(replacement.New.Path) {
|
||||
if err := module.AddReplace(replacement.Old.Path, replacement.Old.Version, filepath.Join(moduleDir, replacement.New.Path), ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// A local replacement ends in its path token. Edit that token on
|
||||
// the parsed directive so a wildcard does not absorb versioned ones.
|
||||
replacement.New.Path = filepath.Join(moduleDir, replacement.New.Path)
|
||||
tokens := replacement.Syntax.Token
|
||||
tokens[len(tokens)-1] = modfile.AutoQuote(replacement.New.Path)
|
||||
}
|
||||
}
|
||||
// The original main module's source remains local if a dependency refers
|
||||
@@ -140,15 +148,31 @@ func RecoveryTool(ctx context.Context, moduleDir, recordDir string) ([]string, e
|
||||
return nil, err
|
||||
}
|
||||
// Prepare the private graph and sums before the later tool invocation.
|
||||
environment := map[string]string{"GOWORK": "off", "GOTOOLCHAIN": "local"}
|
||||
hostJSON, err := discover(ctx, directory, environment, "go", "env", "-json", "GOHOSTOS", "GOHOSTARCH")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prepare recovery tool: %w", err)
|
||||
}
|
||||
var host struct {
|
||||
GOHOSTOS string
|
||||
GOHOSTARCH string
|
||||
}
|
||||
if err := json.Unmarshal(hostJSON, &host); err != nil {
|
||||
return nil, fmt.Errorf("prepare recovery tool: decode host target: %w", err)
|
||||
}
|
||||
if host.GOHOSTOS == "" || host.GOHOSTARCH == "" {
|
||||
return nil, fmt.Errorf("prepare recovery tool: missing host target")
|
||||
}
|
||||
environment["GOOS"], environment["GOARCH"] = host.GOHOSTOS, host.GOHOSTARCH
|
||||
captured := &combinedWriter{mode: captureStatus}
|
||||
err = process.Run(ctx, process.Invocation{
|
||||
Args: []string{"go", "list", "-mod=mod", "-modfile=", "-deps", releaseKitCommand},
|
||||
Dir: directory, Env: map[string]string{"GOWORK": "off", "GOTOOLCHAIN": "local"},
|
||||
Dir: directory, Env: environment,
|
||||
Stdout: captured, Stderr: captured,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prepare recovery tool: %w: %s", err, diagnosticText(captured.take(), captured.omitted()))
|
||||
}
|
||||
complete = true
|
||||
return []string{"-C", directory, "tool", "-modfile=", releaseKitCommand}, nil
|
||||
return &RecoveryInvocation{Directory: directory, Args: []string{"tool", "-modfile=", releaseKitCommand}}, nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ if [ "$#" -ne 1 ]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
releasekit_host_os=$(GOTOOLCHAIN=local go env GOHOSTOS) || exit $?
|
||||
releasekit_host_arch=$(GOTOOLCHAIN=local go env GOHOSTARCH) || exit $?
|
||||
export GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch"
|
||||
mkdir -p .releasekit/bin
|
||||
GOTOOLCHAIN=local go build -o .releasekit/bin/releasekit ./cmd/releasekit
|
||||
exec .releasekit/bin/releasekit publish "$1"
|
||||
|
||||
Reference in New Issue
Block a user