diff --git a/.dagger/modules/e2e/main.dang b/.dagger/modules/e2e/main.dang index 0977fce..343ac74 100644 --- a/.dagger/modules/e2e/main.dang +++ b/.dagger/modules/e2e/main.dang @@ -4,8 +4,17 @@ End-to-end checks for the Go module. type E2e { let generatedFixturePath: String! = "testdata/go-module-generate" let generatedFilePath: String! = generatedFixturePath + "/generated.go" + let baseFixturePath: String! = "testdata/go-module-custom-base" + let baseGeneratedFilePath: String! = baseFixturePath + "/generated.go" let emptyFixturePath: String! = "testdata/go-module-empty" + let base: Container! { + container + .from("golang:1.25-alpine") + .withEnvVariable("DAGGER_GO_CUSTOM_BASE", "yes") + .withNewFile("/custom-go-base", "yes\n") + } + let assert(condition: Boolean!, message: String!): Void { if (condition == false) { raise message @@ -58,19 +67,71 @@ type E2e { go(version: "1.25").module(ws, "fixtures/go-module-with-replace").test(ws) } + """ + A custom base container must be used for Go helpers, tests, and generate. + """ + pub baseCheck(ws: Workspace!): Void @check { + let tool = go( + base: base, + skipLintFilename: ".dagger-skip-lint-disabled", + skipTestFilename: ".dagger-skip-test-disabled", + skipGenerateFilename: ".dagger-skip-generate-disabled", + ) + let mod = tool.module(ws, baseFixturePath) + + assert( + tool.version == null, + "base-only constructor exposed a Go version for a black-box base image", + ) + + let conflict = try { + go(version: "1.25", base: base).version ?? "missing conflict" + } catch { + err => err.message + } + assert( + conflict.contains("version and base are mutually exclusive"), + "constructor did not reject version and base together: " + conflict, + ) + + let testDirs = mod.testDirectories(ws) + assert( + containsModulePath(testDirs.{path}, baseFixturePath), + "custom base test directory discovery did not include expected path", + ) + + mod.test(ws) + + let changes = mod.generate(ws) + assert( + containsPath(changes.addedPaths, baseGeneratedFilePath), + "custom base generate did not add expected path: " + baseGeneratedFilePath, + ) + assert( + changes.layer.file(baseGeneratedFilePath).contents.contains("GeneratedFromBase"), + "custom base generate did not produce expected contents", + ) + + null + } + """ Per-module introspection fields must expose the source selection intermediates. """ pub moduleIntrospectionCheck(ws: Workspace!): Void @check { let mod = go(version: "1.25").module(ws, "testdata/go-module-with-testdata/testdata") + assert( + go().version == "1.26", + "default constructor did not expose default Go version", + ) assert( mod.path == "testdata/go-module-with-testdata", "module lookup did not snap to containing module: " + mod.path, ) assert( mod.version == "1.25", - "module did not expose inherited Go version: " + mod.version, + "module did not expose inherited Go version: " + (mod.version ?? ""), ) let direct = go(version: "1.25").module(ws, "testdata/go-module-with-testdata/testdata", findUp: false) diff --git a/go.dang b/go.dang index 8ea0ad1..d9c9fd8 100644 --- a/go.dang +++ b/go.dang @@ -6,9 +6,56 @@ https://golang.org """ type Go { """ - Go version used for test and generate commands. - """ - pub version: String! = "1.26" + Go version used for the default Go base image. Null when a custom base is set. + """ + pub version: String + + """ + Base image used for Go test, generate, and helper containers. + """ + pub base: Container! + + new( + """ + Go version used for the default Go base image. + """ + version: String = null, + """ + Base image used for Go test, generate, and helper containers. + Mutually exclusive with version. + """ + base: Container = null, + """ + Extra workspace-root include patterns mounted for each module's Go commands. + Use this for fixtures, generator inputs, or other files not covered by the + built-in Go patterns. + """ + includeExtraFiles: [String!]! = [], + """ + Marker filename that skips lint checks when found at or above a Go module root. + """ + skipLintFilename: String! = ".dagger-skip-go-lint", + """ + Marker filename that skips tests when found at or above a Go module root. + """ + skipTestFilename: String! = ".dagger-skip-go-test", + """ + Marker filename that skips go generate when found at or above a Go module root. + """ + skipGenerateFilename: String! = ".dagger-skip-go-generate", + ) { + if (version != null and base != null) { + raise "version and base are mutually exclusive" + } else { + self.version = if (base == null) { version ?? "1.26" } else { null } + self.base = base ?? container.from("golang:" + (version ?? "1.26") + "-alpine") + self.includeExtraFiles = includeExtraFiles + self.skipLintFilename = skipLintFilename + self.skipTestFilename = skipTestFilename + self.skipGenerateFilename = skipGenerateFilename + self + } + } """ Extra workspace-root include patterns mounted for each module's Go commands. @@ -67,6 +114,7 @@ type Go { GoModule( path: modPath, version: version, + baseImage: base, includeExtraFiles: includeExtraFiles, skipLintFilename: skipLintFilename, skipTestFilename: skipTestFilename, @@ -133,6 +181,7 @@ type Go { path.trimPrefix("/") }, version: version, + baseImage: base, includeExtraFiles: includeExtraFiles, skipLintFilename: skipLintFilename, skipTestFilename: skipTestFilename, @@ -218,9 +267,14 @@ type GoModule { pub path: String! """ - Go version used by this module's test and generate containers. + Go version configured on the root Go module. Null when a custom base is set. + """ + pub version: String + + """ + Base image used by this module's Go containers. """ - pub version: String! + let baseImage: Container! """ Extra workspace-root include patterns mounted for this module's Go commands. @@ -268,8 +322,7 @@ type GoModule { Base container for the Go include helper. """ let goIncludesHelper(ws: Workspace!): Container! { - container - .from("golang:" + version + "-alpine") + baseImage .withoutEntrypoint .withWorkdir("/helpers/go-includes") .withMountedCache("/go/pkg/mod", cacheVolume("go-mod")) @@ -380,7 +433,7 @@ type GoModule { path: testPath, ws: ws, modulePath: path, - version: version, + baseImage: baseImage, includeExtraFiles: includeExtraFiles, skipTestFilename: skipTestFilename, testSkipMarkers: testSkipMarkers, @@ -472,8 +525,7 @@ type GoModule { Base Go container before source files and commands are added. """ pub base: Container! { - container - .from("golang:" + version + "-alpine") + baseImage .withoutEntrypoint .withWorkdir("/ws") .withMountedCache("/go/pkg/mod", cacheVolume("go-mod")) @@ -599,14 +651,14 @@ type GoDirectory { let modulePath: String! """ - Go version used by this directory's test container. + Extra workspace-root include patterns mounted for this directory's Go tests. """ - let version: String! + let includeExtraFiles: [String!]! """ - Extra workspace-root include patterns mounted for this directory's Go tests. + Base image used by this directory's Go containers. """ - let includeExtraFiles: [String!]! + let baseImage: Container! """ Marker filename that skips tests when found at or above this directory's Go module root. @@ -637,8 +689,7 @@ type GoDirectory { Base container for the Go include helper. """ let goIncludesHelper: Container! { - container - .from("golang:" + version + "-alpine") + baseImage .withoutEntrypoint .withWorkdir("/helpers/go-includes") .withMountedCache("/go/pkg/mod", cacheVolume("go-mod")) @@ -709,8 +760,7 @@ type GoDirectory { Base Go container before source files and commands are added. """ let base: Container! { - container - .from("golang:" + version + "-alpine") + baseImage .withoutEntrypoint .withWorkdir("/ws") .withMountedCache("/go/pkg/mod", cacheVolume("go-mod")) diff --git a/testdata/go-module-custom-base/.dagger-skip-go-generate b/testdata/go-module-custom-base/.dagger-skip-go-generate new file mode 100644 index 0000000..e32e76d --- /dev/null +++ b/testdata/go-module-custom-base/.dagger-skip-go-generate @@ -0,0 +1 @@ +skip diff --git a/testdata/go-module-custom-base/.dagger-skip-go-lint b/testdata/go-module-custom-base/.dagger-skip-go-lint new file mode 100644 index 0000000..e32e76d --- /dev/null +++ b/testdata/go-module-custom-base/.dagger-skip-go-lint @@ -0,0 +1 @@ +skip diff --git a/testdata/go-module-custom-base/.dagger-skip-go-test b/testdata/go-module-custom-base/.dagger-skip-go-test new file mode 100644 index 0000000..e32e76d --- /dev/null +++ b/testdata/go-module-custom-base/.dagger-skip-go-test @@ -0,0 +1 @@ +skip diff --git a/testdata/go-module-custom-base/cmd/writegenerated/main.go b/testdata/go-module-custom-base/cmd/writegenerated/main.go new file mode 100644 index 0000000..fdfecf9 --- /dev/null +++ b/testdata/go-module-custom-base/cmd/writegenerated/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + if got := os.Getenv("DAGGER_GO_CUSTOM_BASE"); got != "yes" { + panic(fmt.Sprintf("DAGGER_GO_CUSTOM_BASE = %q, want yes", got)) + } + if _, err := os.Stat("/custom-go-base"); err != nil { + panic(err) + } + + const contents = `package custombase + +const GeneratedFromBase = true +` + if err := os.WriteFile("generated.go", []byte(contents), 0o644); err != nil { + panic(err) + } +} diff --git a/testdata/go-module-custom-base/custom_base_test.go b/testdata/go-module-custom-base/custom_base_test.go new file mode 100644 index 0000000..3331c87 --- /dev/null +++ b/testdata/go-module-custom-base/custom_base_test.go @@ -0,0 +1,15 @@ +package custombase + +import ( + "os" + "testing" +) + +func TestBase(t *testing.T) { + if got := os.Getenv("DAGGER_GO_CUSTOM_BASE"); got != "yes" { + t.Fatalf("DAGGER_GO_CUSTOM_BASE = %q, want yes", got) + } + if _, err := os.Stat("/custom-go-base"); err != nil { + t.Fatalf("custom base marker file is missing: %v", err) + } +} diff --git a/testdata/go-module-custom-base/generate.go b/testdata/go-module-custom-base/generate.go new file mode 100644 index 0000000..7a03a87 --- /dev/null +++ b/testdata/go-module-custom-base/generate.go @@ -0,0 +1,3 @@ +package custombase + +//go:generate go run ./cmd/writegenerated diff --git a/testdata/go-module-custom-base/go.mod b/testdata/go-module-custom-base/go.mod new file mode 100644 index 0000000..892a89e --- /dev/null +++ b/testdata/go-module-custom-base/go.mod @@ -0,0 +1,3 @@ +module example.com/custombase + +go 1.25