Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .dagger/modules/e2e/dagger.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "e2e",
"engineVersion": "v1.0.0-beta.7",
"sdk": {
"source": "dang"
},
"dependencies": [
{
"name": "go",
"source": "../../.."
}
]
}
58 changes: 58 additions & 0 deletions .dagger/modules/e2e/main.dang
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ type E2e {
let baseFixturePath: String! = "testdata/go-module-custom-base"
let baseGeneratedFilePath: String! = baseFixturePath + "/generated.go"
let emptyFixturePath: String! = "testdata/go-module-empty"
let cgoCxxFixturePath: String! = "testdata/go-module-cgo-cxx"
let lintFailureFixturePath: String! = "testdata/go-module-lint-fail"
let lint: [String!]! = [
"**",
"!" + baseFixturePath,
"!" + cgoCxxFixturePath,
"!" + emptyFixturePath,
"!" + lintFailureFixturePath,
"!testdata/go-module-excluded",
"!testdata/go-module-skip-tree",
]
let test: [String!]! = [
"**",
"!" + baseFixturePath,
"!" + cgoCxxFixturePath,
"!" + emptyFixturePath,
"!" + lintFailureFixturePath,
"!testdata/go-module-excluded",
"!testdata/go-module-skip-tree",
]
Expand Down Expand Up @@ -63,6 +69,14 @@ type E2e {
go(version: "1.26.1", generate: patterns).module(ws, modPath, findUp: false).skipGenerate(ws)
}

"""
Whether a module at modPath is excluded from lint selection `patterns`.
Uses findUp: false so modPath need not be a real module on disk.
"""
let lintSelectsOut(ws: Workspace!, patterns: [String!]!, modPath: String!): Boolean! {
go(version: "1.26.1", lint: patterns).module(ws, modPath, findUp: false).skipLint(ws)
}

"""
Module discovery (findConfigDirs) returns exactly the directories holding a
go.mod, at any depth, and nothing else.
Expand Down Expand Up @@ -124,6 +138,13 @@ type E2e {
go(version: "1.26.1").module(ws, "fixtures/go-module-with-replace").test(ws)
}

"""
Lint can typecheck cgo packages that need a C++ compiler.
"""
pub cgoCxxLintCheck(ws: Workspace!): Void @check {
go(version: "1.26.1").module(ws, cgoCxxFixturePath).lint(ws)
}

"""
A custom base container must be used for Go helpers, tests, and generate.
"""
Expand Down Expand Up @@ -571,6 +592,43 @@ type E2e {
# No includes means "everything", minus excludes.
assert(selectsOut(ws, ["!docs"], "docs"), "exclude-only list did not exclude")
assert(selectsOut(ws, ["!docs"], "core") == false, "exclude-only list did not include the rest")
assert(lintSelectsOut(ws, ["!docs"], "docs"), "lint exclude-only list did not exclude")
assert(lintSelectsOut(ws, ["!docs"], "core") == false, "lint exclude-only list did not include the rest")
assert(
lintSelectsOut(ws, ["[\"!docs\"", "\"!tmp\"]"], "docs"),
"lint did not normalize beta workspace-settings array fragments before excluding",
)
assert(
lintSelectsOut(ws, ["[\"!docs\"", "\"!tmp\"]"], "core") == false,
"lint did not normalize beta workspace-settings array fragments before including the rest",
)

let excludeOnlyLintModules = go(
version: "1.26.1",
lint: ["!" + emptyFixturePath],
).modules(ws, includeSkipLint: false).{path}
assert(
containsModulePath(excludeOnlyLintModules, lintFailureFixturePath),
"exclude-only lint settings excluded every non-matching module",
)
assert(
containsModulePath(excludeOnlyLintModules, emptyFixturePath) == false,
"exclude-only lint settings included the excluded module",
)

let lintFailure = try {
go(
version: "1.26.1",
lint: ["!" + emptyFixturePath],
).module(ws, lintFailureFixturePath).lint(ws)
"lint unexpectedly succeeded"
} catch {
err => err.message
}
assert(
lintFailure.contains("exit code"),
"exclude-only lint settings did not run lint on a non-excluded module: " + lintFailure,
)

# "." selects only the root module.
assert(selectsOut(ws, ["."], "core"), "\".\" leaked to a non-root module")
Expand Down
2 changes: 1 addition & 1 deletion dagger.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "go",
"engineVersion": "v0.20.6",
"engineVersion": "v1.0.0-beta.7",
"sdk": {
"source": "dang"
},
Expand Down
118 changes: 43 additions & 75 deletions go.dang
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,26 @@ type Go {
self.version = if (base == null) { version ?? "1.26" } else { null }
self.base = base ?? container.from("golang:" + (version ?? "1.26") + "-alpine")
self.includeExtraFiles = includeExtraFiles
self.lint = lint
self.test = test
self.generate = generate
self.lint = normalizePatterns(lint)
self.test = normalizePatterns(test)
self.generate = normalizePatterns(generate)
self
}
}

"""
Normalize selector strings from workspace module settings.

Dagger v1.0.0-beta.4 may pass TOML string arrays as JSON-fragment strings
like ["[\"!infra\"", "\"!docs\"]"]; trim those wrappers so selection still
sees the intended patterns.
"""
let normalizePatterns(patterns: [String!]!): [String!]! {
patterns.map { p =>
p.trimPrefix("[").trimSuffix("]").trimPrefix("\"").trimSuffix("\"")
}
}

"""
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
Expand Down Expand Up @@ -292,29 +305,6 @@ type GoModule {
}
}

"""
Absolute workspace path for this module root.
"""
let workspacePath: String! {
if (path == ".") { "/" } else { "/" + path.trimSuffix("/") }
}

"""
Base container for the Go include helper.
"""
let goIncludesHelper(ws: Workspace!): Container! {
baseImage
.withoutEntrypoint
.withWorkdir("/helpers/go-includes")
.withMountedCache("/go/pkg/mod", cacheVolume("go-mod"))
.withMountedCache("/root/.cache/go-build", cacheVolume("go-build"))
.withDirectory("/helpers/go-includes", currentModule.source.directory("helpers/go-includes"))
.withExec(["go", "build", "-o", "/usr/local/bin/go-includes", "."])
.withDirectory("/ws", directory)
.withWorkdir("/ws")
.withEnvVariable("DAGGER_GO_WORKSPACE_ID", toJSON(ws.id))
}

"""
Whether this module falls outside the configured lint selection.
"""
Expand Down Expand Up @@ -350,7 +340,7 @@ type GoModule {
literal: true,
filesOnly: true,
limit: 1,
).{id}
).{{id}}
.length > 0
}
}
Expand Down Expand Up @@ -412,10 +402,9 @@ type GoModule {
}

"""
Discovery output directory: one file of include patterns per module, named
"<moduleRoot>.inc" (the root module is "_root_.inc"). For a given mode every
module produces the same container and exec, so discovery runs once per
workspace rather than once per module, and each module reads only its own file.
Discovery output directory: include patterns and test directories for every
module. For a given mode every module produces the same container and exec,
so discovery runs once per workspace and each module reads only its own files.
"""
let allIncludesDir(
ws: Workspace!,
Expand Down Expand Up @@ -451,12 +440,15 @@ type GoModule {
Directories in this module containing Go test files.
"""
pub testDirectories(ws: Workspace!): [GoDirectory!]! {
goIncludesHelper(ws)
.withExec(
["go-includes", "--output", "/output", "--test-dirs", workspacePath],
experimentalPrivilegedNesting: true,
)
.file("/output")
let outputStem = if (path == ".") { "_root_" } else { path }
let index = allIncludesDir(ws, lint: false, test: true, generate: false)
let discoveredIncludes = index
.file(outputStem + ".inc")
.contents
.split("\n")
.filter { pattern => pattern != "" }
index
.file(outputStem + ".testdirs")
.contents
.split("\n")
.filter { testPath => testPath != "" }
Expand All @@ -467,6 +459,7 @@ type GoModule {
modulePath: path,
baseImage: baseImage,
includeExtraFiles: includeExtraFiles,
includeDiscovered: discoveredIncludes,
testPatterns: testPatterns,
)
}
Expand All @@ -489,7 +482,13 @@ type GoModule {
[
subpath("**/*.go"),
subpath("**/*.c"),
subpath("**/*.cc"),
subpath("**/*.cpp"),
subpath("**/*.cxx"),
subpath("**/*.h"),
subpath("**/*.hh"),
subpath("**/*.hpp"),
subpath("**/*.hxx"),
subpath("**/*.s"),
subpath("**/*.S"),
subpath("**/*.syso"),
Expand Down Expand Up @@ -621,6 +620,8 @@ type GoModule {
let lintImage = "docker.io/golangci/golangci-lint:v2.11.4-alpine@sha256:" + "72bcd68512b4e27540dd3a778a1b7afd45759d8145cfb3c089f1d7af53e718e9"
container
.from(lintImage)
# cgo dependencies may need a C/C++ toolchain during typecheck.
.withExec(["apk", "add", "--no-cache", "build-base"])
.withMountedCache("/go/pkg/mod", cacheVolume("go-mod"))
.withMountedCache("/root/.cache/go-build", cacheVolume("go-build"))
.withMountedCache("/root/.cache/golangci-lint", cacheVolume("golangci-lint"))
Expand Down Expand Up @@ -686,6 +687,11 @@ type GoDirectory {
"""
let includeExtraFiles: [String!]!

"""
Additional workspace inputs discovered for this directory's Go module.
"""
let includeDiscovered: [String!]!

"""
Base image used by this directory's Go containers.
"""
Expand All @@ -707,29 +713,6 @@ type GoDirectory {
}
}

"""
Absolute workspace path for this directory's Go module root.
"""
let workspacePath: String! {
if (modulePath == ".") { "/" } else { "/" + modulePath.trimSuffix("/") }
}

"""
Base container for the Go include helper.
"""
let goIncludesHelper: Container! {
baseImage
.withoutEntrypoint
.withWorkdir("/helpers/go-includes")
.withMountedCache("/go/pkg/mod", cacheVolume("go-mod"))
.withMountedCache("/root/.cache/go-build", cacheVolume("go-build"))
.withDirectory("/helpers/go-includes", currentModule.source.directory("helpers/go-includes"))
.withExec(["go", "build", "-o", "/usr/local/bin/go-includes", "."])
.withDirectory("/ws", directory)
.withWorkdir("/ws")
.withEnvVariable("DAGGER_GO_WORKSPACE_ID", toJSON(ws.id))
}

"""
Whether this directory's module falls outside the configured test selection.

Expand Down Expand Up @@ -759,21 +742,6 @@ type GoDirectory {
}
}

"""
Additional workspace include patterns discovered for this directory's Go tests.
"""
let includeDiscovered: [String!]! {
goIncludesHelper
.withExec(
["go-includes", "--output", "/output", "--test", workspacePath],
experimentalPrivilegedNesting: true,
)
.file("/output")
.contents
.split("\n")
.filter { pattern => pattern != "" }
}

"""
Final workspace include patterns used to build this directory's source.
"""
Expand Down
31 changes: 30 additions & 1 deletion helpers/go-includes/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,20 @@ func runAll(cliArgs []string) error {
// The ".inc" suffix keeps a module's file distinct from a nested module's
// subdirectory (e.g. "sdk/go.inc" never collides with the "sdk/go/" tree).
func moduleIncludeFile(moduleRoot string) string {
return moduleOutputFile(moduleRoot, ".inc")
}

// moduleTestDirectoriesFile returns the per-module test-directory output file.
func moduleTestDirectoriesFile(moduleRoot string) string {
return moduleOutputFile(moduleRoot, ".testdirs")
}

func moduleOutputFile(moduleRoot, suffix string) string {
name := moduleRoot
if moduleRoot == "." {
name = "_root_"
}
return filepath.FromSlash(name) + ".inc"
return filepath.FromSlash(name) + suffix
}

// writeAllDir writes one file of include patterns per module, so each consumer
Expand All @@ -69,6 +78,16 @@ func (index *localIndex) writeAllDir(dir string, lint, test, generate bool) erro
if err := os.WriteFile(outPath, []byte(data), 0o644); err != nil {
return err
}

testDirs := index.testDirectoriesFor(moduleRoot)
testDirsData := strings.Join(testDirs, "\n")
if len(testDirs) > 0 {
testDirsData += "\n"
}
testDirsPath := filepath.Join(dir, moduleTestDirectoriesFile(moduleRoot))
if err := os.WriteFile(testDirsPath, []byte(testDirsData), 0o644); err != nil {
return err
}
}
return nil
}
Expand Down Expand Up @@ -215,6 +234,16 @@ func (index *localIndex) directives(moduleRoot string) ([]goDirective, error) {
return directives, nil
}

func (index *localIndex) testDirectoriesFor(moduleRoot string) []string {
var testFiles []string
for _, filePath := range index.goFilesByModule[moduleRoot] {
if strings.HasSuffix(filePath, "_test.go") {
testFiles = append(testFiles, filePath)
}
}
return testDirectoriesFromFiles(testFiles)
}

// replaceModules resolves local go.mod replace targets to module roots.
func (index *localIndex) replaceModules(moduleRoot string) ([]string, error) {
goModPath := path.Join(moduleRoot, "go.mod")
Expand Down
15 changes: 15 additions & 0 deletions helpers/go-includes/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,21 @@ func TestTestDirectoriesFromFiles(t *testing.T) {
}
}

func TestLocalIndexTestDirectories(t *testing.T) {
index := &localIndex{goFilesByModule: map[string][]string{
"api": {
"api/auth/auth.go",
"api/auth/auth_test.go",
"api/auth/more_test.go",
"api/db/db_test.go",
},
}}
want := []string{"api/auth", "api/db"}
if got := index.testDirectoriesFor("api"); !reflect.DeepEqual(got, want) {
t.Fatalf("testDirectoriesFor mismatch:\n got: %#v\nwant: %#v", got, want)
}
}

func TestInvalidQuotedDirectiveArg(t *testing.T) {
_, err := (goDirective{
position: "test.go:1:1",
Expand Down
Loading