Skip to content

Commit

Permalink
Compatibility with Spring Boot 2.4.2
Browse files Browse the repository at this point in the history
In Boot 2.4.2 the format of classpath.idx changed to include the relative path of each jar that should be added to the classpath. Previous in only provided a list of jar names. When generating the classpath, the buildpack will now only prepend the value of Spring-Boot-Lib from the manifest to the indexed entries if the entries are naked file names. It will use the exact path provided if the entry contains a '/'

Also, adds all native-image arguments to the layer metadata (not just additional user provided arguments) so that changes in buildpack computed arguments trigger a rebuild.

Signed-off-by: Emily Casey <ecasey@vmware.com>
  • Loading branch information
ekcasey committed Jan 21, 2021
1 parent 1377491 commit 1e241e9
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 204 deletions.
146 changes: 79 additions & 67 deletions native/native_image.go
Expand Up @@ -40,7 +40,6 @@ type NativeImage struct {
ApplicationPath string
Arguments []string
Executor effect.Executor
LayerContributor libpak.LayerContributor
Logger bard.Logger
Manifest *properties.Properties
StackID string
Expand All @@ -49,89 +48,59 @@ type NativeImage struct {
func NewNativeImage(applicationPath string, arguments string, manifest *properties.Properties, stackID string) (NativeImage, error) {
var err error

expected := map[string]interface{}{}

expected["arguments"], err = shellwords.Parse(arguments)
args, err := shellwords.Parse(arguments)
if err != nil {
return NativeImage{}, fmt.Errorf("unable to parse arguments from %s\n%w", arguments, err)
}

expected["files"], err = sherpa.NewFileListing(applicationPath)
if err != nil {
return NativeImage{}, fmt.Errorf("unable to create file listing for %s\n%w", applicationPath, err)
}

n := NativeImage{
return NativeImage{
ApplicationPath: applicationPath,
Arguments: expected["arguments"].([]string),
Arguments: args,
Executor: effect.NewExecutor(),
LayerContributor: libpak.NewLayerContributor("Native Image", expected),
Manifest: manifest,
StackID: stackID,
}

return n, nil
}, nil
}

func (n NativeImage) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
n.LayerContributor.Logger = n.Logger

startClass, ok := n.Manifest.Get("Start-Class")
if !ok {
return libcnb.Layer{}, fmt.Errorf("manifest does not contain Start-Class")
}

layer, err := n.LayerContributor.Contribute(layer, func() (libcnb.Layer, error) {
cp := []string{n.ApplicationPath}

s, ok := n.Manifest.Get("Spring-Boot-Classes")
if !ok {
return libcnb.Layer{}, fmt.Errorf("manifest does not contain Spring-Boot-Classes")
}
cp = append(cp, filepath.Join(n.ApplicationPath, s))

s, ok = n.Manifest.Get("Spring-Boot-Classpath-Index")
if !ok {
return libcnb.Layer{}, fmt.Errorf("manifest does not contain Spring-Boot-Classpath-Index")
}

file := filepath.Join(n.ApplicationPath, s)
in, err := os.Open(file)
if err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to open %s\n%w", file, err)
}
defer in.Close()

var libs []string
if err := yaml.NewDecoder(in).Decode(&libs); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to decode %s\n%w", file, err)
}
cp, err := n.classpath()
if err != nil {
return libcnb.Layer{}, fmt.Errorf("failed to compute classpath\n%w", err)
}

s, ok = n.Manifest.Get("Spring-Boot-Lib")
if !ok {
return libcnb.Layer{}, fmt.Errorf("manifest does not contain Spring-Boot-Lib")
}
if !n.hasSpringNative(cp) {
return libcnb.Layer{}, errors.New("application is missing required 'spring-native' dependency")
}

for _, l := range libs {
cp = append(cp, filepath.Join(n.ApplicationPath, s, l))
}
arguments := n.Arguments

if !n.hasSpringNative(libs) {
return libcnb.Layer{}, errors.New("application is missing required 'spring-native' dependency")
}
if n.StackID == libpak.TinyStackID {
arguments = append(arguments, "-H:+StaticExecutableWithDynamicLibC")
}

arguments := n.Arguments
arguments = append(arguments,
fmt.Sprintf("-H:Name=%s", filepath.Join(layer.Path, startClass)),
"-cp", strings.Join(cp, ":"),
startClass,
)

if n.StackID == libpak.TinyStackID {
arguments = append(arguments, "-H:+StaticExecutableWithDynamicLibC")
}
files, err := sherpa.NewFileListing(n.ApplicationPath)
if err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to create file listing for %s\n%w", n.ApplicationPath, err)
}

arguments = append(arguments,
fmt.Sprintf("-H:Name=%s", filepath.Join(layer.Path, startClass)),
"-cp", strings.Join(cp, ":"),
startClass,
)
contributor := libpak.NewLayerContributor("Native Image", map[string]interface{}{
"files": files,
"arguments": arguments,
})
contributor.Logger = n.Logger

layer, err = contributor.Contribute(layer, func() (libcnb.Layer, error) {
if err := n.Executor.Execute(effect.Execution{
Command: "native-image",
Args: []string{"--version"},
Expand Down Expand Up @@ -171,17 +140,17 @@ func (n NativeImage) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
}
}

file := filepath.Join(layer.Path, startClass)
in, err := os.Open(file)
src := filepath.Join(layer.Path, startClass)
in, err := os.Open(src)
if err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to open %s\n%w", file, err)
return libcnb.Layer{}, fmt.Errorf("unable to open %s\n%w", filepath.Join(layer.Path, startClass), err)
}
defer in.Close()

file = filepath.Join(n.ApplicationPath, startClass)
out, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0755)
dst := filepath.Join(n.ApplicationPath, startClass)
out, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0755)
if err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to open %s\n%w", file, err)
return libcnb.Layer{}, fmt.Errorf("unable to open %s\n%w", dst, err)
}
defer out.Close()

Expand All @@ -192,6 +161,49 @@ func (n NativeImage) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
return layer, nil
}

func (n NativeImage) classpath() ([]string, error) {
cp := []string{n.ApplicationPath}

classesDir, ok := n.Manifest.Get("Spring-Boot-Classes")
if !ok {
return nil, fmt.Errorf("manifest does not contain Spring-Boot-Classes")
}
cp = append(cp, filepath.Join(n.ApplicationPath, classesDir))

classpathIdx, ok := n.Manifest.Get("Spring-Boot-Classpath-Index")
if !ok {
return nil, fmt.Errorf("manifest does not contain Spring-Boot-Classpath-Index")
}

file := filepath.Join(n.ApplicationPath, classpathIdx)
in, err := os.Open(filepath.Join(n.ApplicationPath, classpathIdx))
if err != nil {
return nil, fmt.Errorf("unable to open %s\n%w", file, err)
}
defer in.Close()

var libs []string
if err := yaml.NewDecoder(in).Decode(&libs); err != nil {
return nil, fmt.Errorf("unable to decode %s\n%w", file, err)
}

libDir, ok := n.Manifest.Get("Spring-Boot-Lib")
if !ok {
return nil, fmt.Errorf("manifest does not contain Spring-Boot-Lib")
}

for _, l := range libs {
if dir, _ := filepath.Split(l); dir == "" {
// In Spring Boot version < 2.4.2 classpath.idx contains a list of jars
cp = append(cp, filepath.Join(n.ApplicationPath, libDir, l))
} else {
// In Spring Boot version >= 2.4.2 classpath.idx contains a list of relative paths to jars
cp = append(cp, filepath.Join(n.ApplicationPath, l))
}
}
return cp, nil
}

func (NativeImage) Name() string {
return "native-image"
}
Expand Down

0 comments on commit 1e241e9

Please sign in to comment.