Skip to content

Commit

Permalink
Merge pull request #171 from paketo-buildpacks/sdkman
Browse files Browse the repository at this point in the history
Support SDKMAN RC files
  • Loading branch information
Daniel Mikusa authored May 18, 2022
2 parents 743dda2 + 083f3bb commit b13c052
Show file tree
Hide file tree
Showing 6 changed files with 360 additions and 45 deletions.
2 changes: 1 addition & 1 deletion build.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
cl := NewCertificateLoader()
cl.Logger = b.Logger.BodyWriter()

jvmVersion := JVMVersion{Logger: b.Logger}
jvmVersion := NewJVMVersion(b.Logger)
v, err := jvmVersion.GetJVMVersion(context.Application.Path, cr)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to determine jvm version\n%w", err)
Expand Down
1 change: 1 addition & 0 deletions init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func TestUnit(t *testing.T) {
suite("NewManifest", testNewManifest)
suite("NewManifestFromJAR", testNewManifestFromJAR)
suite("MavenJARListing", testMavenJARListing)
suite("SDKMAN", testSDKMAN)
suite("Versions", testVersions)
suite("JVMVersions", testJVMVersion)
suite.Run(t)
Expand Down
86 changes: 65 additions & 21 deletions jvm_version.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package libjvm

import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/heroku/color"
Expand All @@ -12,36 +16,76 @@ type JVMVersion struct {
Logger bard.Logger
}

func (jvmVersion JVMVersion) GetJVMVersion(appPath string, cr libpak.ConfigurationResolver) (string, error) {
func NewJVMVersion(logger bard.Logger) JVMVersion {
return JVMVersion{Logger: logger}
}

func (j JVMVersion) GetJVMVersion(appPath string, cr libpak.ConfigurationResolver) (string, error) {
version, explicit := cr.Resolve("BP_JVM_VERSION")
if explicit {
f := color.New(color.Faint)
j.Logger.Body(f.Sprintf("Using Java version %s from BP_JVM_VERSION", version))
return version, nil
}

if !explicit {
manifest, err := NewManifest(appPath)
if err != nil {
return version, err
}
sdkmanrcJavaVersion, err := readJavaVersionFromSDKMANRCFile(appPath)
if err != nil {
return "", fmt.Errorf("unable to read Java version from SDMANRC file\n%w", err)
}

javaVersion := ""
if len(sdkmanrcJavaVersion) > 0 {
sdkmanrcJavaMajorVersion := extractMajorVersion(sdkmanrcJavaVersion)
f := color.New(color.Faint)
j.Logger.Body(f.Sprintf("Using Java version %s extracted from .sdkmanrc", sdkmanrcJavaMajorVersion))
return sdkmanrcJavaMajorVersion, nil
}

buildJdkSpecVersion, ok := manifest.Get("Build-Jdk-Spec")
if ok {
javaVersion = buildJdkSpecVersion
}
mavenJavaVersion, err := readJavaVersionFromMavenMetadata(appPath)
if err != nil {
return "", fmt.Errorf("unable to read Java version from Maven metadata\n%w", err)
}

buildJdkVersion, ok := manifest.Get("Build-Jdk")
if ok {
javaVersion = buildJdkVersion
}
if len(mavenJavaVersion) > 0 {
mavenJavaMajorVersion := extractMajorVersion(mavenJavaVersion)
f := color.New(color.Faint)
j.Logger.Body(f.Sprintf("Using Java version %s extracted from MANIFEST.MF", mavenJavaMajorVersion))
return mavenJavaMajorVersion, nil
}

f := color.New(color.Faint)
j.Logger.Body(f.Sprintf("Using buildpack default Java version %s", version))
return version, nil
}

func readJavaVersionFromSDKMANRCFile(appPath string) (string, error) {
components, err := ReadSDKMANRC(filepath.Join(appPath, ".sdkmanrc"))
if err != nil && errors.Is(err, os.ErrNotExist) {
return "", nil
} else if err != nil {
return "", err
}

if len(javaVersion) > 0 {
javaVersionFromMaven := extractMajorVersion(javaVersion)
f := color.New(color.Faint)
jvmVersion.Logger.Body(f.Sprintf("Using Java version %s extracted from MANIFEST.MF", javaVersionFromMaven))
return javaVersionFromMaven, nil
for _, component := range components {
if component.Type == "java" {
return component.Version, nil
}
}

return version, nil
return "", nil
}

func readJavaVersionFromMavenMetadata(appPath string) (string, error) {
manifest, err := NewManifest(appPath)
if err != nil {
return "", err
}

javaVersion, ok := manifest.Get("Build-Jdk-Spec")
if !ok {
javaVersion, _ = manifest.Get("Build-Jdk")
}

return javaVersion, nil
}

func extractMajorVersion(version string) string {
Expand Down
81 changes: 58 additions & 23 deletions jvm_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
package libjvm_test

import (
"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/libpak/bard"
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/libpak/bard"

"github.com/buildpacks/libcnb"
. "github.com/onsi/gomega"
"github.com/sclevine/spec"
Expand All @@ -40,6 +41,11 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) {
)

it.Before(func() {
var err error

appPath, err = ioutil.TempDir("", "application")
Expect(err).NotTo(HaveOccurred())

buildpack = libcnb.Buildpack{
Metadata: map[string]interface{}{
"configurations": []map[string]interface{}{
Expand All @@ -53,6 +59,10 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) {
logger = bard.NewLogger(ioutil.Discard)
})

it.After(func() {
Expect(os.RemoveAll(appPath)).To(Succeed())
})

it("detecting JVM version from default", func() {
jvmVersion := libjvm.JVMVersion{Logger: logger}

Expand Down Expand Up @@ -85,13 +95,7 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) {

context("detecting JVM version", func() {
it.Before(func() {
temp, err := prepareAppWithEntry("Build-Jdk: 1.8")
Expect(err).ToNot(HaveOccurred())
appPath = temp
})

it.After(func() {
os.RemoveAll(appPath)
Expect(prepareAppWithEntry(appPath, "Build-Jdk: 1.8")).ToNot(HaveOccurred())
})

it("from manifest via Build-Jdk-Spec", func() {
Expand All @@ -108,14 +112,11 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) {
context("detecting JVM version", func() {
it.Before(func() {
Expect(os.Setenv("BP_JVM_VERSION", "17")).To(Succeed())
temp, err := prepareAppWithEntry("Build-Jdk: 1.8")
Expect(err).ToNot(HaveOccurred())
appPath = temp
Expect(prepareAppWithEntry(appPath, "Build-Jdk: 1.8")).ToNot(HaveOccurred())
})

it.After(func() {
Expect(os.Unsetenv("BP_JVM_VERSION")).To(Succeed())
os.RemoveAll(appPath)
})

it("prefers environment variable over manifest", func() {
Expand All @@ -129,22 +130,56 @@ func testJVMVersion(t *testing.T, context spec.G, it spec.S) {
})
})

context("detecting JVM version", func() {
var sdkmanrcFile string

it.Before(func() {
sdkmanrcFile = filepath.Join(appPath, ".sdkmanrc")
Expect(ioutil.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)
Expect(err).ToNot(HaveOccurred())
Expect(version).To(Equal("17"))
})
})

context("detecting JVM version", func() {
var sdkmanrcFile string

it.Before(func() {
sdkmanrcFile = filepath.Join(appPath, ".sdkmanrc")
Expect(ioutil.WriteFile(sdkmanrcFile, []byte(`java=17.0.2-tem
java=11.0.2-tem`), 0644)).To(Succeed())
})

it("picks first from .sdkmanrc file if there are multiple", func() {
jvmVersion := libjvm.JVMVersion{Logger: logger}

cr, err := libpak.NewConfigurationResolver(buildpack, &logger)
Expect(err).ToNot(HaveOccurred())
version, err := jvmVersion.GetJVMVersion(appPath, cr)
Expect(err).ToNot(HaveOccurred())
Expect(version).To(Equal("17"))
})
})
}

func prepareAppWithEntry(entry string) (string, error) {
temp, err := ioutil.TempDir("", "jre-app")
if err != nil {
return "", err
}
err = os.Mkdir(filepath.Join(temp, "META-INF"), 0744)
func prepareAppWithEntry(appPath, entry string) error {
err := os.Mkdir(filepath.Join(appPath, "META-INF"), 0744)
if err != nil {
return "", err
return err
}
manifest := filepath.Join(temp, "META-INF", "MANIFEST.MF")
manifest := filepath.Join(appPath, "META-INF", "MANIFEST.MF")
manifestContent := []byte(entry)
err = ioutil.WriteFile(manifest, manifestContent, 0644)
if err != nil {
return "", err
return err
}
return temp, nil
return nil
}
76 changes: 76 additions & 0 deletions sdkman.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package libjvm

import (
"fmt"
"io/ioutil"
"strings"
)

// SDKInfo represents the information from each line in the `.sdkmanrc` file
type SDKInfo struct {
Type string
Version string
Vendor string
}

// ReadSDKMANRC reads the `.sdkmanrc` format file from path and retuns the list of SDKS in it
func ReadSDKMANRC(path string) ([]SDKInfo, error) {
sdkmanrcContents, err := ioutil.ReadFile(path)
if err != nil {
return []SDKInfo{}, fmt.Errorf("unable to read SDKMANRC file at %s\n%w", path, err)
}

sdks := []SDKInfo{}
for _, line := range strings.Split(string(sdkmanrcContents), "\n") {
if strings.TrimSpace(line) == "" {
continue
}

parts := strings.SplitN(line, "#", 2) // strip comments
if len(parts) != 1 && len(parts) != 2 {
return []SDKInfo{}, fmt.Errorf("unable to strip comments from %q resulted in %q", line, parts)
}

if strings.TrimSpace(parts[0]) != "" {
kv := strings.SplitN(parts[0], "=", 2) // split key=value
if len(kv) != 2 {
return []SDKInfo{}, fmt.Errorf("unable to split key/value from %q resulted in %q", parts[0], kv)
}

versionAndVendor := []string{"", ""}
if strings.TrimSpace(kv[1]) != "" {
versionAndVendor = strings.SplitN(kv[1], "-", 2) // split optional vendor name
if len(versionAndVendor) == 1 {
versionAndVendor = append(versionAndVendor, "")
}
if len(versionAndVendor) != 2 {
return []SDKInfo{}, fmt.Errorf("unable to split vendor from %q resulted in %q", kv[1], versionAndVendor)
}
}

sdks = append(sdks, SDKInfo{
Type: strings.ToLower(strings.TrimSpace(kv[0])),
Version: strings.TrimSpace(versionAndVendor[0]),
Vendor: strings.ToLower(strings.TrimSpace(versionAndVendor[1])),
})
}
}

return sdks, nil
}
Loading

0 comments on commit b13c052

Please sign in to comment.