Skip to content

Commit

Permalink
Fix #488
Browse files Browse the repository at this point in the history
* if app Spring Boot >= 3.20 and has a META-INF/services/java.nio.file.spi.FileSystemProvider file, remove the line org.springframework.boot.loader.nio.file.NestedFileSystemProvider or delete the file if it was the only line found
  • Loading branch information
anthonydahanne committed Jun 18, 2024
1 parent aae2913 commit af38a5e
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 7 deletions.
55 changes: 53 additions & 2 deletions boot/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const (
LabelDataFlowConfigurationMetadata = "org.springframework.cloud.dataflow.spring-configuration-metadata.json"
SpringCloudBindingsBoot2 = "1"
SpringCloudBindingsBoot3 = "2"
FileSystemProvider = "java.nio.file.spi.FileSystemProvider"
NestedFileSystemProvider = "org.springframework.boot.loader.nio.file.NestedFileSystemProvider"
)

type Build struct {
Expand Down Expand Up @@ -149,6 +151,14 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
}

if buildNativeImage {
// Fix for https://github.com/paketo-buildpacks/spring-boot/issues/488
if versionFound && versionRespectsConstraint(version, ">= 3.2.0") {
err = lookForServicesFileSystemProviderAndRemoveNested(context)
if err != nil {
return libcnb.BuildResult{}, err
}
}

// set CLASSPATH for native image build
classpathLayer, err := NewNativeImageClasspath(context.Application.Path, manifest)
if err != nil {
Expand Down Expand Up @@ -281,6 +291,43 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
return result, nil
}

func lookForServicesFileSystemProviderAndRemoveNested(context libcnb.BuildContext) error {
dir := filepath.Join(context.Application.Path, "META-INF", "services")
if servicesFolderExists, err := sherpa.DirExists(dir); err != nil {
return fmt.Errorf("unable to check directory %s\n%w", dir, err)
} else if servicesFolderExists {
fileSystemProvider := filepath.Join(dir, FileSystemProvider)

content, err := os.ReadFile(fileSystemProvider)
if os.IsNotExist(err) {
return nil
} else if err != nil {
return fmt.Errorf("unable to read %s\n%w", fileSystemProvider, err)
}
originalLines := strings.Split(string(content), "\n")
var newLines []string
for _, line := range originalLines {
if !strings.Contains(line, NestedFileSystemProvider) {
newLines = append(newLines, line)
}
}

if len(newLines) > 0 {
err = os.WriteFile(fileSystemProvider, []byte(strings.Join(newLines, "\n")), 0644)
if err != nil {
return fmt.Errorf("unable to write to %s\n%w", fileSystemProvider, err)
}
} else {
err = os.Remove(fileSystemProvider)
if err != nil {
return fmt.Errorf("unable to delete file %s\n%w", fileSystemProvider, err)
}
}

}
return nil
}

func labels(jarPath string, manifest *properties.Properties) ([]libcnb.Label, error) {
var labels []libcnb.Label

Expand Down Expand Up @@ -545,7 +592,7 @@ func (b *Build) findSpringBootExecutableJAR(appPath string) (string, *properties
return noJar
})

if errors.Is(err, noJar) || err == nil{
if errors.Is(err, noJar) || err == nil {
return "", &properties.Properties{}, nil
}

Expand All @@ -569,7 +616,11 @@ func (b *Build) findSpringBootExecutableJAR(appPath string) (string, *properties
}

func bootCDSExtractionSupported(manifestVer string) bool {
bootThreeThreeConstraint, _ := semver.NewConstraint(">= 3.3.0")
return versionRespectsConstraint(manifestVer, ">= 3.3.0")
}

func versionRespectsConstraint(manifestVer string, constraint string) bool {
bootThreeThreeConstraint, _ := semver.NewConstraint(constraint)
bv, err := bootVersion(manifestVer)
if err != nil {
return false
Expand Down
98 changes: 93 additions & 5 deletions boot/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,94 @@ Spring-Boot-Lib: BOOT-INF/lib

Expect(result.Slices).To(HaveLen(0))
})

it("does not change META-INF/services if FileSystemProvider exists but Spring Boot < 3.2", 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())

Expect(os.Mkdir(filepath.Join(ctx.Application.Path, "META-INF", "services"), 0755)).To(Succeed())
Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "META-INF", "services", "java.nio.file.spi.FileSystemProvider"),
[]byte(`org.springframework.boot.loader.nio.file.NestedFileSystemProvider`), 0644)).To(Succeed())

result, err := build.Build(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(result.Layers).To(HaveLen(1))

fileBytes, err := os.ReadFile(filepath.Join(ctx.Application.Path, "META-INF", "services", "java.nio.file.spi.FileSystemProvider"))
Expect(err).NotTo(HaveOccurred())
Expect(string(fileBytes)).To(Equal("org.springframework.boot.loader.nio.file.NestedFileSystemProvider"))

})

it("does not blow up if META-INF/services does not exist and Spring Boot >= 3.2", func() {
Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "META-INF", "MANIFEST.MF"), []byte(`
Spring-Boot-Version: 3.2.0
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))
})

it("does not blow up if META-INF/services/java.nio.file.spi.FileSystemProvider does not exist and Spring Boot >= 3.2", func() {
Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "META-INF", "MANIFEST.MF"), []byte(`
Spring-Boot-Version: 3.2.0
Spring-Boot-Classes: BOOT-INF/classes
Spring-Boot-Lib: BOOT-INF/lib
`), 0644)).To(Succeed())
Expect(os.Mkdir(filepath.Join(ctx.Application.Path, "META-INF", "services"), 0755)).To(Succeed())

result, err := build.Build(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(result.Layers).To(HaveLen(1))
})

it("changes META-INF/services removing FileSystemProvider if Spring Boot >= 3.2 and only line is NestedFileSystemProvider", func() {
Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "META-INF", "MANIFEST.MF"), []byte(`
Spring-Boot-Version: 3.2.0
Spring-Boot-Classes: BOOT-INF/classes
Spring-Boot-Lib: BOOT-INF/lib
`), 0644)).To(Succeed())

Expect(os.Mkdir(filepath.Join(ctx.Application.Path, "META-INF", "services"), 0755)).To(Succeed())
Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "META-INF", "services", "java.nio.file.spi.FileSystemProvider"),
[]byte(`org.springframework.boot.loader.nio.file.NestedFileSystemProvider`), 0644)).To(Succeed())

result, err := build.Build(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(result.Layers).To(HaveLen(1))

_, err = os.Stat(filepath.Join(ctx.Application.Path, "META-INF", "services", "java.nio.file.spi.FileSystemProvider"))
Expect(err).To(HaveOccurred())
Expect(os.IsNotExist(err)).To(BeTrue())
})

it("changes META-INF/services removing line NestedFileSystemProvider from FileSystemProvider if Spring Boot >= 3.2", func() {
Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "META-INF", "MANIFEST.MF"), []byte(`
Spring-Boot-Version: 3.2.0
Spring-Boot-Classes: BOOT-INF/classes
Spring-Boot-Lib: BOOT-INF/lib
`), 0644)).To(Succeed())

Expect(os.Mkdir(filepath.Join(ctx.Application.Path, "META-INF", "services"), 0755)).To(Succeed())
Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "META-INF", "services", "java.nio.file.spi.FileSystemProvider"),
[]byte(`org.springframework.boot.loader.nio.file.NestedFileSystemProvider
jdk.nio.zipfs.ZipFileSystemProvider`), 0644)).To(Succeed())

result, err := build.Build(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(result.Layers).To(HaveLen(1))

fileBytes, err := os.ReadFile(filepath.Join(ctx.Application.Path, "META-INF", "services", "java.nio.file.spi.FileSystemProvider"))
Expect(err).NotTo(HaveOccurred())
Expect(string(fileBytes)).To(Equal("jdk.nio.zipfs.ZipFileSystemProvider"))
})

})

context("when a native-processed BuildPlanEntry is found with a native-image sub entry", func() {
Expand Down Expand Up @@ -637,9 +725,9 @@ Spring-Boot-Lib: BOOT-INF/lib
os.Remove(filepath.Join(ctx.Application.Path, "META-INF"))
})

it("finds and extracts a jar that exists", func(){
it("finds and extracts a jar that exists", func() {

Copy("cds","spring-app-3.3-no-dependencies.jar", "")
Copy("cds", "spring-app-3.3-no-dependencies.jar", "")

Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "other-file"), []byte(`
stuff
Expand All @@ -650,7 +738,7 @@ Spring-Boot-Lib: BOOT-INF/lib
Expect(result.Layers).To(HaveLen(1))
})

it("returns silently if no jar is found", func(){
it("returns silently if no jar is found", func() {

Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "other-file"), []byte(`
stuff
Expand All @@ -661,9 +749,9 @@ Spring-Boot-Lib: BOOT-INF/lib
Expect(result).To(Equal(libcnb.BuildResult{}))
})

it("returns silently if only a non-boot jar is found", func(){
it("returns silently if only a non-boot jar is found", func() {

Copy("","stub-empty.jar", "")
Copy("", "stub-empty.jar", "")

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

0 comments on commit af38a5e

Please sign in to comment.