From 0e9aca27937dd286d10cfacfdb4a5aadb5165965 Mon Sep 17 00:00:00 2001 From: Anthony Dahanne Date: Fri, 8 Nov 2024 10:28:19 -0500 Subject: [PATCH 1/2] Close #434 * Provide better error messages in case the user has requested, via BP_JVM_VERSION or sdkmanrc a Java version that is not available in the buildpack * Try and find a more recent JRE/JDK version in case the Manifest found version is not available (best guess) * In case there's not even a JDK matching the non-available desired JRE version, return the error instead of moving on with non-existing JDK --- build.go | 25 ++++++---- build_test.go | 119 ++++++++++++++++++++++++++++++++++++++++++++ jvm_version.go | 24 ++++++++- jvm_version_test.go | 24 ++++----- 4 files changed, 170 insertions(+), 22 deletions(-) diff --git a/build.go b/build.go index 6cdc91c..a171265 100644 --- a/build.go +++ b/build.go @@ -77,7 +77,7 @@ type NativeImage struct { } func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { - var jdkRequired, jreRequired, jreMissing, jreSkipped, jLinkEnabled, nativeImage bool + var jdkRequired, jreRequired, jreMissing, jdkMissing, jreSkipped, jLinkEnabled, nativeImage bool pr := libpak.PlanEntryResolver{Plan: context.Plan} @@ -106,15 +106,15 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { return libcnb.BuildResult{}, fmt.Errorf("unable to create configuration resolver\n%w", err) } - jvmVersion := NewJVMVersion(b.Logger) - v, err := jvmVersion.GetJVMVersion(context.Application.Path, cr) + dr, err := libpak.NewDependencyResolver(context) if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to determine jvm version\n%w", err) + return libcnb.BuildResult{}, fmt.Errorf("unable to create dependency resolver\n%w", err) } - dr, err := libpak.NewDependencyResolver(context) + jvmVersion := NewJVMVersion(b.Logger) + v, err := jvmVersion.GetJVMVersion(context.Application.Path, cr, dr) if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to create dependency resolver\n%w", err) + return libcnb.BuildResult{}, fmt.Errorf("unable to determine jvm version\n%w", err) } b.DependencyCache, err = libpak.NewDependencyCache(context) @@ -123,9 +123,13 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { } b.DependencyCache.Logger = b.Logger + jdkMissing = false depJDK, err := dr.Resolve("jdk", v) if (jdkRequired && !nativeImage) && err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) + return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency for JDK %s - make sure the buildpack includes the Java version you have requested\n%w", v, err) + } + if libpak.IsNoValidDependencies(err) { + jdkMissing = true } jreMissing = false @@ -145,7 +149,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { if nativeImage { depNative, err := dr.Resolve("native-image-svm", v) if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) + return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency for native-image-svm %s - make sure the buildpack includes the Java Native version you have requested\n%w", v, err) } if b.Native.BundledWithJDK { if err = b.contributeJDK(depNative); err != nil { @@ -176,6 +180,9 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { // use JDK as JRE if jreRequired && (jreSkipped || jreMissing) { + if jdkMissing { + return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency for JRE %s even as a JDK - make sure the buildpack includes the Java version you have requested\n%w", v, err) + } b.warnIfJreNotUsed(jreMissing, jreSkipped) if err = b.contributeJDKAsJRE(depJDK, jrePlanEntry, context); err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to contribute JDK as JRE\n%w", err) @@ -223,7 +230,7 @@ func (b *Build) contributeJDKAsJRE(jdkDep libpak.BuildpackDependency, jrePlanEnt dt := JDKType if err := b.contributeJRE(jdkDep, context.Application.Path, dt, jrePlanEntry.Metadata); err != nil { - return fmt.Errorf("unable to contribute JRE\n%w", err) + return fmt.Errorf("unable to contribute JDK\n%w", err) } return nil } diff --git a/build_test.go b/build_test.go index b5ce4f3..b138ce2 100644 --- a/build_test.go +++ b/build_test.go @@ -19,6 +19,7 @@ package libjvm_test import ( "io" "os" + "path/filepath" "testing" "github.com/paketo-buildpacks/libpak/bard" @@ -56,6 +57,10 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { t.Setenv("BP_ARCH", "amd64") }) + it.After(func() { + Expect(os.Unsetenv("BP_JVM_VERSION")).To(Succeed()) + }) + it("contributes JDK", func() { ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: "jdk"}) ctx.Buildpack.Metadata = map[string]interface{}{ @@ -110,6 +115,120 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(result.BOM.Entries[1].Launch).To(BeTrue()) }) + it("contributes available next JRE version when Manifest refers to not available version", func() { + ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: "jre", Metadata: LaunchContribution}) + ctx.Buildpack.API = "0.6" + + appPath, err := os.MkdirTemp("", "application") + Expect(prepareAppWithEntry(appPath, "Build-Jdk: 22")).ToNot(HaveOccurred()) + + ctx.Application.Path = appPath + ctx.Buildpack.Metadata = map[string]interface{}{ + "dependencies": []map[string]interface{}{ + { + "id": "jre", + "version": "8.0.432", + "stacks": []interface{}{"test-stack-id"}, + }, + { + "id": "jre", + "version": "23.0.1", + "stacks": []interface{}{"test-stack-id"}, + }, + { + "id": "jre", + "version": "43.43.43", + "stacks": []interface{}{"test-stack-id"}, + }, + }, + } + ctx.StackID = "test-stack-id" + + result, err := libjvm.NewBuild(bard.NewLogger(io.Discard)).Build(ctx) + Expect(err).NotTo(HaveOccurred()) + + Expect(result.Layers).To(HaveLen(3)) + Expect(result.Layers[0].Name()).To(Equal("jre")) + Expect(result.Layers[1].Name()).To(Equal("helper")) + Expect(result.Layers[2].Name()).To(Equal("java-security-properties")) + + Expect(result.BOM.Entries).To(HaveLen(2)) + Expect(result.BOM.Entries[0].Name).To(Equal("jre")) + Expect(result.BOM.Entries[0].Metadata["version"]).To(Equal("23.0.1")) + Expect(result.BOM.Entries[0].Launch).To(BeTrue()) + Expect(result.BOM.Entries[1].Name).To(Equal("helper")) + Expect(result.BOM.Entries[1].Launch).To(BeTrue()) + }) + + it("provides meaningful error message if user requested via sdkmanrc a non available JRE", func() { + ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: "jre", Metadata: LaunchContribution}) + ctx.Buildpack.API = "0.6" + + appPath, err := os.MkdirTemp("", "application") + sdkmanrcFile := filepath.Join(appPath, ".sdkmanrc") + Expect(os.WriteFile(sdkmanrcFile, []byte(`java=20.0.2-tem`), 0644)).To(Succeed()) + + ctx.Application.Path = appPath + ctx.Buildpack.Metadata = map[string]interface{}{ + "dependencies": []map[string]interface{}{ + { + "id": "jre", + "version": "8.0.432", + "stacks": []interface{}{"test-stack-id"}, + }, + { + "id": "jre", + "version": "23.0.1", + "stacks": []interface{}{"test-stack-id"}, + }, + { + "id": "jre", + "version": "43.43.43", + "stacks": []interface{}{"test-stack-id"}, + }, + }, + } + ctx.StackID = "test-stack-id" + + _, err = libjvm.NewBuild(bard.NewLogger(io.Discard)).Build(ctx) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unable to find dependency for JRE 20 even as a JDK - make sure the buildpack includes the Java version you have requested")) + Expect(err.Error()).To(ContainSubstring("no valid dependencies for jre, 20, and test-stack-id in [(jre, 8.0.432, [test-stack-id]) (jre, 23.0.1, [test-stack-id]) (jre, 43.43.43, [test-stack-id])]")) + }) + + it("provides meaningful error message if user requested via env.var a non available JRE", func() { + ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: "jre", Metadata: LaunchContribution}) + ctx.Buildpack.API = "0.6" + + Expect(os.Setenv("BP_JVM_VERSION", "24")).To(Succeed()) + + ctx.Buildpack.Metadata = map[string]interface{}{ + "dependencies": []map[string]interface{}{ + { + "id": "jre", + "version": "8.0.432", + "stacks": []interface{}{"test-stack-id"}, + }, + { + "id": "jre", + "version": "23.0.1", + "stacks": []interface{}{"test-stack-id"}, + }, + { + "id": "jre", + "version": "43.43.43", + "stacks": []interface{}{"test-stack-id"}, + }, + }, + } + ctx.StackID = "test-stack-id" + + _, err := libjvm.NewBuild(bard.NewLogger(io.Discard)).Build(ctx) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unable to find dependency for JRE 24 even as a JDK - make sure the buildpack includes the Java version you have requested")) + Expect(err.Error()).To(ContainSubstring("no valid dependencies for jre, 24, and test-stack-id in [(jre, 8.0.432, [test-stack-id]) (jre, 23.0.1, [test-stack-id]) (jre, 43.43.43, [test-stack-id])]")) + }) + it("contributes security-providers-classpath-8 before Java 9", func() { ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: "jre", Metadata: LaunchContribution}) ctx.Buildpack.Metadata = map[string]interface{}{ diff --git a/jvm_version.go b/jvm_version.go index 0050fb4..ae1ea47 100644 --- a/jvm_version.go +++ b/jvm_version.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "github.com/heroku/color" @@ -20,7 +21,7 @@ func NewJVMVersion(logger bard.Logger) JVMVersion { return JVMVersion{Logger: logger} } -func (j JVMVersion) GetJVMVersion(appPath string, cr libpak.ConfigurationResolver) (string, error) { +func (j JVMVersion) GetJVMVersion(appPath string, cr libpak.ConfigurationResolver, dr libpak.DependencyResolver) (string, error) { version, explicit := cr.Resolve("BP_JVM_VERSION") if explicit { f := color.New(color.Faint) @@ -47,6 +48,7 @@ func (j JVMVersion) GetJVMVersion(appPath string, cr libpak.ConfigurationResolve if len(mavenJavaVersion) > 0 { mavenJavaMajorVersion := extractMajorVersion(mavenJavaVersion) + retrieveNextAvailableJavaVersionIfMavenVersionNotAvailable(dr, &mavenJavaMajorVersion) f := color.New(color.Faint) j.Logger.Body(f.Sprintf("Using Java version %s extracted from MANIFEST.MF", mavenJavaMajorVersion)) return mavenJavaMajorVersion, nil @@ -57,6 +59,26 @@ func (j JVMVersion) GetJVMVersion(appPath string, cr libpak.ConfigurationResolve return version, nil } +func retrieveNextAvailableJavaVersionIfMavenVersionNotAvailable(dr libpak.DependencyResolver, mavenJavaMajorVersion *string) { + _, jdkErr := dr.Resolve("jdk", *mavenJavaMajorVersion) + _, jreErr := dr.Resolve("jre", *mavenJavaMajorVersion) + if libpak.IsNoValidDependencies(jdkErr) && libpak.IsNoValidDependencies(jreErr) { + // the buildpack does not provide the wanted JDK or JRE version - let's check if we can choose a more recent version + mavenJavaMajorVersionAsInt, _ := strconv.ParseInt(*mavenJavaMajorVersion, 10, 64) + versionToEvaluate := mavenJavaMajorVersionAsInt + 1 + for versionToEvaluate <= mavenJavaMajorVersionAsInt+5 { + _, jdkErr := dr.Resolve("jdk", strconv.FormatInt(versionToEvaluate, 10)) + _, jreErr := dr.Resolve("jre", strconv.FormatInt(versionToEvaluate, 10)) + if libpak.IsNoValidDependencies(jdkErr) && libpak.IsNoValidDependencies(jreErr) { + versionToEvaluate = versionToEvaluate + 1 + } else { + *mavenJavaMajorVersion = strconv.FormatInt(versionToEvaluate, 10) + break + } + } + } +} + func readJavaVersionFromSDKMANRCFile(appPath string) (string, error) { components, err := ReadSDKMANRC(filepath.Join(appPath, ".sdkmanrc")) if err != nil && errors.Is(err, os.ErrNotExist) { diff --git a/jvm_version_test.go b/jvm_version_test.go index 022fb52..e651737 100644 --- a/jvm_version_test.go +++ b/jvm_version_test.go @@ -17,7 +17,7 @@ package libjvm_test import ( - "io/ioutil" + "io" "os" "path/filepath" "testing" @@ -43,7 +43,7 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) { it.Before(func() { var err error - appPath, err = ioutil.TempDir("", "application") + appPath, err = os.MkdirTemp("", "application") Expect(err).NotTo(HaveOccurred()) buildpack = libcnb.Buildpack{ @@ -56,7 +56,7 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) { }, }, } - logger = bard.NewLogger(ioutil.Discard) + logger = bard.NewLogger(io.Discard) }) it.After(func() { @@ -68,7 +68,7 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) { cr, err := libpak.NewConfigurationResolver(buildpack, &logger) Expect(err).ToNot(HaveOccurred()) - version, err := jvmVersion.GetJVMVersion(appPath, cr) + version, err := jvmVersion.GetJVMVersion(appPath, cr, libpak.DependencyResolver{}) Expect(err).ToNot(HaveOccurred()) Expect(version).To(Equal("1.1.1")) }) @@ -87,7 +87,7 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) { cr, err := libpak.NewConfigurationResolver(buildpack, &logger) Expect(err).ToNot(HaveOccurred()) - version, err := jvmVersion.GetJVMVersion(appPath, cr) + version, err := jvmVersion.GetJVMVersion(appPath, cr, libpak.DependencyResolver{}) Expect(err).ToNot(HaveOccurred()) Expect(version).To(Equal("17")) }) @@ -103,7 +103,7 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) { cr, err := libpak.NewConfigurationResolver(buildpack, &logger) Expect(err).ToNot(HaveOccurred()) - version, err := jvmVersion.GetJVMVersion(appPath, cr) + version, err := jvmVersion.GetJVMVersion(appPath, cr, libpak.DependencyResolver{}) Expect(err).ToNot(HaveOccurred()) Expect(version).To(Equal("8")) }) @@ -124,7 +124,7 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) { cr, err := libpak.NewConfigurationResolver(buildpack, &logger) Expect(err).ToNot(HaveOccurred()) - version, err := jvmVersion.GetJVMVersion(appPath, cr) + version, err := jvmVersion.GetJVMVersion(appPath, cr, libpak.DependencyResolver{}) Expect(err).ToNot(HaveOccurred()) Expect(version).To(Equal("17")) }) @@ -135,7 +135,7 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) { it.Before(func() { sdkmanrcFile = filepath.Join(appPath, ".sdkmanrc") - Expect(ioutil.WriteFile(sdkmanrcFile, []byte(`java=17.0.2-tem`), 0644)).To(Succeed()) + Expect(os.WriteFile(sdkmanrcFile, []byte(`java=17.0.2-tem`), 0644)).To(Succeed()) }) it("from .sdkmanrc file", func() { @@ -143,7 +143,7 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) { cr, err := libpak.NewConfigurationResolver(buildpack, &logger) Expect(err).ToNot(HaveOccurred()) - version, err := jvmVersion.GetJVMVersion(appPath, cr) + version, err := jvmVersion.GetJVMVersion(appPath, cr, libpak.DependencyResolver{}) Expect(err).ToNot(HaveOccurred()) Expect(version).To(Equal("17")) }) @@ -154,7 +154,7 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) { it.Before(func() { sdkmanrcFile = filepath.Join(appPath, ".sdkmanrc") - Expect(ioutil.WriteFile(sdkmanrcFile, []byte(`java=17.0.2-tem + Expect(os.WriteFile(sdkmanrcFile, []byte(`java=17.0.2-tem java=11.0.2-tem`), 0644)).To(Succeed()) }) @@ -163,7 +163,7 @@ java=11.0.2-tem`), 0644)).To(Succeed()) cr, err := libpak.NewConfigurationResolver(buildpack, &logger) Expect(err).ToNot(HaveOccurred()) - version, err := jvmVersion.GetJVMVersion(appPath, cr) + version, err := jvmVersion.GetJVMVersion(appPath, cr, libpak.DependencyResolver{}) Expect(err).ToNot(HaveOccurred()) Expect(version).To(Equal("17")) }) @@ -177,7 +177,7 @@ func prepareAppWithEntry(appPath, entry string) error { } manifest := filepath.Join(appPath, "META-INF", "MANIFEST.MF") manifestContent := []byte(entry) - err = ioutil.WriteFile(manifest, manifestContent, 0644) + err = os.WriteFile(manifest, manifestContent, 0644) if err != nil { return err } From e6513ec1b853e1e156f77f85a4bab269a05cb604 Mon Sep 17 00:00:00 2001 From: Anthony Dahanne Date: Tue, 12 Nov 2024 12:00:38 -0500 Subject: [PATCH 2/2] Close #434 * Try and find a more recent JRE/JDK version in case the Manifest found version is not available (best guess - trying only the next major version) --- jvm_version.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/jvm_version.go b/jvm_version.go index ae1ea47..347f9a6 100644 --- a/jvm_version.go +++ b/jvm_version.go @@ -65,16 +65,13 @@ func retrieveNextAvailableJavaVersionIfMavenVersionNotAvailable(dr libpak.Depend if libpak.IsNoValidDependencies(jdkErr) && libpak.IsNoValidDependencies(jreErr) { // the buildpack does not provide the wanted JDK or JRE version - let's check if we can choose a more recent version mavenJavaMajorVersionAsInt, _ := strconv.ParseInt(*mavenJavaMajorVersion, 10, 64) - versionToEvaluate := mavenJavaMajorVersionAsInt + 1 - for versionToEvaluate <= mavenJavaMajorVersionAsInt+5 { - _, jdkErr := dr.Resolve("jdk", strconv.FormatInt(versionToEvaluate, 10)) - _, jreErr := dr.Resolve("jre", strconv.FormatInt(versionToEvaluate, 10)) - if libpak.IsNoValidDependencies(jdkErr) && libpak.IsNoValidDependencies(jreErr) { - versionToEvaluate = versionToEvaluate + 1 - } else { - *mavenJavaMajorVersion = strconv.FormatInt(versionToEvaluate, 10) - break - } + nextVersionToEvaluate := mavenJavaMajorVersionAsInt + 1 + _, jdkErr := dr.Resolve("jdk", strconv.FormatInt(nextVersionToEvaluate, 10)) + _, jreErr := dr.Resolve("jre", strconv.FormatInt(nextVersionToEvaluate, 10)) + if libpak.IsNoValidDependencies(jdkErr) && libpak.IsNoValidDependencies(jreErr) { + // we tried with the next major version, still no Java candidate, we are done trying. + } else { + *mavenJavaMajorVersion = strconv.FormatInt(nextVersionToEvaluate, 10) } } }