blob: 9fd5ddd8eaecd09b042d4d73d55f4ccb2fe2430c [file] [log] [blame]
Jeff Gaston8bab5f22017-09-01 13:34:28 -07001// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18 "flag"
19 "fmt"
20 "log"
21 "os"
Nan Zhang13f4cf52017-09-19 18:42:01 -070022 "path/filepath"
Jeff Gaston8bab5f22017-09-01 13:34:28 -070023 "sort"
24 "strings"
25
26 "android/soong/jar"
27 "android/soong/third_party/zip"
28)
29
Nan Zhang13f4cf52017-09-19 18:42:01 -070030type stripDir struct{}
Nan Zhangd5998cc2017-09-13 13:17:43 -070031
Nan Zhang13f4cf52017-09-19 18:42:01 -070032func (s *stripDir) String() string {
Nan Zhangd5998cc2017-09-13 13:17:43 -070033 return `""`
34}
35
Nan Zhang13f4cf52017-09-19 18:42:01 -070036func (s *stripDir) Set(dir string) error {
37 stripDirs = append(stripDirs, filepath.Clean(dir))
38
39 return nil
40}
41
42type zipToNotStrip struct{}
43
44func (s *zipToNotStrip) String() string {
45 return `""`
46}
47
48func (s *zipToNotStrip) Set(zip_path string) error {
49 zipsToNotStrip[zip_path] = true
Nan Zhangd5998cc2017-09-13 13:17:43 -070050
51 return nil
52}
53
Jeff Gaston8bab5f22017-09-01 13:34:28 -070054var (
Nan Zhang13f4cf52017-09-19 18:42:01 -070055 sortEntries = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)")
56 emulateJar = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
57 stripDirs []string
58 zipsToNotStrip = make(map[string]bool)
Jeff Gaston8bab5f22017-09-01 13:34:28 -070059)
60
Nan Zhangd5998cc2017-09-13 13:17:43 -070061func init() {
Nan Zhang13f4cf52017-09-19 18:42:01 -070062 flag.Var(&stripDir{}, "stripDir", "the prefix of file path to be excluded from the output zip")
63 flag.Var(&zipToNotStrip{}, "zipToNotStrip", "the input zip file which is not applicable for stripping")
Nan Zhangd5998cc2017-09-13 13:17:43 -070064}
65
Jeff Gaston8bab5f22017-09-01 13:34:28 -070066func main() {
67 flag.Usage = func() {
68 fmt.Fprintln(os.Stderr, "usage: merge_zips [-j] output [inputs...]")
69 flag.PrintDefaults()
70 }
71
72 // parse args
73 flag.Parse()
74 args := flag.Args()
75 if len(args) < 2 {
76 flag.Usage()
77 os.Exit(1)
78 }
79 outputPath := args[0]
80 inputs := args[1:]
81
82 log.SetFlags(log.Lshortfile)
83
84 // make writer
85 output, err := os.Create(outputPath)
86 if err != nil {
87 log.Fatal(err)
88 }
89 defer output.Close()
90 writer := zip.NewWriter(output)
91 defer func() {
92 err := writer.Close()
93 if err != nil {
94 log.Fatal(err)
95 }
96 }()
97
98 // make readers
99 readers := []namedZipReader{}
100 for _, input := range inputs {
101 reader, err := zip.OpenReader(input)
102 if err != nil {
103 log.Fatal(err)
104 }
105 defer reader.Close()
106 namedReader := namedZipReader{path: input, reader: reader}
107 readers = append(readers, namedReader)
108 }
109
110 // do merge
Colin Cross34540312017-09-06 12:52:37 -0700111 if err := mergeZips(readers, writer, *sortEntries, *emulateJar); err != nil {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700112 log.Fatal(err)
113 }
114}
115
116// a namedZipReader reads a .zip file and can say which file it's reading
117type namedZipReader struct {
118 path string
119 reader *zip.ReadCloser
120}
121
122// a zipEntryPath refers to a file contained in a zip
123type zipEntryPath struct {
124 zipName string
125 entryName string
126}
127
128func (p zipEntryPath) String() string {
129 return p.zipName + "/" + p.entryName
130}
131
132// a zipEntry knows the location and content of a file within a zip
133type zipEntry struct {
134 path zipEntryPath
135 content *zip.File
136}
137
138// a fileMapping specifies to copy a zip entry from one place to another
139type fileMapping struct {
140 source zipEntry
141 dest string
142}
143
Colin Cross34540312017-09-06 12:52:37 -0700144func mergeZips(readers []namedZipReader, writer *zip.Writer, sortEntries bool, emulateJar bool) error {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700145
146 mappingsByDest := make(map[string]fileMapping, 0)
147 orderedMappings := []fileMapping{}
148
149 for _, namedReader := range readers {
Nan Zhang13f4cf52017-09-19 18:42:01 -0700150 _, skipStripThisZip := zipsToNotStrip[namedReader.path]
Nan Zhangd5998cc2017-09-13 13:17:43 -0700151 FileLoop:
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700152 for _, file := range namedReader.reader.File {
Nan Zhang13f4cf52017-09-19 18:42:01 -0700153 if !skipStripThisZip {
154 for _, dir := range stripDirs {
155 if strings.HasPrefix(file.Name, dir+"/") {
156 if emulateJar {
157 if file.Name != jar.MetaDir && file.Name != jar.ManifestFile {
158 continue FileLoop
159 }
160 } else {
161 continue FileLoop
162 }
163 }
Nan Zhangd5998cc2017-09-13 13:17:43 -0700164 }
165 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700166 // check for other files or directories destined for the same path
167 dest := file.Name
168 mapKey := dest
169 if strings.HasSuffix(mapKey, "/") {
170 mapKey = mapKey[:len(mapKey)-1]
171 }
172 existingMapping, exists := mappingsByDest[mapKey]
173
174 // make a new entry to add
175 source := zipEntry{path: zipEntryPath{zipName: namedReader.path, entryName: file.Name}, content: file}
176 newMapping := fileMapping{source: source, dest: dest}
177
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700178 if exists {
Colin Cross34540312017-09-06 12:52:37 -0700179 // handle duplicates
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700180 wasDir := existingMapping.source.content.FileHeader.FileInfo().IsDir()
181 isDir := newMapping.source.content.FileHeader.FileInfo().IsDir()
Colin Cross34540312017-09-06 12:52:37 -0700182 if wasDir != isDir {
183 return fmt.Errorf("Directory/file mismatch at %v from %v and %v\n",
184 dest, existingMapping.source.path, newMapping.source.path)
185 }
186 if emulateJar &&
187 file.Name == jar.ManifestFile || file.Name == jar.ModuleInfoClass {
188 // Skip manifest and module info files that are not from the first input file
189 continue
190 }
191 if !isDir {
Nan Zhangd5998cc2017-09-13 13:17:43 -0700192 if emulateJar {
193 if existingMapping.source.content.CRC32 != newMapping.source.content.CRC32 {
194 fmt.Fprintf(os.Stdout, "WARNING: Duplicate path %v found in %v and %v\n",
195 dest, existingMapping.source.path, newMapping.source.path)
196 }
197 } else {
198 return fmt.Errorf("Duplicate path %v found in %v and %v\n",
199 dest, existingMapping.source.path, newMapping.source.path)
200 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700201 }
Colin Cross34540312017-09-06 12:52:37 -0700202 } else {
203 // save entry
204 mappingsByDest[mapKey] = newMapping
205 orderedMappings = append(orderedMappings, newMapping)
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700206 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700207 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700208 }
209
Colin Cross34540312017-09-06 12:52:37 -0700210 if emulateJar {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700211 jarSort(orderedMappings)
212 } else if sortEntries {
213 alphanumericSort(orderedMappings)
214 }
215
216 for _, entry := range orderedMappings {
217 if err := writer.CopyFrom(entry.source.content, entry.dest); err != nil {
218 return err
219 }
220 }
221
222 return nil
223}
224
225func jarSort(files []fileMapping) {
226 sort.SliceStable(files, func(i, j int) bool {
227 return jar.EntryNamesLess(files[i].dest, files[j].dest)
228 })
229}
230
231func alphanumericSort(files []fileMapping) {
232 sort.SliceStable(files, func(i, j int) bool {
233 return files[i].dest < files[j].dest
234 })
235}