Skip to content

Commit 3ff2b4e

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 3ff2b4e

9 files changed

Lines changed: 147 additions & 28 deletions

File tree

cmd/gradle-cache/integration_test.go

Lines changed: 63 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,6 +165,7 @@ 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

@@ -367,6 +369,14 @@ func verifyRestore(t *testing.T, ctx integrationCtx) {
367369
t.Fatalf("expected wrapper dir after restore: %v", err)
368370
}
369371

372+
// Included-build outputs should survive the round-trip.
373+
buildLogicBuildDir := filepath.Join(ctx.projectDir, "build-logic", "build")
374+
if _, err := os.Stat(buildLogicBuildDir); err != nil {
375+
t.Error("build-logic/build/ was NOT restored")
376+
} else {
377+
t.Log(" build-logic/build/ restored")
378+
}
379+
370380
ccRestored := filepath.Join(ctx.projectDir, ".gradle", "configuration-cache")
371381
if _, err := os.Stat(ccRestored); err != nil {
372382
t.Log(" configuration-cache dir was NOT restored")
@@ -525,6 +535,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
525535
"--cache-key", ctx.cacheKey,
526536
"--commit", commitSHA,
527537
"--gradle-user-home", ctx.gradleUserHome,
538+
"--included-build", "build-logic",
528539
)
529540
runCLI(t, binaryPath, ctx, saveArgs...)
530541

@@ -537,6 +548,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
537548
"--ref", commitSHA,
538549
"--git-dir", ctx.projectDir,
539550
"--gradle-user-home", ctx.gradleUserHome,
551+
"--included-build", "build-logic",
540552
)
541553
runCLI(t, binaryPath, ctx, restoreArgs...)
542554

@@ -574,6 +586,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
574586
"--branch", "test-branch",
575587
"--gradle-user-home", ctx.gradleUserHome,
576588
"--project-dir", ctx.projectDir,
589+
"--included-build", "build-logic",
577590
)
578591
runCLI(t, binaryPath, ctx, saveDeltaArgs...)
579592

@@ -590,6 +603,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
590603
"--branch", "test-branch",
591604
"--gradle-user-home", freshHome,
592605
"--project-dir", ctx.projectDir,
606+
"--included-build", "build-logic",
593607
)
594608
runCLI(t, binaryPath, ctx, freshRestoreDelta...)
595609

@@ -618,6 +632,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
618632
"--branch", "test-branch",
619633
"--gradle-user-home", ctx.gradleUserHome,
620634
"--project-dir", ctx.projectDir,
635+
"--included-build", "build-logic",
621636
)
622637
runCLI(t, binaryPath, ctx, fullRestoreDelta...)
623638

@@ -832,22 +847,42 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
832847
}
833848

834849
for _, tt := range []struct {
835-
name string
836-
fixture string
837-
buildFile string
838-
change string // appended to build file to invalidate CC
850+
name string
851+
fixture string
852+
mutate func(t *testing.T, projectDir string) // invalidate CC
839853
}{
840854
{
841-
name: "groovy-dsl",
842-
fixture: "groovy-project",
843-
buildFile: "build.gradle",
844-
change: "\n// force CC invalidation\n",
855+
name: "groovy-dsl",
856+
fixture: "groovy-project",
857+
mutate: func(t *testing.T, projectDir string) {
858+
appendToFile(t, filepath.Join(projectDir, "build.gradle"), "\n// force CC invalidation\n")
859+
},
845860
},
846861
{
847-
name: "kotlin-dsl",
848-
fixture: "gradle-project",
849-
buildFile: "build.gradle.kts",
850-
change: "\n// force CC invalidation\n",
862+
name: "kotlin-dsl",
863+
fixture: "gradle-project",
864+
mutate: func(t *testing.T, projectDir string) {
865+
appendToFile(t, filepath.Join(projectDir, "build.gradle.kts"), "\n// force CC invalidation\n")
866+
},
867+
},
868+
{
869+
name: "included-build-plugin-change",
870+
fixture: "gradle-project",
871+
mutate: func(t *testing.T, projectDir string) {
872+
must(t, os.WriteFile(
873+
filepath.Join(projectDir, "build-logic", "src", "main", "java", "com", "example", "IncludedPlugin.java"),
874+
[]byte(`package com.example;
875+
876+
import org.gradle.api.Plugin;
877+
import org.gradle.api.Project;
878+
879+
public class IncludedPlugin implements Plugin<Project> {
880+
@Override public void apply(Project project) {
881+
project.getLogger().lifecycle("IncludedPlugin applied (modified)");
882+
}
883+
}
884+
`), 0o644))
885+
},
851886
},
852887
} {
853888
tt := tt
@@ -867,6 +902,7 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
867902
"--cache-key", ctx.cacheKey,
868903
"--commit", commitSHA,
869904
"--gradle-user-home", ctx.gradleUserHome,
905+
"--included-build", "build-logic",
870906
)
871907
runCLI(t, binaryPath, ctx, saveArgs...)
872908

@@ -880,16 +916,12 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
880916
"--ref", commitSHA,
881917
"--git-dir", ctx.projectDir,
882918
"--gradle-user-home", ctx.gradleUserHome,
919+
"--included-build", "build-logic",
883920
)
884921
runCLI(t, binaryPath, ctx, restoreArgs...)
885922

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())
923+
// Mutate the project to invalidate configuration cache.
924+
tt.mutate(t, ctx.projectDir)
893925

894926
output := gradleRun(t, ctx.projectDir, ctx.gradlew, ctx.gradleUserHome, "build")
895927
if !strings.Contains(output, "Calculating task graph") &&
@@ -905,6 +937,7 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
905937
"--branch", "cc-test-branch",
906938
"--gradle-user-home", ctx.gradleUserHome,
907939
"--project-dir", ctx.projectDir,
940+
"--included-build", "build-logic",
908941
)
909942
runCLI(t, binaryPath, ctx, saveDeltaArgs...)
910943

@@ -919,6 +952,7 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
919952
"--branch", "cc-test-branch",
920953
"--gradle-user-home", ctx.gradleUserHome,
921954
"--project-dir", ctx.projectDir,
955+
"--included-build", "build-logic",
922956
)
923957
runCLI(t, binaryPath, ctx, restoreDeltaArgs...)
924958

@@ -928,7 +962,7 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
928962
t.Fatalf("configuration-cache dir not restored: %v", err)
929963
}
930964

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

@@ -945,6 +979,15 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
945979
}
946980
}
947981

982+
func appendToFile(t *testing.T, path, content string) {
983+
t.Helper()
984+
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0o644)
985+
must(t, err)
986+
_, err = f.WriteString(content)
987+
must(t, err)
988+
must(t, f.Close())
989+
}
990+
948991
func extractLine(output, substr string) string {
949992
for _, line := range strings.Split(output, "\n") {
950993
if strings.Contains(strings.ToLower(line), strings.ToLower(substr)) {

cmd/gradle-cache/main.go

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

55
import (
66
"context"
7+
"fmt"
78
"log/slog"
89
"os"
910
"runtime"
1011
"runtime/pprof"
12+
"strings"
1113

1214
"github.com/alecthomas/errors"
1315
"github.com/alecthomas/kong"
@@ -61,6 +63,26 @@ func (f *backendFlags) validate() error {
6163
return nil
6264
}
6365

66+
// validateIncludedBuilds checks that each --included-build value refers to an
67+
// existing directory (or, for glob patterns like "build-logic/*", that the
68+
// parent directory exists).
69+
func validateIncludedBuilds(entries []string) error {
70+
for _, entry := range entries {
71+
dir := entry
72+
if strings.HasSuffix(dir, "/*") {
73+
dir = strings.TrimSuffix(dir, "/*")
74+
}
75+
info, err := os.Stat(dir)
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,16 @@ 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+
IncludedBuilds []string `help:"Included build directories whose build/ output to restore. May be repeated." name:"included-build"`
7597
Branch string `help:"Branch name to also apply a delta bundle for." optional:""`
7698
}
7799

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

80107
func (c *RestoreCmd) Run(ctx context.Context, metrics gradlecache.MetricsClient) error {
81108
slog.Debug(gradleUserHomeEnv, "path", c.GradleUserHome)
@@ -104,10 +131,15 @@ type RestoreDeltaCmd struct {
104131
Branch string `help:"Branch name to look up a delta for." required:""`
105132
GradleUserHome string `help:"Path to GRADLE_USER_HOME." env:"GRADLE_USER_HOME" type:"path"`
106133
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"`
134+
IncludedBuilds []string `help:"Included build directories whose build/ output to route. May be repeated." name:"included-build"`
108135
}
109136

110-
func (c *RestoreDeltaCmd) AfterApply() error { return c.validate() }
137+
func (c *RestoreDeltaCmd) AfterApply() error {
138+
if err := c.validate(); err != nil {
139+
return err
140+
}
141+
return validateIncludedBuilds(c.IncludedBuilds)
142+
}
111143

112144
func (c *RestoreDeltaCmd) Run(ctx context.Context, metrics gradlecache.MetricsClient) error {
113145
slog.Debug(gradleUserHomeEnv, "path", c.GradleUserHome)
@@ -133,10 +165,15 @@ type SaveCmd struct {
133165
Commit string `help:"Commit SHA to tag this bundle with. Defaults to HEAD of --git-dir."`
134166
GitDir string `help:"Path to the git repository." default:"." type:"path" hidden:""`
135167
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"`
168+
IncludedBuilds []string `help:"Included build directories whose build/ output to archive. May be repeated." name:"included-build"`
137169
}
138170

139-
func (c *SaveCmd) AfterApply() error { return c.validate() }
171+
func (c *SaveCmd) AfterApply() error {
172+
if err := c.validate(); err != nil {
173+
return err
174+
}
175+
return validateIncludedBuilds(c.IncludedBuilds)
176+
}
140177

141178
func (c *SaveCmd) Run(ctx context.Context, metrics gradlecache.MetricsClient) error {
142179
slog.Debug(gradleUserHomeEnv, "path", c.GradleUserHome)
@@ -162,10 +199,15 @@ type SaveDeltaCmd struct {
162199
Branch string `help:"Branch name to save the delta under." required:""`
163200
GradleUserHome string `help:"Path to GRADLE_USER_HOME." env:"GRADLE_USER_HOME" type:"path"`
164201
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"`
202+
IncludedBuilds []string `help:"Included build directories whose build/ output to include in delta. May be repeated." name:"included-build"`
166203
}
167204

168-
func (c *SaveDeltaCmd) AfterApply() error { return c.validate() }
205+
func (c *SaveDeltaCmd) AfterApply() error {
206+
if err := c.validate(); err != nil {
207+
return err
208+
}
209+
return validateIncludedBuilds(c.IncludedBuilds)
210+
}
169211

170212
func (c *SaveDeltaCmd) Run(ctx context.Context, metrics gradlecache.MetricsClient) error {
171213
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)