Skip to content

Commit

Permalink
Close #273: Use BP_MAVEN_ACTIVE_PROFILES to detect native
Browse files Browse the repository at this point in the history
* so that the user does not need to specify BP_NATIVE_IMAGE=true as well
  • Loading branch information
anthonydahanne committed Jun 13, 2023
1 parent bbff132 commit 1ecb10b
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 40 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ 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:
* If `<APPLICATION_ROOT>/META-INF/MANIFEST.MF` contains a `Spring-Boot-Native-Processed` entry OR if `$BP_MAVEN_ACTIVE_PROFILES` contains the `native` profile:
* 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
54 changes: 46 additions & 8 deletions boot/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,27 @@ package boot
import (
"fmt"
"github.com/buildpacks/libcnb"
"github.com/magiconair/properties"
"github.com/paketo-buildpacks/libjvm"
"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/libpak/bard"
"regexp"
"strconv"
"strings"
)

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

type Detect struct{}
type Detect struct {
Logger bard.Logger
}

func (Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) {
func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) {
result := libcnb.DetectResult{
Pass: true,
Plans: []libcnb.BuildPlan{
Expand All @@ -51,9 +59,15 @@ func (Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error)
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 {
cr, err := libpak.NewConfigurationResolver(context.Buildpack, nil)
if err != nil {
return libcnb.DetectResult{}, fmt.Errorf("unable to create configuration resolver\n%w", err)
}

mavenNativeProfileDetected := isMavenNativeProfileDetected(&cr, &d.Logger)
springBootNativeProcessedDetected := isSpringBootNativeProcessedDetected(manifest, &d.Logger)

if springBootNativeProcessedDetected || mavenNativeProfileDetected {
result = libcnb.DetectResult{
Pass: true,
Plans: []libcnb.BuildPlan{
Expand All @@ -72,3 +86,27 @@ func (Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error)
}
return result, nil
}

func isSpringBootNativeProcessedDetected(manifest *properties.Properties, logger *bard.Logger) bool {
springBootNativeProcessedString, found := manifest.Get("Spring-Boot-Native-Processed")
springBootNativeProcessed, _ := strconv.ParseBool(springBootNativeProcessedString)
detected := found && springBootNativeProcessed
if detected {
logger.Bodyf("Spring-Boot-Native-Processed MANIFEST entry was detected, activating native image")
}
return detected
}

func isMavenNativeProfileDetected(cr *libpak.ConfigurationResolver, logger *bard.Logger) bool {
mavenActiveProfiles, _ := cr.Resolve(MavenConfigActiveProfiles)
mavenActiveProfilesAsSlice := strings.Split(mavenActiveProfiles, ",")
r, _ := regexp.Compile("^native$|^\\?native$")

for _, profile := range mavenActiveProfilesAsSlice {
if r.MatchString(profile) {
logger.Bodyf("Maven native profile was detected in %s, activating native image", MavenConfigActiveProfiles)
return true
}
}
return false
}
94 changes: 65 additions & 29 deletions boot/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,47 +36,83 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
detect boot.Detect
)

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{
{
Provides: []libcnb.BuildPlanProvide{
{Name: "spring-boot"},
},
Requires: []libcnb.BuildPlanRequire{
{Name: "jvm-application"},
{Name: "spring-boot"},
},
nativeResult := 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"},
},
},
},
}

normalResult := libcnb.DetectResult{
Pass: true,
Plans: []libcnb.BuildPlan{
{
Provides: []libcnb.BuildPlanProvide{
{Name: "spring-boot"},
},
Requires: []libcnb.BuildPlanRequire{
{Name: "jvm-application"},
{Name: "spring-boot"},
},
},
}))
},
}

it("always passes for standard build", func() {
Expect(os.Unsetenv("BP_MAVEN_ACTIVE_PROFILES")).To(Succeed())
Expect(os.RemoveAll(filepath.Join(ctx.Application.Path, "META-INF"))).To(Succeed())
Expect(detect.Detect(ctx)).To(Equal(normalResult))
})

it("always passes for native build", func() {
Expect(os.Unsetenv("BP_MAVEN_ACTIVE_PROFILES")).To(Succeed())
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"},
},
},
},
}))
Expect(detect.Detect(ctx)).To(Equal(nativeResult))
})

it("using BP_MAVEN_ACTIVE_PROFILES", func() {

Expect(os.RemoveAll(filepath.Join(ctx.Application.Path, "META-INF"))).To(Succeed())

Expect(os.Setenv("BP_MAVEN_ACTIVE_PROFILES", "native")).To(Succeed())
Expect(detect.Detect(ctx)).To(Equal(nativeResult))

Expect(os.Setenv("BP_MAVEN_ACTIVE_PROFILES", "p1,native")).To(Succeed())
Expect(detect.Detect(ctx)).To(Equal(nativeResult))

Expect(os.Setenv("BP_MAVEN_ACTIVE_PROFILES", "p1,?native")).To(Succeed())
Expect(detect.Detect(ctx)).To(Equal(nativeResult))

Expect(os.Setenv("BP_MAVEN_ACTIVE_PROFILES", "native,p1")).To(Succeed())
Expect(detect.Detect(ctx)).To(Equal(nativeResult))

Expect(os.Setenv("BP_MAVEN_ACTIVE_PROFILES", "?native")).To(Succeed())
Expect(detect.Detect(ctx)).To(Equal(nativeResult))

Expect(os.Setenv("BP_MAVEN_ACTIVE_PROFILES", "mynative,native")).To(Succeed())
Expect(detect.Detect(ctx)).To(Equal(nativeResult))

Expect(os.Setenv("BP_MAVEN_ACTIVE_PROFILES", "mynative")).To(Succeed())
Expect(detect.Detect(ctx)).To(Equal(normalResult))

Expect(os.Setenv("BP_MAVEN_ACTIVE_PROFILES", "!native")).To(Succeed())
Expect(detect.Detect(ctx)).To(Equal(normalResult))

})

}
5 changes: 3 additions & 2 deletions cmd/main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ import (
)

func main() {
logger := bard.NewLogger(os.Stdout)
libpak.Main(
boot.Detect{},
boot.Build{Logger: bard.NewLogger(os.Stdout)},
boot.Detect{Logger: logger},
boot.Build{Logger: logger},
)
}

0 comments on commit 1ecb10b

Please sign in to comment.