Skip to content

Commit 53de696

Browse files
committed
Fix included-build outputs missing from save bundles
Kong's type:"path" resolved --included-build values to absolute paths, causing ConventionBuildDirs to construct invalid doubled paths that silently matched nothing. Drop type:"path", validate paths upfront, and bake an included build into the default integration fixture.
1 parent 0c128dd commit 53de696

11 files changed

Lines changed: 168 additions & 50 deletions

File tree

cmd/gradle-cache/integration_test.go

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ func TestIntegrationGradleBuildCycle(t *testing.T) {
150150
"--cache-key", ctx.cacheKey,
151151
"--commit", commitSHA,
152152
"--gradle-user-home", ctx.gradleUserHome,
153+
"--included-build", "build-logic",
153154
)
154155
runCLI(t, binaryPath, ctx, saveArgs...)
155156

@@ -164,10 +165,15 @@ func TestIntegrationGradleBuildCycle(t *testing.T) {
164165
"--ref", commitSHA,
165166
"--git-dir", ctx.projectDir,
166167
"--gradle-user-home", ctx.gradleUserHome,
168+
"--included-build", "build-logic",
167169
)
168170
runCLI(t, binaryPath, ctx, restoreArgs...)
169171

170172
t.Log("Step 4: Verifying restore...")
173+
buildLogicBuildDir := filepath.Join(ctx.projectDir, "build-logic", "build")
174+
if _, err := os.Stat(buildLogicBuildDir); err != nil {
175+
t.Fatal("build-logic/build/ was NOT restored")
176+
}
171177
verifyRestore(t, ctx)
172178
}
173179

@@ -525,6 +531,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
525531
"--cache-key", ctx.cacheKey,
526532
"--commit", commitSHA,
527533
"--gradle-user-home", ctx.gradleUserHome,
534+
"--included-build", "build-logic",
528535
)
529536
runCLI(t, binaryPath, ctx, saveArgs...)
530537

@@ -537,6 +544,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
537544
"--ref", commitSHA,
538545
"--git-dir", ctx.projectDir,
539546
"--gradle-user-home", ctx.gradleUserHome,
547+
"--included-build", "build-logic",
540548
)
541549
runCLI(t, binaryPath, ctx, restoreArgs...)
542550

@@ -574,6 +582,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
574582
"--branch", "test-branch",
575583
"--gradle-user-home", ctx.gradleUserHome,
576584
"--project-dir", ctx.projectDir,
585+
"--included-build", "build-logic",
577586
)
578587
runCLI(t, binaryPath, ctx, saveDeltaArgs...)
579588

@@ -590,6 +599,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
590599
"--branch", "test-branch",
591600
"--gradle-user-home", freshHome,
592601
"--project-dir", ctx.projectDir,
602+
"--included-build", "build-logic",
593603
)
594604
runCLI(t, binaryPath, ctx, freshRestoreDelta...)
595605

@@ -618,6 +628,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
618628
"--branch", "test-branch",
619629
"--gradle-user-home", ctx.gradleUserHome,
620630
"--project-dir", ctx.projectDir,
631+
"--included-build", "build-logic",
621632
)
622633
runCLI(t, binaryPath, ctx, fullRestoreDelta...)
623634

@@ -832,22 +843,42 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
832843
}
833844

834845
for _, tt := range []struct {
835-
name string
836-
fixture string
837-
buildFile string
838-
change string // appended to build file to invalidate CC
846+
name string
847+
fixture string
848+
mutate func(t *testing.T, projectDir string) // invalidate CC
839849
}{
840850
{
841-
name: "groovy-dsl",
842-
fixture: "groovy-project",
843-
buildFile: "build.gradle",
844-
change: "\n// force CC invalidation\n",
851+
name: "groovy-dsl",
852+
fixture: "groovy-project",
853+
mutate: func(t *testing.T, projectDir string) {
854+
appendToFile(t, filepath.Join(projectDir, "build.gradle"), "\n// force CC invalidation\n")
855+
},
845856
},
846857
{
847-
name: "kotlin-dsl",
848-
fixture: "gradle-project",
849-
buildFile: "build.gradle.kts",
850-
change: "\n// force CC invalidation\n",
858+
name: "kotlin-dsl",
859+
fixture: "gradle-project",
860+
mutate: func(t *testing.T, projectDir string) {
861+
appendToFile(t, filepath.Join(projectDir, "build.gradle.kts"), "\n// force CC invalidation\n")
862+
},
863+
},
864+
{
865+
name: "included-build-plugin-change",
866+
fixture: "gradle-project",
867+
mutate: func(t *testing.T, projectDir string) {
868+
must(t, os.WriteFile(
869+
filepath.Join(projectDir, "build-logic", "src", "main", "java", "com", "example", "IncludedPlugin.java"),
870+
[]byte(`package com.example;
871+
872+
import org.gradle.api.Plugin;
873+
import org.gradle.api.Project;
874+
875+
public class IncludedPlugin implements Plugin<Project> {
876+
@Override public void apply(Project project) {
877+
project.getLogger().lifecycle("IncludedPlugin applied (modified)");
878+
}
879+
}
880+
`), 0o644))
881+
},
851882
},
852883
} {
853884
tt := tt
@@ -867,6 +898,7 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
867898
"--cache-key", ctx.cacheKey,
868899
"--commit", commitSHA,
869900
"--gradle-user-home", ctx.gradleUserHome,
901+
"--included-build", "build-logic",
870902
)
871903
runCLI(t, binaryPath, ctx, saveArgs...)
872904

@@ -880,16 +912,12 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
880912
"--ref", commitSHA,
881913
"--git-dir", ctx.projectDir,
882914
"--gradle-user-home", ctx.gradleUserHome,
915+
"--included-build", "build-logic",
883916
)
884917
runCLI(t, binaryPath, ctx, restoreArgs...)
885918

886-
// Modify build file to invalidate configuration cache.
887-
buildFilePath := filepath.Join(ctx.projectDir, tt.buildFile)
888-
f, err := os.OpenFile(buildFilePath, os.O_APPEND|os.O_WRONLY, 0o644)
889-
must(t, err)
890-
_, err = f.WriteString(tt.change)
891-
must(t, err)
892-
must(t, f.Close())
919+
// Mutate the project to invalidate configuration cache.
920+
tt.mutate(t, ctx.projectDir)
893921

894922
output := gradleRun(t, ctx.projectDir, ctx.gradlew, ctx.gradleUserHome, "build")
895923
if !strings.Contains(output, "Calculating task graph") &&
@@ -905,6 +933,7 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
905933
"--branch", "cc-test-branch",
906934
"--gradle-user-home", ctx.gradleUserHome,
907935
"--project-dir", ctx.projectDir,
936+
"--included-build", "build-logic",
908937
)
909938
runCLI(t, binaryPath, ctx, saveDeltaArgs...)
910939

@@ -919,6 +948,7 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
919948
"--branch", "cc-test-branch",
920949
"--gradle-user-home", ctx.gradleUserHome,
921950
"--project-dir", ctx.projectDir,
951+
"--included-build", "build-logic",
922952
)
923953
runCLI(t, binaryPath, ctx, restoreDeltaArgs...)
924954

@@ -928,7 +958,7 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
928958
t.Fatalf("configuration-cache dir not restored: %v", err)
929959
}
930960

931-
// ── Step 5: Verify CC hit with the modified build file ──────
961+
// ── Step 5: Verify CC hit after delta restore ──────────────
932962
t.Log("Step 5: Verifying configuration cache hit...")
933963
output = gradleRun(t, ctx.projectDir, ctx.gradlew, ctx.gradleUserHome, "build")
934964

@@ -945,6 +975,15 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
945975
}
946976
}
947977

978+
func appendToFile(t *testing.T, path, content string) {
979+
t.Helper()
980+
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0o644)
981+
must(t, err)
982+
_, err = f.WriteString(content)
983+
must(t, err)
984+
must(t, f.Close())
985+
}
986+
948987
func extractLine(output, substr string) string {
949988
for _, line := range strings.Split(output, "\n") {
950989
if strings.Contains(strings.ToLower(line), strings.ToLower(substr)) {

cmd/gradle-cache/main.go

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ package main
44

55
import (
66
"context"
7+
"fmt"
78
"log/slog"
89
"os"
10+
"path/filepath"
911
"runtime"
1012
"runtime/pprof"
13+
"strings"
1114

1215
"github.com/alecthomas/errors"
1316
"github.com/alecthomas/kong"
@@ -61,6 +64,25 @@ func (f *backendFlags) validate() error {
6164
return nil
6265
}
6366

67+
// validateIncludedBuilds checks that each --included-build value refers to an
68+
// existing directory (or, for glob patterns like "build-logic/*", that the
69+
// parent directory exists). baseDir is the directory paths are resolved
70+
// against (typically the project directory).
71+
func validateIncludedBuilds(baseDir string, entries []string) error {
72+
for _, entry := range entries {
73+
dir := strings.TrimSuffix(entry, "/*")
74+
path := filepath.Join(baseDir, dir)
75+
info, err := os.Stat(path)
76+
if err != nil {
77+
return fmt.Errorf("--included-build %q: %w", entry, err)
78+
}
79+
if !info.IsDir() {
80+
return fmt.Errorf("--included-build %q: not a directory", entry)
81+
}
82+
}
83+
return nil
84+
}
85+
6486
// ── Restore ─────────────────────────────────────────────────────────────────
6587

6688
type RestoreCmd struct {
@@ -71,11 +93,17 @@ type RestoreCmd struct {
7193
Commit string `help:"Specific commit SHA to try directly, skipping history walk."`
7294
MaxBlocks int `help:"Number of distinct-author commit blocks to search." default:"20"`
7395
GradleUserHome string `help:"Path to GRADLE_USER_HOME." env:"GRADLE_USER_HOME" type:"path"`
74-
IncludedBuilds []string `help:"Included build directories whose build/ output to restore. May be repeated." name:"included-build" type:"path"`
96+
ProjectDir string `help:"Project directory containing included builds and .gradle/." default:"." type:"path"`
97+
IncludedBuilds []string `help:"Included build directories whose build/ output to restore. May be repeated." name:"included-build"`
7598
Branch string `help:"Branch name to also apply a delta bundle for." optional:""`
7699
}
77100

78-
func (c *RestoreCmd) AfterApply() error { return c.validate() }
101+
func (c *RestoreCmd) AfterApply() error {
102+
if err := c.validate(); err != nil {
103+
return err
104+
}
105+
return validateIncludedBuilds(c.ProjectDir, c.IncludedBuilds)
106+
}
79107

80108
func (c *RestoreCmd) Run(ctx context.Context, metrics gradlecache.MetricsClient) error {
81109
slog.Debug(gradleUserHomeEnv, "path", c.GradleUserHome)
@@ -90,6 +118,7 @@ func (c *RestoreCmd) Run(ctx context.Context, metrics gradlecache.MetricsClient)
90118
Commit: c.Commit,
91119
MaxBlocks: c.MaxBlocks,
92120
GradleUserHome: c.GradleUserHome,
121+
ProjectDir: c.ProjectDir,
93122
IncludedBuilds: c.IncludedBuilds,
94123
Branch: c.Branch,
95124
Metrics: metrics,
@@ -104,10 +133,15 @@ type RestoreDeltaCmd struct {
104133
Branch string `help:"Branch name to look up a delta for." required:""`
105134
GradleUserHome string `help:"Path to GRADLE_USER_HOME." env:"GRADLE_USER_HOME" type:"path"`
106135
ProjectDir string `help:"Project directory for routing project-specific cache entries." type:"path"`
107-
IncludedBuilds []string `help:"Included build directories whose build/ output to route. May be repeated." name:"included-build" type:"path"`
136+
IncludedBuilds []string `help:"Included build directories whose build/ output to route. May be repeated." name:"included-build"`
108137
}
109138

110-
func (c *RestoreDeltaCmd) AfterApply() error { return c.validate() }
139+
func (c *RestoreDeltaCmd) AfterApply() error {
140+
if err := c.validate(); err != nil {
141+
return err
142+
}
143+
return validateIncludedBuilds(c.ProjectDir, c.IncludedBuilds)
144+
}
111145

112146
func (c *RestoreDeltaCmd) Run(ctx context.Context, metrics gradlecache.MetricsClient) error {
113147
slog.Debug(gradleUserHomeEnv, "path", c.GradleUserHome)
@@ -133,10 +167,16 @@ type SaveCmd struct {
133167
Commit string `help:"Commit SHA to tag this bundle with. Defaults to HEAD of --git-dir."`
134168
GitDir string `help:"Path to the git repository." default:"." type:"path" hidden:""`
135169
GradleUserHome string `help:"Path to GRADLE_USER_HOME." env:"GRADLE_USER_HOME" type:"path"`
136-
IncludedBuilds []string `help:"Included build directories whose build/ output to archive. May be repeated." name:"included-build" type:"path"`
170+
ProjectDir string `help:"Project directory containing included builds and .gradle/." default:"." type:"path"`
171+
IncludedBuilds []string `help:"Included build directories whose build/ output to archive. May be repeated." name:"included-build"`
137172
}
138173

139-
func (c *SaveCmd) AfterApply() error { return c.validate() }
174+
func (c *SaveCmd) AfterApply() error {
175+
if err := c.validate(); err != nil {
176+
return err
177+
}
178+
return validateIncludedBuilds(c.ProjectDir, c.IncludedBuilds)
179+
}
140180

141181
func (c *SaveCmd) Run(ctx context.Context, metrics gradlecache.MetricsClient) error {
142182
slog.Debug(gradleUserHomeEnv, "path", c.GradleUserHome)
@@ -149,6 +189,7 @@ func (c *SaveCmd) Run(ctx context.Context, metrics gradlecache.MetricsClient) er
149189
Commit: c.Commit,
150190
GitDir: c.GitDir,
151191
GradleUserHome: c.GradleUserHome,
192+
ProjectDir: c.ProjectDir,
152193
IncludedBuilds: c.IncludedBuilds,
153194
Metrics: metrics,
154195
})
@@ -162,10 +203,15 @@ type SaveDeltaCmd struct {
162203
Branch string `help:"Branch name to save the delta under." required:""`
163204
GradleUserHome string `help:"Path to GRADLE_USER_HOME." env:"GRADLE_USER_HOME" type:"path"`
164205
ProjectDir string `help:"Project directory to scan for project-specific cache changes." type:"path"`
165-
IncludedBuilds []string `help:"Included build directories whose build/ output to include in delta. May be repeated." name:"included-build" type:"path"`
206+
IncludedBuilds []string `help:"Included build directories whose build/ output to include in delta. May be repeated." name:"included-build"`
166207
}
167208

168-
func (c *SaveDeltaCmd) AfterApply() error { return c.validate() }
209+
func (c *SaveDeltaCmd) AfterApply() error {
210+
if err := c.validate(); err != nil {
211+
return err
212+
}
213+
return validateIncludedBuilds(c.ProjectDir, c.IncludedBuilds)
214+
}
169215

170216
func (c *SaveDeltaCmd) Run(ctx context.Context, metrics gradlecache.MetricsClient) error {
171217
slog.Debug(gradleUserHomeEnv, "path", c.GradleUserHome)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
plugins {
2+
`java-gradle-plugin`
3+
}
4+
5+
gradlePlugin {
6+
plugins {
7+
create("included") {
8+
id = "com.example.included"
9+
implementationClass = "com.example.IncludedPlugin"
10+
}
11+
}
12+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootProject.name = "build-logic"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.example;
2+
3+
import org.gradle.api.Plugin;
4+
import org.gradle.api.Project;
5+
6+
public class IncludedPlugin implements Plugin<Project> {
7+
@Override
8+
public void apply(Project project) {
9+
// no-op plugin — exercises the included-build path
10+
}
11+
}

cmd/gradle-cache/testdata/gradle-project/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
kotlin("jvm") version "2.3.20"
3+
id("com.example.included")
34
}
45

56
repositories {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1+
pluginManagement {
2+
includeBuild("build-logic")
3+
}
4+
15
rootProject.name = "cache-test"

cmd/gradle-cache/testdata/groovy-project/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
id 'java'
3+
id 'com.example.included'
34
}
45

56
repositories {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1+
pluginManagement {
2+
includeBuild('build-logic')
3+
}
4+
15
rootProject.name = 'groovy-cache-test'

0 commit comments

Comments
 (0)