Skip to content

Commit

Permalink
Fix 273: Auto detect native image
Browse files Browse the repository at this point in the history
  • Loading branch information
anthonydahanne committed May 10, 2023
1 parent fc2d77a commit 34375c8
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 38 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ The buildpack will do the following:
* Contributes application slices as defined by the layer's index
* If the application is a reactive web application
* Configures `$BPL_JVM_THREAD_COUNT` to 50
* If `<APPLICATION_ROOT>/META-INF/MANIFEST.MF` contains a `Spring-Boot-Native-Processed` entry:
* A build plan entry is provided, `native-image-application`, which can be required by `native-image` to automatically trigger a native image build
* When contributing to a native image application:
* Adds classes from the executable JAR and entries from `classpath.idx` to the build-time class path, so they are available to `native-image`

Expand Down
6 changes: 6 additions & 0 deletions boot/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
}
}

if _, ok, err := pr.Resolve("native-processed"); err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to resolve native-processed plan entry\n%w", err)
} else if ok {
buildNativeImage = true
}

if buildNativeImage {
// set CLASSPATH for native image build
classpathLayer, err := NewNativeImageClasspath(context.Application.Path, manifest)
Expand Down
25 changes: 25 additions & 0 deletions boot/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,31 @@ Spring-Boot-Lib: BOOT-INF/lib
})
})

context("when a native-processed BuildPlanEntry is found with a native-image sub entry", func() {
it.Before(func() {
ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{
Name: "native-processed",
Metadata: map[string]interface{}{"native-image": true},
})
})

it("contributes a native image build", func() {
Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "META-INF", "MANIFEST.MF"), []byte(`
Spring-Boot-Version: 1.1.1
Spring-Boot-Classes: BOOT-INF/classes
Spring-Boot-Lib: BOOT-INF/lib
`), 0644)).To(Succeed())

result, err := build.Build(ctx)
Expect(err).NotTo(HaveOccurred())

Expect(result.Layers).To(HaveLen(1))
Expect(result.Layers[0].Name()).To(Equal("Class Path"))
Expect(result.Slices).To(HaveLen(0))
})

})

context("set BP_SPRING_CLOUD_BINDINGS_DISABLED to true", func() {
it.Before(func() {
Expect(os.Setenv("BP_SPRING_CLOUD_BINDINGS_DISABLED", "true")).To(Succeed())
Expand Down
43 changes: 36 additions & 7 deletions boot/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,58 @@
package boot

import (
"fmt"
"github.com/buildpacks/libcnb"
"github.com/paketo-buildpacks/libjvm"
"strconv"
)

const (
PlanEntrySpringBoot = "spring-boot"
PlanEntryJVMApplication = "jvm-application"
PlanEntrySpringBoot = "spring-boot"
PlanEntryJVMApplication = "jvm-application"
PlanEntryNativeProcessed = "native-processed"
)

type Detect struct{}

func (Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) {
return libcnb.DetectResult{
result := libcnb.DetectResult{
Pass: true,
Plans: []libcnb.BuildPlan{
{
Provides: []libcnb.BuildPlanProvide{
{Name: "spring-boot"},
{Name: PlanEntrySpringBoot},
},
Requires: []libcnb.BuildPlanRequire{
{Name: "jvm-application"},
{Name: "spring-boot"},
{Name: PlanEntryJVMApplication},
{Name: PlanEntrySpringBoot},
},
},
},
}, nil
}
manifest, err := libjvm.NewManifest(context.Application.Path)
if err != nil {
return libcnb.DetectResult{}, fmt.Errorf("unable to read manifest in %s\n%w", context.Application.Path, err)
}

springBootNativeProcessedString, ok := manifest.Get("Spring-Boot-Native-Processed")
springBootNativeProcessed, err := strconv.ParseBool(springBootNativeProcessedString)
if ok && springBootNativeProcessed {
result = libcnb.DetectResult{
Pass: true,
Plans: []libcnb.BuildPlan{
{
Provides: []libcnb.BuildPlanProvide{
{Name: PlanEntrySpringBoot},
{Name: PlanEntryNativeProcessed},
},
Requires: []libcnb.BuildPlanRequire{
{Name: PlanEntryJVMApplication},
{Name: PlanEntrySpringBoot},
},
},
},
}
}
return result, nil
}
30 changes: 29 additions & 1 deletion boot/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package boot_test

import (
"os"
"path/filepath"
"testing"

"github.com/buildpacks/libcnb"
Expand All @@ -34,7 +36,8 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
detect boot.Detect
)

it("always passes", func() {
it("always passes for standard build", func() {
Expect(os.RemoveAll(filepath.Join(ctx.Application.Path, "META-INF"))).To(Succeed())
Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{
Pass: true,
Plans: []libcnb.BuildPlan{
Expand All @@ -51,4 +54,29 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
}))
})

it("always passes for native build", func() {
Expect(os.MkdirAll(filepath.Join(ctx.Application.Path, "META-INF"), 0755)).To(Succeed())
Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "META-INF", "MANIFEST.MF"), []byte(`
Spring-Boot-Version: 1.1.1
Spring-Boot-Classes: BOOT-INF/classes
Spring-Boot-Lib: BOOT-INF/lib
Spring-Boot-Native-Processed: true
`), 0644)).To(Succeed())
Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{
Pass: true,
Plans: []libcnb.BuildPlan{
{
Provides: []libcnb.BuildPlanProvide{
{Name: "spring-boot"},
{Name: "native-processed"},
},
Requires: []libcnb.BuildPlanRequire{
{Name: "jvm-application"},
{Name: "spring-boot"},
},
},
},
}))
})

}
10 changes: 0 additions & 10 deletions boot/native_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package boot

import (
"fmt"
"github.com/paketo-buildpacks/libpak/sherpa"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -66,15 +65,6 @@ func (n NativeImageClasspath) Contribute(layer libcnb.Layer) (libcnb.Layer, erro
string(filepath.ListSeparator),
strings.Join(cp, string(filepath.ListSeparator)),
)

nativeImageArgFile := filepath.Join(n.ApplicationPath, "META-INF", "native-image", "argfile")
if exists, err := sherpa.Exists(nativeImageArgFile); err != nil{
return libcnb.Layer{}, fmt.Errorf("unable to check for native-image arguments file at %s\n%w", nativeImageArgFile, err)
} else if exists{
lc.Logger.Bodyf(fmt.Sprintf("native args file %s", nativeImageArgFile))
layer.BuildEnvironment.Default("BP_NATIVE_IMAGE_BUILD_ARGUMENTS_FILE", nativeImageArgFile)
}

return layer, nil
})
}
Expand Down
20 changes: 0 additions & 20 deletions boot/native_image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,24 +116,4 @@ func testNativeImage(t *testing.T, context spec.G, it spec.S) {
Expect(layer.LayerTypes.Launch).To(BeFalse())
})
})
context("Boot @argfile is found", func() {
it.Before(func() {
Expect(ioutil.WriteFile(filepath.Join(appDir, "BOOT-INF", "classpath.idx"), []byte(`
- "some.jar"
- "other.jar"
`), 0644)).To(Succeed())
Expect(os.MkdirAll(filepath.Join(appDir, "META-INF", "native-image"), 0755)).To(Succeed())
Expect(ioutil.WriteFile(filepath.Join(appDir, "META-INF", "native-image", "argfile"), []byte("file-data"), 0644)).To(Succeed())
})

it("ensures BP_NATIVE_IMAGE_BUILD_ARGUMENTS_FILE is set when argfile is found", func() {
layer, err := contributor.Contribute(layer)
Expect(err).NotTo(HaveOccurred())

Expect(layer.BuildEnvironment["BP_NATIVE_IMAGE_BUILD_ARGUMENTS_FILE.default"]).To(Equal(
filepath.Join(appDir, "META-INF", "native-image", "argfile")))
Expect(layer.LayerTypes.Build).To(BeTrue())
Expect(layer.LayerTypes.Launch).To(BeFalse())
})
})
}

0 comments on commit 34375c8

Please sign in to comment.