Skip to content

Commit

Permalink
Merge pull request #436 from paketo-buildpacks/fix-434
Browse files Browse the repository at this point in the history
Close #434 : Better UX when there's not the desired JRE
  • Loading branch information
anthonydahanne authored Nov 12, 2024
2 parents 9928a07 + e6513ec commit d0895b1
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 22 deletions.
25 changes: 16 additions & 9 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
119 changes: 119 additions & 0 deletions build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package libjvm_test
import (
"io"
"os"
"path/filepath"
"testing"

"github.com/paketo-buildpacks/libpak/bard"
Expand Down Expand Up @@ -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{}{
Expand Down Expand Up @@ -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{}{
Expand Down
21 changes: 20 additions & 1 deletion jvm_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/heroku/color"
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -57,6 +59,23 @@ 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)
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)
}
}
}

func readJavaVersionFromSDKMANRCFile(appPath string) (string, error) {
components, err := ReadSDKMANRC(filepath.Join(appPath, ".sdkmanrc"))
if err != nil && errors.Is(err, os.ErrNotExist) {
Expand Down
24 changes: 12 additions & 12 deletions jvm_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package libjvm_test

import (
"io/ioutil"
"io"
"os"
"path/filepath"
"testing"
Expand All @@ -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{
Expand All @@ -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() {
Expand All @@ -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"))
})
Expand All @@ -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"))
})
Expand All @@ -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"))
})
Expand All @@ -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"))
})
Expand All @@ -135,15 +135,15 @@ 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() {
jvmVersion := libjvm.JVMVersion{Logger: logger}

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"))
})
Expand All @@ -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())
})

Expand All @@ -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"))
})
Expand All @@ -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
}
Expand Down

0 comments on commit d0895b1

Please sign in to comment.