diff --git a/README.md b/README.md index 5d35106..ce11e4f 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,13 @@ The buildpack optionally accepts the following bindings: | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `gradle.properties` | If present, the contents of the file are copied to `$GRADLE_USER_HOME/gradle.properties` which is [picked up by gradle and merged](https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties) when it runs. | +### Type: `gradle-wrapper` + +| Secret | Description | +|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `gradle-wrapper.properties` | If present, the values of the properties file override the default ones found at /gradle/wrapper/gradle-wrapper.properties which is [picked up by the gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html#customizing_wrapper). | + + ### Type: `dependency-mapping` | Key | Value | Description | diff --git a/gradle/build.go b/gradle/build.go index c98a972..888f406 100644 --- a/gradle/build.go +++ b/gradle/build.go @@ -133,6 +133,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { if binding, ok, err := bindings.ResolveOne(context.Platform.Bindings, bindings.OfType("gradle")); err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to resolve binding\n%w", err) } else if ok { + b.Logger.Debug("binding of type gradle successfully detected, configuring layer") gradlePropertiesPath, ok := binding.SecretFilePath("gradle.properties") if ok { gradlePropertiesFile, err := os.Open(gradlePropertiesPath) @@ -149,6 +150,37 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { result.Layers = append(result.Layers, PropertiesFile{ binding, gradleHome, + "gradle.properties", + "gradle-properties", + b.Logger, + }) + } + } + + gradleWrapperHome := filepath.Join("gradle", "wrapper") + if binding, ok, err := bindings.ResolveOne(context.Platform.Bindings, bindings.OfType("gradle-wrapper")); err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to resolve binding\n%w", err) + } else if ok { + b.Logger.Debug("binding of type gradle-wrapper successfully detected, configuring layer") + gradleWrapperPropertiesPath, ok := binding.SecretFilePath("gradle-wrapper.properties") + if ok { + gradleWrapperPropertiesFile, err := os.Open(gradleWrapperPropertiesPath) + if err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to open gradle-wrapper.properties\n%w", err) + } + + hasher := sha256.New() + if _, err := io.Copy(hasher, gradleWrapperPropertiesFile); err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to hash gradle-wrapper.properties\n%w", err) + } + md["gradle-wrapper-properties-sha256"] = hex.EncodeToString(hasher.Sum(nil)) + + result.Layers = append(result.Layers, PropertiesFile{ + binding, + gradleWrapperHome, + "gradle-wrapper.properties", + "gradle-wrapper-properties", + b.Logger, }) } } diff --git a/gradle/build_test.go b/gradle/build_test.go index e472038..9c93192 100644 --- a/gradle/build_test.go +++ b/gradle/build_test.go @@ -18,7 +18,6 @@ package gradle_test import ( "errors" - "io/ioutil" "os" "path/filepath" "testing" @@ -48,13 +47,13 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { it.Before(func() { var err error - ctx.Application.Path, err = ioutil.TempDir("", "build-application") + ctx.Application.Path, err = os.MkdirTemp("", "build-application") Expect(err).NotTo(HaveOccurred()) - ctx.Layers.Path, err = ioutil.TempDir("", "build-layers") + ctx.Layers.Path, err = os.MkdirTemp("", "build-layers") Expect(err).NotTo(HaveOccurred()) - homeDir, err = ioutil.TempDir("", "home-dir") + homeDir, err = os.MkdirTemp("", "home-dir") Expect(err).NotTo(HaveOccurred()) gradlewFilepath = filepath.Join(ctx.Application.Path, "gradlew") @@ -72,7 +71,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { }) it("does not contribute distribution if wrapper exists", func() { - Expect(ioutil.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) + Expect(os.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) ctx.StackID = "test-stack-id" result, err := gradleBuild.Build(ctx) @@ -85,7 +84,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { }) it("makes sure that gradlew is executable", func() { - Expect(ioutil.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) + Expect(os.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) ctx.StackID = "test-stack-id" _, err := gradleBuild.Build(ctx) @@ -182,7 +181,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { }) it("sets the settings path", func() { - Expect(ioutil.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) + Expect(os.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) result, err := gradleBuild.Build(ctx) Expect(err).NotTo(HaveOccurred()) @@ -203,7 +202,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { }) it("sets the settings path", func() { - Expect(ioutil.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) + Expect(os.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) result, err := gradleBuild.Build(ctx) Expect(err).NotTo(HaveOccurred()) @@ -223,7 +222,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(os.Unsetenv(("BP_GRADLE_BUILD_ARGUMENTS"))).To(Succeed()) }) it("sets some build arguments", func() { - Expect(ioutil.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) + Expect(os.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) result, err := gradleBuild.Build(ctx) Expect(err).NotTo(HaveOccurred()) @@ -245,7 +244,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(os.Unsetenv(("BP_GRADLE_ADDITIONAL_BUILD_ARGUMENTS"))).To(Succeed()) }) it("sets some build and additional build arguments", func() { - Expect(ioutil.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) + Expect(os.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) result, err := gradleBuild.Build(ctx) Expect(err).NotTo(HaveOccurred()) @@ -262,9 +261,9 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { it.Before(func() { var err error ctx.StackID = "test-stack-id" - ctx.Platform.Path, err = ioutil.TempDir("", "gradle-test-platform") + ctx.Platform.Path, err = os.MkdirTemp("", "gradle-test-platform") Expect(err).NotTo(HaveOccurred()) - Expect(ioutil.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) + Expect(os.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) bindingPath = filepath.Join(ctx.Platform.Path, "bindings", "some-gradle") ctx.Platform.Bindings = libcnb.Bindings{ { @@ -277,7 +276,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { gradlePropertiesPath, ok := ctx.Platform.Bindings[0].SecretFilePath("gradle.properties") Expect(os.MkdirAll(filepath.Dir(gradlePropertiesPath), 0777)).To(Succeed()) Expect(ok).To(BeTrue()) - Expect(ioutil.WriteFile( + Expect(os.WriteFile( gradlePropertiesPath, []byte("gradle-properties-content"), 0644, @@ -309,6 +308,61 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(mdMap["gradle-properties-sha256"]).To(Equal(expected)) }) }) + + context("gradle wrapper properties binding exists", func() { + var bindingPath string + + it.Before(func() { + var err error + ctx.StackID = "test-stack-id" + ctx.Platform.Path, err = os.MkdirTemp("", "gradle-test-platform") + Expect(err).NotTo(HaveOccurred()) + Expect(os.WriteFile(gradlewFilepath, []byte{}, 0644)).To(Succeed()) + bindingPath = filepath.Join(ctx.Platform.Path, "bindings", "some-gradle") + ctx.Platform.Bindings = libcnb.Bindings{ + { + Name: "some-gradle", + Type: "gradle-wrapper", + Secret: map[string]string{"gradle-wrapper.properties": "gradle-wrapper-properties-content"}, + Path: bindingPath, + }, + } + gradlePropertiesPath, ok := ctx.Platform.Bindings[0].SecretFilePath("gradle-wrapper.properties") + Expect(os.MkdirAll(filepath.Dir(gradlePropertiesPath), 0777)).To(Succeed()) + Expect(ok).To(BeTrue()) + Expect(os.WriteFile( + gradlePropertiesPath, + []byte("gradle-wrapper-properties-content"), + 0644, + )).To(Succeed()) + }) + + it.After(func() { + Expect(os.RemoveAll(ctx.Platform.Path)).To(Succeed()) + }) + + it("contributes bound gradle-wrapper.properties", func() { + result, err := gradleBuild.Build(ctx) + Expect(err).NotTo(HaveOccurred()) + + Expect(result.Layers).To(HaveLen(3)) + Expect(result.Layers[1].Name()).To(Equal("gradle-wrapper-properties")) + Expect(result.Layers[1]) + }) + + it("adds the hash of gradle-wrapper.properties to the layer metadata", func() { + result, err := gradleBuild.Build(ctx) + Expect(err).NotTo(HaveOccurred()) + + md := result.Layers[2].(libbs.Application).LayerContributor.ExpectedMetadata + mdMap, ok := md.(map[string]interface{}) + Expect(ok).To(BeTrue()) + // expected: sha256 of the string "gradle-wrapper-properties-content" + expected := "8d98502ceb9504c887b12cfba9427c5338d133a2f10613cb0137695ca09c7ddc" + Expect(mdMap["gradle-wrapper-properties-sha256"]).To(Equal(expected)) + }) + }) + } type FakeApplicationFactory struct{} diff --git a/gradle/gradle_properties.go b/gradle/gradle_properties.go index cbb176b..a4a462d 100644 --- a/gradle/gradle_properties.go +++ b/gradle/gradle_properties.go @@ -2,6 +2,8 @@ package gradle import ( "fmt" + "github.com/magiconair/properties" + "github.com/paketo-buildpacks/libpak/bard" "os" "path/filepath" @@ -9,34 +11,56 @@ import ( ) type PropertiesFile struct { - Binding libcnb.Binding - GradleHome string + Binding libcnb.Binding + GradlePropertiesHome string + GradlePropertiesFileName string + GradlePropertiesName string + Logger bard.Logger } func (p PropertiesFile) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { - path, ok := p.Binding.SecretFilePath("gradle.properties") + path, ok := p.Binding.SecretFilePath(p.GradlePropertiesFileName) if !ok { return libcnb.Layer{}, nil } - gradlePropertiesPath := filepath.Join(p.GradleHome, "gradle.properties") - if err := os.Symlink(path, gradlePropertiesPath); os.IsExist(err) { - err = os.Remove(gradlePropertiesPath) + originalPropertiesFilePath := filepath.Join(p.GradlePropertiesHome, p.GradlePropertiesFileName) + if p.GradlePropertiesName == "gradle-properties" { + p.Logger.Debug("symlinking gradle-properties bound file") + gradlePropertiesPath := originalPropertiesFilePath + if err := os.Symlink(path, gradlePropertiesPath); os.IsExist(err) { + err = os.Remove(gradlePropertiesPath) + if err != nil { + return libcnb.Layer{}, fmt.Errorf("unable to remove old symlink for %s\n%w", p.GradlePropertiesFileName, err) + } + + err = os.Symlink(path, gradlePropertiesPath) + if err != nil { + return libcnb.Layer{}, fmt.Errorf("unable to create symlink for %s on retry\n%w", p.GradlePropertiesFileName, err) + } + } else if err != nil { + return libcnb.Layer{}, fmt.Errorf("unable to symlink bound %s\n%w", p.GradlePropertiesFileName, err) + } + } else if p.GradlePropertiesName == "gradle-wrapper-properties" { + file, err := os.ReadFile(path) if err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to remove old symlink for gradle.properties\n%w", err) + return libcnb.Layer{}, fmt.Errorf("unable to read bound gradle-wrapper.properties file at %s\n%w", path, err) } - - err = os.Symlink(path, gradlePropertiesPath) + p.Logger.Debugf("applying these bound gradle-wrapper-properties to default one: \n%s\n", string(file)) + mergedProperties := properties.MustLoadFiles([]string{originalPropertiesFilePath, path}, properties.UTF8, true) + propertiesFile, err := os.Create(originalPropertiesFilePath) + if err != nil { + return libcnb.Layer{}, fmt.Errorf("unable to create/update original gradle-wrapper.properties file at %s\n%w", originalPropertiesFilePath, err) + } + _, err = mergedProperties.Write(propertiesFile, properties.UTF8) if err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to create symlink for gradle.properties on retry\n%w", err) + return libcnb.Layer{}, fmt.Errorf("unable to merge gradle-wrapper.properties files.\n%w", err) } - } else if err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to symlink bound gradle.properties\n%w", err) } return layer, nil } func (p PropertiesFile) Name() string { - return "gradle-properties" + return p.GradlePropertiesName } diff --git a/gradle/gradle_properties_test.go b/gradle/gradle_properties_test.go index e3ceae4..fc9a10a 100644 --- a/gradle/gradle_properties_test.go +++ b/gradle/gradle_properties_test.go @@ -1,7 +1,8 @@ package gradle_test import ( - "io/ioutil" + "github.com/magiconair/properties" + "github.com/paketo-buildpacks/libpak/sherpa" "os" "path/filepath" "testing" @@ -17,32 +18,37 @@ func testGradleProperties(t *testing.T, context spec.G, it spec.S) { var ( Expect = NewWithT(t).Expect - ctx libcnb.BuildContext - gradleProps gradle.PropertiesFile - gradleLayer libcnb.Layer - gradleHome string - gradleTargetPropsPath string - bindingPath string - homeDir string + ctx libcnb.BuildContext + gradleProps gradle.PropertiesFile + gradleLayer libcnb.Layer + gradleHome string + gradleWrapperHome string + gradleWrapperTargetPropsPath string + gradleTargetPropsPath string + bindingPath string + homeDir string ) it.Before(func() { var err error - ctx.Platform.Path, err = ioutil.TempDir("", "gradle-test-platform") + ctx.Platform.Path, err = os.MkdirTemp("", "gradle-test-platform") Expect(err).NotTo(HaveOccurred()) - ctx.Application.Path, err = ioutil.TempDir("", "build-application") + ctx.Application.Path, err = os.MkdirTemp("", "build-application") Expect(err).NotTo(HaveOccurred()) - ctx.Layers.Path, err = ioutil.TempDir("", "build-layers") + ctx.Layers.Path, err = os.MkdirTemp("", "build-layers") Expect(err).NotTo(HaveOccurred()) - homeDir, err = ioutil.TempDir("", "home-dir") + homeDir, err = os.MkdirTemp("", "home-dir") Expect(err).NotTo(HaveOccurred()) gradleHome = filepath.Join(homeDir, ".gradle") gradleTargetPropsPath = filepath.Join(gradleHome, "gradle.properties") + + gradleWrapperHome = filepath.Join(ctx.Application.Path, "gradle", "wrapper") + gradleWrapperTargetPropsPath = filepath.Join(gradleWrapperHome, "gradle-wrapper.properties") }) it.After(func() { @@ -63,7 +69,7 @@ func testGradleProperties(t *testing.T, context spec.G, it spec.S) { }) }) - context("a binding is present", func() { + context("a gradle properties binding is present", func() { it.Before(func() { var err error @@ -79,7 +85,7 @@ func testGradleProperties(t *testing.T, context spec.G, it spec.S) { gradleSrcPropsPath, ok := ctx.Platform.Bindings[0].SecretFilePath("gradle.properties") Expect(os.MkdirAll(filepath.Dir(gradleSrcPropsPath), 0777)).To(Succeed()) Expect(ok).To(BeTrue()) - Expect(ioutil.WriteFile( + Expect(os.WriteFile( gradleSrcPropsPath, []byte("gradle-properties-content"), 0644, @@ -92,8 +98,10 @@ func testGradleProperties(t *testing.T, context spec.G, it spec.S) { Expect(err).NotTo(HaveOccurred()) gradleProps = gradle.PropertiesFile{ - Binding: ctx.Platform.Bindings[0], - GradleHome: gradleHome, + Binding: ctx.Platform.Bindings[0], + GradlePropertiesHome: gradleHome, + GradlePropertiesFileName: "gradle.properties", + GradlePropertiesName: "gradle-properties", } }) @@ -110,7 +118,7 @@ func testGradleProperties(t *testing.T, context spec.G, it spec.S) { Expect(err).NotTo(HaveOccurred()) Expect(target).To(Equal(filepath.Join(bindingPath, "gradle.properties"))) - data, err := ioutil.ReadFile(gradleTargetPropsPath) + data, err := os.ReadFile(gradleTargetPropsPath) Expect(err).ToNot(HaveOccurred()) Expect(string(data)).To(Equal("gradle-properties-content")) }) @@ -134,10 +142,76 @@ func testGradleProperties(t *testing.T, context spec.G, it spec.S) { // symlink should point to our binding, not /dev/null Expect(target).To(Equal(filepath.Join(bindingPath, "gradle.properties"))) - data, err := ioutil.ReadFile(gradleTargetPropsPath) + data, err := os.ReadFile(gradleTargetPropsPath) Expect(err).ToNot(HaveOccurred()) Expect(string(data)).To(Equal("gradle-properties-content")) }) }) + + context("a gradle wrapper properties binding is present and contributes its content", func() { + it.Before(func() { + var err error + + bindingPath = filepath.Join(ctx.Platform.Path, "bindings", "some-gradle") + ctx.Platform.Bindings = libcnb.Bindings{ + { + Name: "some-gradle", + Type: "gradle-wrapper", + Secret: map[string]string{"gradle-wrapper.properties": `distributionUrl=https://g.o/gradle-7.5-bin.zip + networkTimeout=43`}, + Path: bindingPath, + }, + } + Expect(os.MkdirAll(gradleWrapperHome, 0755)).ToNot(HaveOccurred()) + gradleSrcPropsPath, ok := ctx.Platform.Bindings[0].SecretFilePath("gradle-wrapper.properties") + Expect(os.MkdirAll(filepath.Dir(gradleSrcPropsPath), 0777)).To(Succeed()) + Expect(ok).To(BeTrue()) + + Expect(os.WriteFile( + gradleSrcPropsPath, + []byte(`distributionUrl=https://g.o/gradle-7.5-bin.zip + networkTimeout=43`), + 0644, + )).To(Succeed()) + + originalGradleWrapperProperties, err := os.Open(filepath.Join("testdata", "gradle-wrapper.properties")) + Expect(err).NotTo(HaveOccurred()) + + err = sherpa.CopyFile(originalGradleWrapperProperties, gradleWrapperTargetPropsPath) + Expect(err).NotTo(HaveOccurred()) + + gradleLayer, err = ctx.Layers.Layer("gradle-wrapper-properties") + Expect(err).NotTo(HaveOccurred()) + + gradleProps = gradle.PropertiesFile{ + Binding: ctx.Platform.Bindings[0], + GradlePropertiesHome: gradleWrapperHome, + GradlePropertiesFileName: "gradle-wrapper.properties", + GradlePropertiesName: "gradle-wrapper-properties", + } + }) + + it("merges gradle wrapper properties files", func() { + layer, err := gradleProps.Contribute(gradleLayer) + Expect(err).NotTo(HaveOccurred()) + Expect(layer).To(Equal(gradleLayer)) + + patchedProperties := properties.MustLoadFile(filepath.Join("testdata", "gradle-wrapper.properties"), properties.UTF8) + previousValue, ok, err := patchedProperties.Set("networkTimeout", "43") + Expect(err).NotTo(HaveOccurred()) + Expect(previousValue).To(Equal("10000")) + Expect(ok).To(Equal(true)) + previousValue, ok, err = patchedProperties.Set("distributionUrl", "https://g.o/gradle-7.5-bin.zip") + Expect(err).NotTo(HaveOccurred()) + Expect(previousValue).To(Equal("https://services.gradle.org/distributions/gradle-7.6-bin.zip")) + Expect(ok).To(Equal(true)) + + finalProperties := properties.MustLoadFile(gradleWrapperTargetPropsPath, properties.UTF8) + + Expect(finalProperties).To(Equal(patchedProperties)) + }) + + }) + } diff --git a/gradle/testdata/gradle-wrapper.properties b/gradle/testdata/gradle-wrapper.properties new file mode 100644 index 0000000..f398c33 --- /dev/null +++ b/gradle/testdata/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists