aboutsummaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
authorJeff Gaston <jeffrygaston@google.com>2017-09-01 13:34:28 -0700
committerJeff Gaston <jeffrygaston@google.com>2017-09-01 13:52:13 -0700
commit8bab5f20153fa722de15a9b17f112def5dce1ab8 (patch)
treec9793a6ecd34ff47972ad540c584eba5e10f77e3 /cmd
parent40962998a21708a4a06c8fd9b2254b6db7796f64 (diff)
downloadbuild_soong-8bab5f20153fa722de15a9b17f112def5dce1ab8.tar.gz
build_soong-8bab5f20153fa722de15a9b17f112def5dce1ab8.tar.bz2
build_soong-8bab5f20153fa722de15a9b17f112def5dce1ab8.zip
Implement merge_zips
For merging .zip and/or .jar files Bug: 64691570 Test: merge_zips merged.zip in1.zip in2.zip Change-Id: I9a080d25b92a506c9b5451670c4786895b2722ab
Diffstat (limited to 'cmd')
-rw-r--r--cmd/merge_zips/Android.bp25
-rw-r--r--cmd/merge_zips/merge_zips.go173
2 files changed, 198 insertions, 0 deletions
diff --git a/cmd/merge_zips/Android.bp b/cmd/merge_zips/Android.bp
new file mode 100644
index 00000000..ace079dc
--- /dev/null
+++ b/cmd/merge_zips/Android.bp
@@ -0,0 +1,25 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+blueprint_go_binary {
+ name: "merge_zips",
+ deps: [
+ "android-archive-zip",
+ "soong-jar",
+ ],
+ srcs: [
+ "merge_zips.go",
+ ],
+}
+
diff --git a/cmd/merge_zips/merge_zips.go b/cmd/merge_zips/merge_zips.go
new file mode 100644
index 00000000..e1780360
--- /dev/null
+++ b/cmd/merge_zips/merge_zips.go
@@ -0,0 +1,173 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "sort"
+ "strings"
+
+ "android/soong/jar"
+ "android/soong/third_party/zip"
+)
+
+var (
+ sortEntries = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)")
+ sortJava = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
+)
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintln(os.Stderr, "usage: merge_zips [-j] output [inputs...]")
+ flag.PrintDefaults()
+ }
+
+ // parse args
+ flag.Parse()
+ args := flag.Args()
+ if len(args) < 2 {
+ flag.Usage()
+ os.Exit(1)
+ }
+ outputPath := args[0]
+ inputs := args[1:]
+
+ log.SetFlags(log.Lshortfile)
+
+ // make writer
+ output, err := os.Create(outputPath)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer output.Close()
+ writer := zip.NewWriter(output)
+ defer func() {
+ err := writer.Close()
+ if err != nil {
+ log.Fatal(err)
+ }
+ }()
+
+ // make readers
+ readers := []namedZipReader{}
+ for _, input := range inputs {
+ reader, err := zip.OpenReader(input)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer reader.Close()
+ namedReader := namedZipReader{path: input, reader: reader}
+ readers = append(readers, namedReader)
+ }
+
+ // do merge
+ if err := mergeZips(readers, writer, *sortEntries, *sortJava); err != nil {
+ log.Fatal(err)
+ }
+}
+
+// a namedZipReader reads a .zip file and can say which file it's reading
+type namedZipReader struct {
+ path string
+ reader *zip.ReadCloser
+}
+
+// a zipEntryPath refers to a file contained in a zip
+type zipEntryPath struct {
+ zipName string
+ entryName string
+}
+
+func (p zipEntryPath) String() string {
+ return p.zipName + "/" + p.entryName
+}
+
+// a zipEntry knows the location and content of a file within a zip
+type zipEntry struct {
+ path zipEntryPath
+ content *zip.File
+}
+
+// a fileMapping specifies to copy a zip entry from one place to another
+type fileMapping struct {
+ source zipEntry
+ dest string
+}
+
+func mergeZips(readers []namedZipReader, writer *zip.Writer, sortEntries bool, sortJava bool) error {
+
+ mappingsByDest := make(map[string]fileMapping, 0)
+ orderedMappings := []fileMapping{}
+
+ for _, namedReader := range readers {
+ for _, file := range namedReader.reader.File {
+ // check for other files or directories destined for the same path
+ dest := file.Name
+ mapKey := dest
+ if strings.HasSuffix(mapKey, "/") {
+ mapKey = mapKey[:len(mapKey)-1]
+ }
+ existingMapping, exists := mappingsByDest[mapKey]
+
+ // make a new entry to add
+ source := zipEntry{path: zipEntryPath{zipName: namedReader.path, entryName: file.Name}, content: file}
+ newMapping := fileMapping{source: source, dest: dest}
+
+ // handle duplicates
+ if exists {
+ wasDir := existingMapping.source.content.FileHeader.FileInfo().IsDir()
+ isDir := newMapping.source.content.FileHeader.FileInfo().IsDir()
+ if !wasDir || !isDir {
+ return fmt.Errorf("Duplicate path %v found in %v and %v\n",
+ dest, existingMapping.source.path, newMapping.source.path)
+ }
+ }
+
+ // save entry
+ mappingsByDest[mapKey] = newMapping
+ orderedMappings = append(orderedMappings, newMapping)
+ }
+
+ }
+
+ if sortJava {
+ jarSort(orderedMappings)
+ } else if sortEntries {
+ alphanumericSort(orderedMappings)
+ }
+
+ for _, entry := range orderedMappings {
+ if err := writer.CopyFrom(entry.source.content, entry.dest); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func jarSort(files []fileMapping) {
+ sort.SliceStable(files, func(i, j int) bool {
+ return jar.EntryNamesLess(files[i].dest, files[j].dest)
+ })
+}
+
+func alphanumericSort(files []fileMapping) {
+ sort.SliceStable(files, func(i, j int) bool {
+ return files[i].dest < files[j].dest
+ })
+}