Skip to content

Commit

Permalink
Add new function CreateJar
Browse files Browse the repository at this point in the history
* for now used in spring-boot buildpack
  • Loading branch information
anthonydahanne committed May 8, 2024
1 parent 4b86a85 commit a0c33d0
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
80 changes: 80 additions & 0 deletions crush/crush.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,86 @@ func CreateTarGz(destination io.Writer, source string) error {
return CreateTar(gz, source)
}

// CreateJar heavily inspired by: https://gosamples.dev/zip-file/
// Be aware that this function does not create a MANIFEST.MF file, not does it strictly enforce jar format
// in regard to elements that need to be STORE'd versus other that need to be DEFLATE'd; here everything is STORE'd
// Finally, source path must end with a trailing "/"
func CreateJar(source, target string) error {

// 1. Create a ZIP file and zip.Writer
f, err := os.Create(target)
if err != nil {
return err
}
defer f.Close()

writer := zip.NewWriter(f)
defer writer.Close()

// 2. Go through all the files of the source
return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
absolutePath := ""

if info.Mode()&os.ModeSymlink == os.ModeSymlink {
if absolutePath, err = filepath.EvalSymlinks(path); err != nil {
return fmt.Errorf("unable to eval symlink %s\n%w", absolutePath, err)
}
if file, err := os.Open(absolutePath); err != nil {
return fmt.Errorf("unable to open %s\n%w", absolutePath, err)
} else {
if info, err = file.Stat(); err != nil {
return fmt.Errorf("unable to stat %s\n%w", absolutePath, err)
}
}
}

// 3. Create a local file header
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}

// set compression
header.Method = zip.Store
// 4. Set relative path of a file as the header name
header.Name, err = filepath.Rel(source, path)
if err != nil {
return err
}
if info.IsDir() {
header.Name += "/"
}

// 5. Create writer for the file header and save content of the file
headerWriter, err := writer.CreateHeader(header)
if err != nil {
return err
}

if info.IsDir() {
return nil
}

if absolutePath != "" {
path = absolutePath
}

f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()

_, err = io.Copy(headerWriter, f)
writer.Flush()
return err
})

}

// Extract decompresses and extract source files to a destination directory or path. For archives, an arbitrary number of top-level directory
// components can be stripped from each path.
func Extract(source io.Reader, destination string, stripComponents int) error {
Expand Down
26 changes: 26 additions & 0 deletions crush/crush_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,32 @@ func testCrush(t *testing.T, context spec.G, it spec.S) {
Expect(filepath.Join(testPath, "dirA", "fileC.txt")).To(BeARegularFile())
Expect(os.Readlink(filepath.Join(testPath, "dirA", "fileD.txt"))).To(Equal(filepath.Join(path, "dirA", "fileC.txt")))
})

it("writes a JAR", func() {
cwd, _ := os.Getwd()
Expect(os.MkdirAll(filepath.Join(path, "META-INF"), 0755)).To(Succeed())
Expect(os.WriteFile(filepath.Join(path, "META-INF", "MANIFEST.MF"), []byte(`
Spring-Boot-Version: 3.3.1
Spring-Boot-Classes: BOOT-INF/classes
Spring-Boot-Lib: BOOT-INF/lib
`), 0644)).To(Succeed())
Expect(os.MkdirAll(filepath.Join(path, "BOOT-INF"), 0755)).To(Succeed())
Expect(os.MkdirAll(filepath.Join(path, "BOOT-INF", "classes"), 0755)).To(Succeed())
Expect(os.WriteFile(filepath.Join(path, "BOOT-INF", "classes", "OtherClass.class"), []byte(""), 0644)).To(Succeed())
Expect(os.WriteFile(filepath.Join(path, "BOOT-INF", "classes", "YetOther.class"), []byte(""), 0644)).To(Succeed())
Expect(os.MkdirAll(filepath.Join(path, "BOOT-INF", "lib"), 0755)).To(Succeed())
os.Symlink(filepath.Join(cwd, "testdata", "spring-cloud-bindings-1.2.3.jar"), filepath.Join(path, "BOOT-INF", "lib", "spring-cloud-bindings-1.2.3.jar"))

Expect(crush.CreateJar(path+"/", out.Name()+".jar")).To(Succeed())

in, err := os.Open(out.Name() + ".jar")
Expect(err).NotTo(HaveOccurred())

Expect(crush.Extract(in, testPath, 0)).To(Succeed())
Expect(filepath.Join(testPath, "BOOT-INF", "classes", "OtherClass.class")).To(BeARegularFile())
Expect(filepath.Join(testPath, "META-INF", "MANIFEST.MF")).To(BeARegularFile())
Expect(filepath.Join(testPath, "BOOT-INF", "lib", "spring-cloud-bindings-1.2.3.jar")).To(BeARegularFile())
})
})

context("Extract", func() {
Expand Down
Binary file added crush/testdata/spring-cloud-bindings-1.2.3.jar
Binary file not shown.

0 comments on commit a0c33d0

Please sign in to comment.