Skip to content
Merged
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
79 changes: 59 additions & 20 deletions cmd/gradle-cache/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ func TestIntegrationGradleBuildCycle(t *testing.T) {
"--cache-key", ctx.cacheKey,
"--commit", commitSHA,
"--gradle-user-home", ctx.gradleUserHome,
"--included-build", "build-logic",
)
runCLI(t, binaryPath, ctx, saveArgs...)

Expand All @@ -164,10 +165,15 @@ func TestIntegrationGradleBuildCycle(t *testing.T) {
"--ref", commitSHA,
"--git-dir", ctx.projectDir,
"--gradle-user-home", ctx.gradleUserHome,
"--included-build", "build-logic",
)
runCLI(t, binaryPath, ctx, restoreArgs...)

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

Expand Down Expand Up @@ -525,6 +531,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
"--cache-key", ctx.cacheKey,
"--commit", commitSHA,
"--gradle-user-home", ctx.gradleUserHome,
"--included-build", "build-logic",
)
runCLI(t, binaryPath, ctx, saveArgs...)

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

Expand Down Expand Up @@ -574,6 +582,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
"--branch", "test-branch",
"--gradle-user-home", ctx.gradleUserHome,
"--project-dir", ctx.projectDir,
"--included-build", "build-logic",
)
runCLI(t, binaryPath, ctx, saveDeltaArgs...)

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

Expand Down Expand Up @@ -618,6 +628,7 @@ dependencies { implementation("com.google.guava:guava:33.4.0-jre") }
"--branch", "test-branch",
"--gradle-user-home", ctx.gradleUserHome,
"--project-dir", ctx.projectDir,
"--included-build", "build-logic",
)
runCLI(t, binaryPath, ctx, fullRestoreDelta...)

Expand Down Expand Up @@ -832,22 +843,42 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
}

for _, tt := range []struct {
name string
fixture string
buildFile string
change string // appended to build file to invalidate CC
name string
fixture string
mutate func(t *testing.T, projectDir string) // invalidate CC
}{
{
name: "groovy-dsl",
fixture: "groovy-project",
buildFile: "build.gradle",
change: "\n// force CC invalidation\n",
name: "groovy-dsl",
fixture: "groovy-project",
mutate: func(t *testing.T, projectDir string) {
appendToFile(t, filepath.Join(projectDir, "build.gradle"), "\n// force CC invalidation\n")
},
},
{
name: "kotlin-dsl",
fixture: "gradle-project",
buildFile: "build.gradle.kts",
change: "\n// force CC invalidation\n",
name: "kotlin-dsl",
fixture: "gradle-project",
mutate: func(t *testing.T, projectDir string) {
appendToFile(t, filepath.Join(projectDir, "build.gradle.kts"), "\n// force CC invalidation\n")
},
},
{
name: "included-build-plugin-change",
fixture: "gradle-project",
mutate: func(t *testing.T, projectDir string) {
must(t, os.WriteFile(
filepath.Join(projectDir, "build-logic", "src", "main", "java", "com", "example", "IncludedPlugin.java"),
[]byte(`package com.example;

import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class IncludedPlugin implements Plugin<Project> {
@Override public void apply(Project project) {
project.getLogger().lifecycle("IncludedPlugin applied (modified)");
}
}
`), 0o644))
},
},
} {
tt := tt
Expand All @@ -867,6 +898,7 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
"--cache-key", ctx.cacheKey,
"--commit", commitSHA,
"--gradle-user-home", ctx.gradleUserHome,
"--included-build", "build-logic",
)
runCLI(t, binaryPath, ctx, saveArgs...)

Expand All @@ -880,16 +912,12 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
"--ref", commitSHA,
"--git-dir", ctx.projectDir,
"--gradle-user-home", ctx.gradleUserHome,
"--included-build", "build-logic",
)
runCLI(t, binaryPath, ctx, restoreArgs...)

// Modify build file to invalidate configuration cache.
buildFilePath := filepath.Join(ctx.projectDir, tt.buildFile)
f, err := os.OpenFile(buildFilePath, os.O_APPEND|os.O_WRONLY, 0o644)
must(t, err)
_, err = f.WriteString(tt.change)
must(t, err)
must(t, f.Close())
// Mutate the project to invalidate configuration cache.
tt.mutate(t, ctx.projectDir)

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

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

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

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

Expand All @@ -945,6 +975,15 @@ func TestIntegrationDeltaConfigurationCache(t *testing.T) {
}
}

func appendToFile(t *testing.T, path, content string) {
t.Helper()
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0o644)
must(t, err)
_, err = f.WriteString(content)
must(t, err)
must(t, f.Close())
}

func extractLine(output, substr string) string {
for _, line := range strings.Split(output, "\n") {
if strings.Contains(strings.ToLower(line), strings.ToLower(substr)) {
Expand Down
62 changes: 54 additions & 8 deletions cmd/gradle-cache/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ package main

import (
"context"
"fmt"
"log/slog"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"

"github.com/alecthomas/errors"
"github.com/alecthomas/kong"
Expand Down Expand Up @@ -61,6 +64,25 @@ func (f *backendFlags) validate() error {
return nil
}

// validateIncludedBuilds checks that each --included-build value refers to an
// existing directory (or, for glob patterns like "build-logic/*", that the
// parent directory exists). baseDir is the directory paths are resolved
// against (typically the project directory).
func validateIncludedBuilds(baseDir string, entries []string) error {
for _, entry := range entries {
dir := strings.TrimSuffix(entry, "/*")
path := filepath.Join(baseDir, dir)
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("--included-build %q: %w", entry, err)
}
if !info.IsDir() {
return fmt.Errorf("--included-build %q: not a directory", entry)
}
}
return nil
}

// ── Restore ─────────────────────────────────────────────────────────────────

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

func (c *RestoreCmd) AfterApply() error { return c.validate() }
func (c *RestoreCmd) AfterApply() error {
if err := c.validate(); err != nil {
return err
}
return validateIncludedBuilds(c.ProjectDir, c.IncludedBuilds)
}

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

func (c *RestoreDeltaCmd) AfterApply() error { return c.validate() }
func (c *RestoreDeltaCmd) AfterApply() error {
if err := c.validate(); err != nil {
return err
}
return validateIncludedBuilds(c.ProjectDir, c.IncludedBuilds)
}

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

func (c *SaveCmd) AfterApply() error { return c.validate() }
func (c *SaveCmd) AfterApply() error {
if err := c.validate(); err != nil {
return err
}
return validateIncludedBuilds(c.ProjectDir, c.IncludedBuilds)
}

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

func (c *SaveDeltaCmd) AfterApply() error { return c.validate() }
func (c *SaveDeltaCmd) AfterApply() error {
if err := c.validate(); err != nil {
return err
}
return validateIncludedBuilds(c.ProjectDir, c.IncludedBuilds)
}

func (c *SaveDeltaCmd) Run(ctx context.Context, metrics gradlecache.MetricsClient) error {
slog.Debug(gradleUserHomeEnv, "path", c.GradleUserHome)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
plugins {
`java-gradle-plugin`
}

gradlePlugin {
plugins {
create("included") {
id = "com.example.included"
implementationClass = "com.example.IncludedPlugin"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = "build-logic"
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example;

import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class IncludedPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// no-op plugin — exercises the included-build path
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
kotlin("jvm") version "2.3.20"
id("com.example.included")
}

repositories {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
pluginManagement {
includeBuild("build-logic")
}

rootProject.name = "cache-test"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
plugins {
id 'java-gradle-plugin'
}

gradlePlugin {
plugins {
included {
id = 'com.example.included'
implementationClass = 'com.example.IncludedPlugin'
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = 'build-logic'
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example;

import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class IncludedPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// no-op plugin — exercises the included-build path
}
}
1 change: 1 addition & 0 deletions cmd/gradle-cache/testdata/groovy-project/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id 'java'
id 'com.example.included'
}

repositories {
Expand Down
4 changes: 4 additions & 0 deletions cmd/gradle-cache/testdata/groovy-project/settings.gradle
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
pluginManagement {
includeBuild('build-logic')
}

rootProject.name = 'groovy-cache-test'
Loading
Loading