Anchor dependency preparation to checkout root

This commit is contained in:
2026-11-01 21:12:05 +00:00
parent b47202a2e9
commit 5aba4c6c87
4 changed files with 83 additions and 8 deletions

View File

@@ -41,7 +41,11 @@ For binary profiles, call `PrepareBuildDependencies` with a
`DependencyPreparationRequest` selecting the repository and module after
checkout preparation and before delivery. It runs `go mod download` in that
module with workspace discovery disabled and the available compatible local Go
toolchain.
toolchain. Inside a Git checkout, `RepositoryDir` can name a subdirectory;
`ModuleDir` is interpreted relative to the containing checkout root, and an
empty selection chooses that root. Previously, a supplied checkout subdirectory
acted as the selection base. Outside a checkout, the supplied directory remains
the base for `ModuleDir`.
The operation may use the network and updates the Go module cache while
preserving the caller's proxy, private-module, credential, and cache settings.
If checksums are missing, Go may update `go.sum`. For binary CI delivery, prepare

View File

@@ -110,8 +110,7 @@ and CLI publication tests. Use the offline environment specified in Stage 10.
**Status:** complete. `go test ./internal/git` passes offline, including real
checkout, worktree, alias, standalone, missing-Git, diagnostic, and failure
cases. The public Go-only preparation composition check is deferred to Stage 3,
where preparation adopts discovery.
cases. The public Go-only preparation composition check passed in Stage 3.
**Files:** `internal/git/source.go` or a focused companion file, Git tests, and
`internal/process/process.go` only if an existing invocation capability proves
@@ -172,7 +171,9 @@ checkout, discovery fails with the missing-Git cause before Go runs.
## Stage 3: Apply checkout-root selection to dependency preparation
**Status:** pending. **Depends on:** Stage 2.
**Status:** complete. Public preparation and CLI `TestCIDeliver` checks pass
offline, including checkout-relative, symlink, standalone, and missing-Git
selection. **Depends on:** Stage 2.
**Files:** `preparation.go`, `preparation_test.go`, and
`docs/consumers/delivery.md`'s preparation guidance.

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"strings"
"go.maximumdirect.net/releasekit/internal/git"
internalpreparation "go.maximumdirect.net/releasekit/internal/preparation"
"go.maximumdirect.net/releasekit/internal/releaseidentity"
)
@@ -37,11 +38,13 @@ func PrepareTaggedCheckout(ctx context.Context, request TaggedCheckoutRequest) e
}
// DependencyPreparationRequest selects the repository module whose dependencies
// should be prepared.
// should be prepared. Within a checkout, RepositoryDir may name any directory
// in it; ModuleDir is resolved from the checkout root. Outside a checkout,
// ModuleDir is resolved from RepositoryDir.
type DependencyPreparationRequest struct {
RepositoryDir string
// ModuleDir selects a repository-relative module directory; empty selects
// the repository root.
// ModuleDir selects a directory relative to the checkout root or standalone
// RepositoryDir; empty selects that base.
ModuleDir string
}
@@ -55,7 +58,15 @@ func PrepareBuildDependencies(ctx context.Context, request DependencyPreparation
if strings.TrimSpace(request.RepositoryDir) == "" {
return errors.New("repository directory is required")
}
selection, err := releaseidentity.ResolveSelection(request.RepositoryDir, request.ModuleDir)
base := request.RepositoryDir
root, found, err := git.DiscoverRepositoryRoot(ctx, base)
if err != nil {
return fmt.Errorf("discover dependency preparation repository: %w", err)
}
if found {
base = root
}
selection, err := releaseidentity.ResolveSelection(base, request.ModuleDir)
if err != nil {
return fmt.Errorf("resolve dependency preparation module: %w", err)
}

View File

@@ -249,6 +249,65 @@ func TestPrepareBuildDependenciesUsesSelectedModuleDirectory(t *testing.T) {
}
}
func TestPrepareBuildDependenciesSelectsFromCheckoutRoot(t *testing.T) {
repo := t.TempDir()
buildGit(t, repo, "init")
nested := filepath.Join(repo, "nested")
child := filepath.Join(nested, "child")
if err := os.MkdirAll(child, 0o755); err != nil {
t.Fatal(err)
}
if err := os.Symlink("nested/child", filepath.Join(repo, "link")); err != nil {
t.Fatal(err)
}
bin := t.TempDir()
marker := filepath.Join(t.TempDir(), "directory")
if err := os.WriteFile(filepath.Join(bin, "go"), []byte("#!/bin/sh\nprintf '%s' \"$PWD\" > \"$PREPARE_DIR_MARKER\"\n"), 0o755); err != nil {
t.Fatal(err)
}
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
t.Setenv("PREPARE_DIR_MARKER", marker)
for _, tc := range []struct{ dir, module, want string }{
{repo, "", repo}, {child, "", repo}, {repo, "nested", nested},
{child, "nested", nested}, {child, "link/..", nested},
} {
err := releasekit.PrepareBuildDependencies(context.Background(), releasekit.DependencyPreparationRequest{RepositoryDir: tc.dir, ModuleDir: tc.module})
got, readErr := os.ReadFile(marker)
if err != nil || readErr != nil || string(got) != tc.want {
t.Fatalf("selection %q, %q = %q, error %v, read %v; want %q", tc.dir, tc.module, got, err, readErr, tc.want)
}
}
}
func TestPrepareBuildDependenciesWithoutGit(t *testing.T) {
repo := t.TempDir()
buildGit(t, repo, "init")
standalone := t.TempDir()
bin := t.TempDir()
marker := filepath.Join(t.TempDir(), "go-ran")
if err := os.WriteFile(filepath.Join(bin, "go"), []byte("#!/bin/sh\nprintf ran > \"$PREPARE_DIR_MARKER\"\n"), 0o755); err != nil {
t.Fatal(err)
}
t.Setenv("PATH", bin)
t.Setenv("PREPARE_DIR_MARKER", marker)
if err := releasekit.PrepareBuildDependencies(context.Background(), releasekit.DependencyPreparationRequest{RepositoryDir: standalone}); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(marker); err != nil {
t.Fatalf("Go did not run: %v", err)
}
if err := os.Remove(marker); err != nil {
t.Fatal(err)
}
err := releasekit.PrepareBuildDependencies(context.Background(), releasekit.DependencyPreparationRequest{RepositoryDir: repo})
if !errors.Is(err, exec.ErrNotFound) {
t.Fatalf("checkout error = %v, want missing Git", err)
}
if _, err := os.Stat(marker); !os.IsNotExist(err) {
t.Fatalf("Go ran after discovery failure: %v", err)
}
}
func publishPreparationTag(t *testing.T, repository string) string {
t.Helper()
return publishPreparationTagNamed(t, repository, "v1.2.3")