diff options
author | Jeff Gaston <jeffrygaston@google.com> | 2017-09-01 13:34:28 -0700 |
---|---|---|
committer | Jeff Gaston <jeffrygaston@google.com> | 2017-09-01 13:52:13 -0700 |
commit | 8bab5f20153fa722de15a9b17f112def5dce1ab8 (patch) | |
tree | c9793a6ecd34ff47972ad540c584eba5e10f77e3 /cmd | |
parent | 40962998a21708a4a06c8fd9b2254b6db7796f64 (diff) | |
download | build_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.bp | 25 | ||||
-rw-r--r-- | cmd/merge_zips/merge_zips.go | 173 |
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 + }) +} |