diff --git a/native/native_image.go b/native/native_image.go index 9a6a9d8..85830d8 100644 --- a/native/native_image.go +++ b/native/native_image.go @@ -20,6 +20,7 @@ import ( "bytes" "crypto/sha256" "fmt" + "github.com/paketo-buildpacks/native-image/v5/native/slices" "io" "io/ioutil" "os" @@ -69,9 +70,14 @@ func (n NativeImage) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { if err != nil { return libcnb.Layer{}, fmt.Errorf("unable to process arguments\n%w", err) } + + if !slices.Contains(arguments, "--auto-fallback") && !slices.Contains(arguments, "--force-fallback") { + arguments = append([]string{"--no-fallback"}, arguments...) + } + moduleVar := "USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM" - if _, set := os.LookupEnv(moduleVar); !set{ - if err := os.Setenv(moduleVar, "false"); err != nil{ + if _, set := os.LookupEnv(moduleVar); !set { + if err := os.Setenv(moduleVar, "false"); err != nil { n.Logger.Bodyf("unable to set %s for GraalVM 22.2, if your build fails, you may need to set this manually at build time", moduleVar) } } @@ -88,10 +94,10 @@ func (n NativeImage) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { nativeBinaryHash := fmt.Sprintf("%x", sha256.Sum256(buf.Bytes())) contributor := libpak.NewLayerContributor("Native Image", map[string]interface{}{ - "files": files, - "arguments": arguments, - "compression": n.Compressor, - "version-hash": nativeBinaryHash, + "files": files, + "arguments": arguments, + "compression": n.Compressor, + "version-hash": nativeBinaryHash, }, libcnb.LayerTypes{ Cache: true, }) diff --git a/native/native_image_test.go b/native/native_image_test.go index c6066b3..04f6a8b 100644 --- a/native/native_image_test.go +++ b/native/native_image_test.go @@ -96,7 +96,7 @@ func testNativeImage(t *testing.T, context spec.G, it spec.S) { executor.On("Execute", mock.MatchedBy(func(e effect.Execution) bool { return e.Command == "native-image" && - (e.Args[0] == "test-argument-1" || (e.Args[0] == "-H:+StaticExecutableWithDynamicLibC" && e.Args[1] == "test-argument-1")) + (e.Args[0] == "--no-fallback" || (e.Args[1] == "-H:+StaticExecutableWithDynamicLibC" && e.Args[0] == "--no-fallback")) })).Run(func(args mock.Arguments) { exec := args.Get(0).(effect.Execution) lastArg := exec.Args[len(exec.Args)-1] @@ -127,6 +127,7 @@ func testNativeImage(t *testing.T, context spec.G, it spec.S) { execution := executor.Calls[1].Arguments[0].(effect.Execution) Expect(execution.Args).To(Equal([]string{ + "--no-fallback", "test-argument-1", "test-argument-2", fmt.Sprintf("-H:Name=%s", filepath.Join(layer.Path, "test-start-class")), @@ -143,6 +144,7 @@ func testNativeImage(t *testing.T, context spec.G, it spec.S) { execution := executor.Calls[1].Arguments[0].(effect.Execution) Expect(execution.Args).To(Equal([]string{ + "--no-fallback", "test-argument-1", "test-argument-2", fmt.Sprintf("-H:Name=%s", filepath.Join(layer.Path, "test-start-class")), @@ -170,6 +172,7 @@ func testNativeImage(t *testing.T, context spec.G, it spec.S) { execution := executor.Calls[1].Arguments[0].(effect.Execution) Expect(execution.Args).To(Equal([]string{ + "--no-fallback", fmt.Sprintf("@%s", argsFile), fmt.Sprintf("-H:Name=%s", filepath.Join(layer.Path, "test-start-class")), "-cp", @@ -182,6 +185,100 @@ func testNativeImage(t *testing.T, context spec.G, it spec.S) { }) }) + context("user opts out of --no-fallback", func() { + var err error + + it("contributes native image with --force-fallback", func() { + executorForceFallback := &mocks.Executor{} + nativeImage, err = native.NewNativeImage(ctx.Application.Path, "--force-fallback test-argument-1 test-argument-2", "", "none", "", props, ctx.StackID) + nativeImage.Logger = bard.NewLogger(io.Discard) + Expect(err).NotTo(HaveOccurred()) + nativeImage.Executor = executorForceFallback + + executorForceFallback.On("Execute", mock.MatchedBy(func(e effect.Execution) bool { + return e.Command == "native-image" && len(e.Args) == 1 && e.Args[0] == "--version" + })).Run(func(args mock.Arguments) { + exec := args.Get(0).(effect.Execution) + _, err := exec.Stdout.Write([]byte("1.2.3")) + Expect(err).To(Succeed()) + }).Return(nil) + + executorForceFallback.On("Execute", mock.MatchedBy(func(e effect.Execution) bool { + return e.Command == "native-image" && + (e.Args[0] == "--force-fallback" || (e.Args[1] == "-H:+StaticExecutableWithDynamicLibC" && e.Args[0] == "--force-fallback")) + })).Run(func(args mock.Arguments) { + exec := args.Get(0).(effect.Execution) + lastArg := exec.Args[len(exec.Args)-1] + Expect(ioutil.WriteFile(filepath.Join(layer.Path, lastArg), []byte{}, 0644)).To(Succeed()) + }).Return(nil) + + layer, err = ctx.Layers.Layer("test-layer") + Expect(err).NotTo(HaveOccurred()) + + _, err := nativeImage.Contribute(layer) + Expect(err).NotTo(HaveOccurred()) + + execution := executorForceFallback.Calls[1].Arguments[0].(effect.Execution) + Expect(execution.Args).To(Equal([]string{ + "--force-fallback", + "test-argument-1", + "test-argument-2", + fmt.Sprintf("-H:Name=%s", filepath.Join(layer.Path, "test-start-class")), + "-cp", + strings.Join([]string{ + ctx.Application.Path, + "manifest-class-path", + }, ":"), + "test-start-class", + })) + }) + + it("contributes native image with --auto-fallback", func() { + executorAutoFallback := &mocks.Executor{} + nativeImage, err = native.NewNativeImage(ctx.Application.Path, "--auto-fallback test-argument-1 test-argument-2", "", "none", "", props, ctx.StackID) + nativeImage.Logger = bard.NewLogger(io.Discard) + Expect(err).NotTo(HaveOccurred()) + nativeImage.Executor = executorAutoFallback + + executorAutoFallback.On("Execute", mock.MatchedBy(func(e effect.Execution) bool { + return e.Command == "native-image" && len(e.Args) == 1 && e.Args[0] == "--version" + })).Run(func(args mock.Arguments) { + exec := args.Get(0).(effect.Execution) + _, err := exec.Stdout.Write([]byte("1.2.3")) + Expect(err).To(Succeed()) + }).Return(nil) + + executorAutoFallback.On("Execute", mock.MatchedBy(func(e effect.Execution) bool { + return e.Command == "native-image" && + (e.Args[0] == "--auto-fallback" || (e.Args[1] == "-H:+StaticExecutableWithDynamicLibC" && e.Args[0] == "--auto-fallback")) + })).Run(func(args mock.Arguments) { + exec := args.Get(0).(effect.Execution) + lastArg := exec.Args[len(exec.Args)-1] + Expect(ioutil.WriteFile(filepath.Join(layer.Path, lastArg), []byte{}, 0644)).To(Succeed()) + }).Return(nil) + + layer, err = ctx.Layers.Layer("test-layer") + Expect(err).NotTo(HaveOccurred()) + + _, err := nativeImage.Contribute(layer) + Expect(err).NotTo(HaveOccurred()) + + execution := executorAutoFallback.Calls[1].Arguments[0].(effect.Execution) + Expect(execution.Args).To(Equal([]string{ + "--auto-fallback", + "test-argument-1", + "test-argument-2", + fmt.Sprintf("-H:Name=%s", filepath.Join(layer.Path, "test-start-class")), + "-cp", + strings.Join([]string{ + ctx.Application.Path, + "manifest-class-path", + }, ":"), + "test-start-class", + })) + }) + }) + context("Not a Spring Boot app", func() { it.Before(func() { // there won't be a Start-Class @@ -198,6 +295,7 @@ func testNativeImage(t *testing.T, context spec.G, it spec.S) { execution := executor.Calls[1].Arguments[0].(effect.Execution) Expect(execution.Args).To(Equal([]string{ + "--no-fallback", "test-argument-1", "test-argument-2", fmt.Sprintf("-H:Name=%s", filepath.Join(layer.Path, "test-main-class")), @@ -291,6 +389,7 @@ func testNativeImage(t *testing.T, context spec.G, it spec.S) { execution := executor.Calls[1].Arguments[0].(effect.Execution) Expect(execution.Command).To(Equal("native-image")) Expect(execution.Args).To(Equal([]string{ + "--no-fallback", "-H:+StaticExecutableWithDynamicLibC", "test-argument-1", "test-argument-2", diff --git a/native/slices/slices.go b/native/slices/slices.go new file mode 100644 index 0000000..a28fd15 --- /dev/null +++ b/native/slices/slices.go @@ -0,0 +1,48 @@ +package slices + +// Those 2 functions were copied from https://cs.opensource.google/go/x/exp/+/master:slices/slices.go , +// an experimental collection of utility methods that could be promoted to stdlib in the future + +//Copyright (c) 2009 The Go Authors. All rights reserved. +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are +//met: +// +// * Redistributions of source code must retain the above copyright +//notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +//copyright notice, this list of conditions and the following disclaimer +//in the documentation and/or other materials provided with the +//distribution. +// * Neither the name of Google Inc. nor the names of its +//contributors may be used to endorse or promote products derived from +//this software without specific prior written permission. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +//A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +//OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +//LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +//DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +//THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Index returns the index of the first occurrence of v in s, +// or -1 if not present. +func Index[E comparable](s []E, v E) int { + for i := range s { + if v == s[i] { + return i + } + } + return -1 +} + +// Contains reports whether v is present in s. +func Contains[E comparable](s []E, v E) bool { + return Index(s, v) >= 0 +}