From c43e300a7b70d00fceda4feffd0409bdf7b0b3cc Mon Sep 17 00:00:00 2001 From: Brian Chuo Date: Tue, 31 Mar 2026 11:36:47 -0400 Subject: [PATCH 1/6] Fix go work vendor command to exit with correctly exit with code 0 instead of 2 Signed-off-by: Brian Chuo --- scripts/gomod-patch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gomod-patch.sh b/scripts/gomod-patch.sh index 909afc7e0..3a1bc90af 100644 --- a/scripts/gomod-patch.sh +++ b/scripts/gomod-patch.sh @@ -110,7 +110,7 @@ EOF # Note: 'go work vendor' requires Go 1.22+ if [ -f "$gowork_path" ]; then # Check if 'go work vendor' is available (Go 1.22+) - if go work vendor -h >/dev/null 2>&1; then + if go help work vendor >/dev/null 2>&1; then echo " Running go work vendor to sync workspace vendor directory" go work vendor else From 7e47341042c9e31e69b0517798858d133b152a1d Mon Sep 17 00:00:00 2001 From: Brian Chuo Date: Wed, 1 Apr 2026 11:07:03 -0400 Subject: [PATCH 2/6] Add test Signed-off-by: Brian Chuo --- test/linux_target_test.go | 90 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/test/linux_target_test.go b/test/linux_target_test.go index 87ae1a08d..a870f1afe 100644 --- a/test/linux_target_test.go +++ b/test/linux_target_test.go @@ -2578,6 +2578,96 @@ func True(t interface{}, value bool, msgAndArgs ...interface{}) bool { }) }) + t.Run("gomod replace with go work and vendor syncs workspace transitive deps", func(t *testing.T) { + t.Parallel() + ctx := startTestSpan(baseCtx, t) + + // This test specifically covers the case where a replace directive introduces + // a transitive dependency that is only reachable through a workspace sub-module, + // not the root module. With the correct 'go work vendor' behaviour, the dep + // must appear in vendor/. With the broken 'GOWORK=off go mod vendor' fallback + // it will be missing, causing a GOPROXY=off build failure. + pg := dalec.ProgressGroup("Setup test context") + contextSt := llb.Scratch(). + // Root module — does NOT directly import testify, only sub does + File(llb.Mkfile("/go.mod", 0644, []byte( + "module example.com/root\n\ngo 1.22\n\nrequire example.com/sub v0.0.0\n\nreplace example.com/sub => ./sub\n", + )), pg). + File(llb.Mkfile("/go.work", 0644, []byte("go 1.22\n\nuse .\nuse ./sub\n")), pg). + File(llb.Mkfile("/main.go", 0644, []byte(`package main +func main() {} +`)), pg). + // Sub-module — imports testify (a workspace-only transitive dep) + File(llb.Mkdir("/sub", 0755), pg). + File(llb.Mkfile("/sub/go.mod", 0644, []byte( + "module example.com/sub\n\ngo 1.22\n\nrequire github.com/stretchr/testify v1.9.0\n", + )), pg). + File(llb.Mkfile("/sub/sub.go", 0644, []byte(`package sub +import _ "github.com/stretchr/testify/assert" +`)), pg). + // Minimal vendor dir — testify v1.9.0 stub + File(llb.Mkdir("/vendor/github.com/stretchr/testify/assert", 0755, llb.WithParents(true)), pg). + File(llb.Mkfile("/vendor/modules.txt", 0644, []byte(`## workspace +# github.com/stretchr/testify v1.9.0 +## explicit; go 1.17 +github.com/stretchr/testify/assert +`)), pg). + File(llb.Mkfile("/vendor/github.com/stretchr/testify/assert/assertions.go", 0644, []byte(`package assert +func True(t interface{}, value bool, msgAndArgs ...interface{}) bool { return value } +`)), pg) + + const contextName = "gowork-vendor-transitive-test" + spec := &dalec.Spec{ + Name: "test-gomod-gowork-vendor-transitive", + Version: "0.0.1", + Revision: "1", + License: "MIT", + Website: "https://github.com/project-dalec/dalec", + Vendor: "Dalec", + Packager: "Dalec", + Description: "Testing that go work vendor syncs workspace sub-module transitive deps", + Sources: map[string]dalec.Source{ + "src": { + Context: &dalec.SourceContext{Name: contextName}, + Generate: []*dalec.SourceGenerator{ + { + Gomod: &dalec.GeneratorGomod{ + Edits: &dalec.GomodEdits{ + Replace: []dalec.GomodReplace{ + // Bump testify — this is only a dep of the sub-module, + // not the root. GOWORK=off go mod vendor would miss it. + {Original: "github.com/stretchr/testify", Update: "github.com/stretchr/testify@v1.8.0"}, + }, + }, + }, + }, + }, + }, + }, + Dependencies: &dalec.PackageDependencies{ + Build: map[string]dalec.PackageConstraints{ + testConfig.GetPackage("golang"): {}, + }, + }, + Build: dalec.ArtifactBuild{ + Steps: []dalec.BuildStep{ + // The replace was applied + {Command: "grep -F 'github.com/stretchr/testify v1.8.0' ./src/vendor/modules.txt"}, + // The vendor entry was updated to v1.8.0, proving go work vendor ran + // (not GOWORK=off go mod vendor, which would not touch sub-module deps) + {Command: "grep -F 'v1.8.0' ./src/vendor/github.com/stretchr/testify/assert/assertions.go || grep -rF 'v1.8' ./src/vendor/github.com/stretchr/testify/"}, + // Builds cleanly with -mod=vendor, proving vendor is complete + {Command: "cd ./src && go build -mod=vendor ./..."}, + }, + }, + } + + testEnv.RunTest(ctx, t, func(ctx context.Context, client gwclient.Client) { + req := newSolveRequest(withBuildTarget(testConfig.Target.Container), withSpec(ctx, t, spec), withBuildContext(ctx, t, contextName, contextSt)) + solveT(ctx, t, client, req) + }) + }) + t.Run("gomod go work version sync", func(t *testing.T) { t.Parallel() ctx := startTestSpan(baseCtx, t) From f1c02bde89add4bcc7fe7846facef026a68bf290 Mon Sep 17 00:00:00 2001 From: Brian Chuo Date: Wed, 1 Apr 2026 11:48:58 -0400 Subject: [PATCH 3/6] Fix transitive deps test Signed-off-by: Brian Chuo --- test/linux_target_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/linux_target_test.go b/test/linux_target_test.go index a870f1afe..97cbe8bd0 100644 --- a/test/linux_target_test.go +++ b/test/linux_target_test.go @@ -2651,13 +2651,14 @@ func True(t interface{}, value bool, msgAndArgs ...interface{}) bool { return va }, Build: dalec.ArtifactBuild{ Steps: []dalec.BuildStep{ - // The replace was applied + // modules.txt should reflect the bumped version, proving go work vendor ran + // (GOWORK=off go mod vendor would not see the sub-module dep at all) {Command: "grep -F 'github.com/stretchr/testify v1.8.0' ./src/vendor/modules.txt"}, - // The vendor entry was updated to v1.8.0, proving go work vendor ran - // (not GOWORK=off go mod vendor, which would not touch sub-module deps) - {Command: "grep -F 'v1.8.0' ./src/vendor/github.com/stretchr/testify/assert/assertions.go || grep -rF 'v1.8' ./src/vendor/github.com/stretchr/testify/"}, - // Builds cleanly with -mod=vendor, proving vendor is complete - {Command: "cd ./src && go build -mod=vendor ./..."}, + // go work vendor also pulls in testify's own transitive deps (go-spew, go-difflib) + // which are only reachable via the sub-module. Their presence proves the full + // workspace graph was walked, not just the root module. + {Command: "test -d ./src/vendor/github.com/davecgh/go-spew"}, + {Command: "test -d ./src/vendor/github.com/pmezard/go-difflib"}, }, }, } From 8e1524f2bb856360bbdaacea00bea626af33437c Mon Sep 17 00:00:00 2001 From: Brian Chuo Date: Wed, 1 Apr 2026 14:07:20 -0400 Subject: [PATCH 4/6] Fix tests to lower min go version for old distros Signed-off-by: Brian Chuo --- test/linux_target_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/linux_target_test.go b/test/linux_target_test.go index 97cbe8bd0..dfc4d8d25 100644 --- a/test/linux_target_test.go +++ b/test/linux_target_test.go @@ -2591,16 +2591,16 @@ func True(t interface{}, value bool, msgAndArgs ...interface{}) bool { contextSt := llb.Scratch(). // Root module — does NOT directly import testify, only sub does File(llb.Mkfile("/go.mod", 0644, []byte( - "module example.com/root\n\ngo 1.22\n\nrequire example.com/sub v0.0.0\n\nreplace example.com/sub => ./sub\n", + "module example.com/root\n\ngo 1.18\n\nrequire example.com/sub v0.0.0\n\nreplace example.com/sub => ./sub\n", )), pg). - File(llb.Mkfile("/go.work", 0644, []byte("go 1.22\n\nuse .\nuse ./sub\n")), pg). + File(llb.Mkfile("/go.work", 0644, []byte("go 1.18\n\nuse .\nuse ./sub\n")), pg). File(llb.Mkfile("/main.go", 0644, []byte(`package main func main() {} `)), pg). // Sub-module — imports testify (a workspace-only transitive dep) File(llb.Mkdir("/sub", 0755), pg). File(llb.Mkfile("/sub/go.mod", 0644, []byte( - "module example.com/sub\n\ngo 1.22\n\nrequire github.com/stretchr/testify v1.9.0\n", + "module example.com/sub\n\ngo 1.18\n\nrequire github.com/stretchr/testify v1.9.0\n", )), pg). File(llb.Mkfile("/sub/sub.go", 0644, []byte(`package sub import _ "github.com/stretchr/testify/assert" From bf4842e9d6144271dcdffdb8aeb5824507b979c7 Mon Sep 17 00:00:00 2001 From: Brian Chuo Date: Wed, 1 Apr 2026 14:53:48 -0400 Subject: [PATCH 5/6] Attempt to fix tests again Signed-off-by: Brian Chuo --- test/linux_target_test.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/test/linux_target_test.go b/test/linux_target_test.go index dfc4d8d25..e2a19c9ef 100644 --- a/test/linux_target_test.go +++ b/test/linux_target_test.go @@ -2651,14 +2651,13 @@ func True(t interface{}, value bool, msgAndArgs ...interface{}) bool { return va }, Build: dalec.ArtifactBuild{ Steps: []dalec.BuildStep{ - // modules.txt should reflect the bumped version, proving go work vendor ran - // (GOWORK=off go mod vendor would not see the sub-module dep at all) - {Command: "grep -F 'github.com/stretchr/testify v1.8.0' ./src/vendor/modules.txt"}, - // go work vendor also pulls in testify's own transitive deps (go-spew, go-difflib) - // which are only reachable via the sub-module. Their presence proves the full - // workspace graph was walked, not just the root module. - {Command: "test -d ./src/vendor/github.com/davecgh/go-spew"}, - {Command: "test -d ./src/vendor/github.com/pmezard/go-difflib"}, + // testify/assert must be present in the vendor directory. + // This is only possible if 'go work vendor' walked the full workspace + // graph and included the sub-module's dependencies. If the broken + // 'GOWORK=off go mod vendor' fallback ran instead, only the root module's + // dependencies would be vendored — and the root module doesn't require + // testify, so it would be absent. + {Command: "test -d ./src/vendor/github.com/stretchr/testify/assert"}, }, }, } From 6be759b0f8231b6d29cf866aabeb49fd79c1b254 Mon Sep 17 00:00:00 2001 From: Brian Chuo Date: Thu, 2 Apr 2026 13:32:16 -0400 Subject: [PATCH 6/6] Add skip for older distros Signed-off-by: Brian Chuo --- test/linux_target_test.go | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/test/linux_target_test.go b/test/linux_target_test.go index e2a19c9ef..8c2e2dbf3 100644 --- a/test/linux_target_test.go +++ b/test/linux_target_test.go @@ -2582,6 +2582,12 @@ func True(t interface{}, value bool, msgAndArgs ...interface{}) bool { t.Parallel() ctx := startTestSpan(baseCtx, t) + // 'go work vendor' requires Go 1.22+. On older distros the script falls back + // to 'GOWORK=off go mod vendor', which does not walk workspace sub-modules. + // Skip rather than fail on those targets. + skip.If(t, !testConfig.SupportsGomodVersionUpdate, + "Test requires Go 1.22+ for 'go work vendor' support") + // This test specifically covers the case where a replace directive introduces // a transitive dependency that is only reachable through a workspace sub-module, // not the root module. With the correct 'go work vendor' behaviour, the dep @@ -2589,15 +2595,15 @@ func True(t interface{}, value bool, msgAndArgs ...interface{}) bool { // it will be missing, causing a GOPROXY=off build failure. pg := dalec.ProgressGroup("Setup test context") contextSt := llb.Scratch(). - // Root module — does NOT directly import testify, only sub does - File(llb.Mkfile("/go.mod", 0644, []byte( - "module example.com/root\n\ngo 1.18\n\nrequire example.com/sub v0.0.0\n\nreplace example.com/sub => ./sub\n", - )), pg). + // Root module — no dependencies at all. This is critical: with GOWORK=off + // go mod vendor, the root has nothing to vendor so testify would be absent. + // Only 'go work vendor' walks the full workspace and vendors sub-module deps. + File(llb.Mkfile("/go.mod", 0644, []byte("module example.com/root\n\ngo 1.18\n")), pg). File(llb.Mkfile("/go.work", 0644, []byte("go 1.18\n\nuse .\nuse ./sub\n")), pg). File(llb.Mkfile("/main.go", 0644, []byte(`package main func main() {} `)), pg). - // Sub-module — imports testify (a workspace-only transitive dep) + // Sub-module — requires testify. Only reachable via go.work, not via root go.mod. File(llb.Mkdir("/sub", 0755), pg). File(llb.Mkfile("/sub/go.mod", 0644, []byte( "module example.com/sub\n\ngo 1.18\n\nrequire github.com/stretchr/testify v1.9.0\n", @@ -2605,16 +2611,14 @@ func main() {} File(llb.Mkfile("/sub/sub.go", 0644, []byte(`package sub import _ "github.com/stretchr/testify/assert" `)), pg). - // Minimal vendor dir — testify v1.9.0 stub - File(llb.Mkdir("/vendor/github.com/stretchr/testify/assert", 0755, llb.WithParents(true)), pg). - File(llb.Mkfile("/vendor/modules.txt", 0644, []byte(`## workspace -# github.com/stretchr/testify v1.9.0 -## explicit; go 1.17 -github.com/stretchr/testify/assert -`)), pg). - File(llb.Mkfile("/vendor/github.com/stretchr/testify/assert/assertions.go", 0644, []byte(`package assert -func True(t interface{}, value bool, msgAndArgs ...interface{}) bool { return value } -`)), pg) + // Minimal vendor dir — just a marker file so gomod-patch.sh knows to run + // 'go work vendor'. Contains NO pre-existing testify files, so if testify + // appears in vendor after patching it proves 'go work vendor' ran (not + // 'GOWORK=off go mod vendor', which would produce an empty vendor for a + // root module with no dependencies). Adding files via patch is safe from + // the dpkg-source --include-removal issue; only deletions are skipped. + File(llb.Mkdir("/vendor", 0755), pg). + File(llb.Mkfile("/vendor/modules.txt", 0644, []byte("## workspace\n")), pg) const contextName = "gowork-vendor-transitive-test" spec := &dalec.Spec{