blob: e16c00ef9bb467a9d3e41fbe8876f1b9560ead33 [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 (
Colin Cross635acc92017-09-12 22:50:46 -070018 "errors"
Jeff Gaston8bab5f22017-09-01 13:34:28 -070019 "flag"
20 "fmt"
Colin Cross635acc92017-09-12 22:50:46 -070021 "hash/crc32"
Jeff Gaston8bab5f22017-09-01 13:34:28 -070022 "log"
23 "os"
Nan Zhang13f4cf52017-09-19 18:42:01 -070024 "path/filepath"
Jeff Gaston8bab5f22017-09-01 13:34:28 -070025 "sort"
26 "strings"
27
28 "android/soong/jar"
29 "android/soong/third_party/zip"
30)
31
Colin Cross0cf45cd2017-10-04 17:04:16 -070032type fileList []string
Nan Zhangd5998cc2017-09-13 13:17:43 -070033
Colin Cross0cf45cd2017-10-04 17:04:16 -070034func (f *fileList) String() string {
Nan Zhangd5998cc2017-09-13 13:17:43 -070035 return `""`
36}
37
Colin Cross0cf45cd2017-10-04 17:04:16 -070038func (f *fileList) Set(name string) error {
39 *f = append(*f, filepath.Clean(name))
Nan Zhang13f4cf52017-09-19 18:42:01 -070040
41 return nil
42}
43
Colin Cross0cf45cd2017-10-04 17:04:16 -070044type zipsToNotStripSet map[string]bool
Nan Zhang13f4cf52017-09-19 18:42:01 -070045
Colin Cross0cf45cd2017-10-04 17:04:16 -070046func (s zipsToNotStripSet) String() string {
Nan Zhang13f4cf52017-09-19 18:42:01 -070047 return `""`
48}
49
Colin Cross0cf45cd2017-10-04 17:04:16 -070050func (s zipsToNotStripSet) Set(zip_path string) error {
51 s[zip_path] = true
Nan Zhangd5998cc2017-09-13 13:17:43 -070052
53 return nil
54}
55
Jeff Gaston8bab5f22017-09-01 13:34:28 -070056var (
Colin Cross635acc92017-09-12 22:50:46 -070057 sortEntries = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)")
58 emulateJar = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
Colin Cross0cf45cd2017-10-04 17:04:16 -070059 stripDirs fileList
60 stripFiles fileList
61 zipsToNotStrip = make(zipsToNotStripSet)
Colin Cross635acc92017-09-12 22:50:46 -070062 stripDirEntries = flag.Bool("D", false, "strip directory entries from the output zip file")
63 manifest = flag.String("m", "", "manifest file to insert in jar")
Jeff Gaston8bab5f22017-09-01 13:34:28 -070064)
65
Nan Zhangd5998cc2017-09-13 13:17:43 -070066func init() {
Colin Cross0cf45cd2017-10-04 17:04:16 -070067 flag.Var(&stripDirs, "stripDir", "the prefix of file path to be excluded from the output zip")
68 flag.Var(&stripFiles, "stripFile", "filenames to be excluded from the output zip, accepts wildcards")
69 flag.Var(&zipsToNotStrip, "zipToNotStrip", "the input zip file which is not applicable for stripping")
Nan Zhangd5998cc2017-09-13 13:17:43 -070070}
71
Jeff Gaston8bab5f22017-09-01 13:34:28 -070072func main() {
73 flag.Usage = func() {
Colin Cross635acc92017-09-12 22:50:46 -070074 fmt.Fprintln(os.Stderr, "usage: merge_zips [-jsD] [-m manifest] output [inputs...]")
Jeff Gaston8bab5f22017-09-01 13:34:28 -070075 flag.PrintDefaults()
76 }
77
78 // parse args
79 flag.Parse()
80 args := flag.Args()
81 if len(args) < 2 {
82 flag.Usage()
83 os.Exit(1)
84 }
85 outputPath := args[0]
86 inputs := args[1:]
87
88 log.SetFlags(log.Lshortfile)
89
90 // make writer
91 output, err := os.Create(outputPath)
92 if err != nil {
93 log.Fatal(err)
94 }
95 defer output.Close()
96 writer := zip.NewWriter(output)
97 defer func() {
98 err := writer.Close()
99 if err != nil {
100 log.Fatal(err)
101 }
102 }()
103
104 // make readers
105 readers := []namedZipReader{}
106 for _, input := range inputs {
107 reader, err := zip.OpenReader(input)
108 if err != nil {
109 log.Fatal(err)
110 }
111 defer reader.Close()
112 namedReader := namedZipReader{path: input, reader: reader}
113 readers = append(readers, namedReader)
114 }
115
Colin Cross635acc92017-09-12 22:50:46 -0700116 if *manifest != "" && !*emulateJar {
117 log.Fatal(errors.New("must specify -j when specifying a manifest via -m"))
118 }
119
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700120 // do merge
Colin Cross635acc92017-09-12 22:50:46 -0700121 err = mergeZips(readers, writer, *manifest, *sortEntries, *emulateJar, *stripDirEntries)
122 if err != nil {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700123 log.Fatal(err)
124 }
125}
126
127// a namedZipReader reads a .zip file and can say which file it's reading
128type namedZipReader struct {
129 path string
130 reader *zip.ReadCloser
131}
132
133// a zipEntryPath refers to a file contained in a zip
134type zipEntryPath struct {
135 zipName string
136 entryName string
137}
138
139func (p zipEntryPath) String() string {
140 return p.zipName + "/" + p.entryName
141}
142
Colin Cross635acc92017-09-12 22:50:46 -0700143// a zipEntry is a zipSource that pulls its content from another zip
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700144type zipEntry struct {
145 path zipEntryPath
146 content *zip.File
147}
148
Colin Cross635acc92017-09-12 22:50:46 -0700149func (ze zipEntry) String() string {
150 return ze.path.String()
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700151}
152
Colin Cross635acc92017-09-12 22:50:46 -0700153func (ze zipEntry) IsDir() bool {
154 return ze.content.FileInfo().IsDir()
155}
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700156
Colin Cross635acc92017-09-12 22:50:46 -0700157func (ze zipEntry) CRC32() uint32 {
158 return ze.content.FileHeader.CRC32
159}
160
161func (ze zipEntry) WriteToZip(dest string, zw *zip.Writer) error {
162 return zw.CopyFrom(ze.content, dest)
163}
164
165// a bufferEntry is a zipSource that pulls its content from a []byte
166type bufferEntry struct {
167 fh *zip.FileHeader
168 content []byte
169}
170
171func (be bufferEntry) String() string {
172 return "internal buffer"
173}
174
175func (be bufferEntry) IsDir() bool {
176 return be.fh.FileInfo().IsDir()
177}
178
179func (be bufferEntry) CRC32() uint32 {
180 return crc32.ChecksumIEEE(be.content)
181}
182
183func (be bufferEntry) WriteToZip(dest string, zw *zip.Writer) error {
184 w, err := zw.CreateHeader(be.fh)
185 if err != nil {
186 return err
187 }
188
189 if !be.IsDir() {
190 _, err = w.Write(be.content)
191 if err != nil {
192 return err
193 }
194 }
195
196 return nil
197}
198
199type zipSource interface {
200 String() string
201 IsDir() bool
202 CRC32() uint32
203 WriteToZip(dest string, zw *zip.Writer) error
204}
205
206// a fileMapping specifies to copy a zip entry from one place to another
207type fileMapping struct {
208 dest string
209 source zipSource
210}
211
212func mergeZips(readers []namedZipReader, writer *zip.Writer, manifest string,
213 sortEntries, emulateJar, stripDirEntries bool) error {
214
215 sourceByDest := make(map[string]zipSource, 0)
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700216 orderedMappings := []fileMapping{}
217
Colin Cross635acc92017-09-12 22:50:46 -0700218 // if dest already exists returns a non-null zipSource for the existing source
219 addMapping := func(dest string, source zipSource) zipSource {
220 mapKey := filepath.Clean(dest)
221 if existingSource, exists := sourceByDest[mapKey]; exists {
222 return existingSource
223 }
224
225 sourceByDest[mapKey] = source
226 orderedMappings = append(orderedMappings, fileMapping{source: source, dest: dest})
227 return nil
228 }
229
230 if manifest != "" {
231 if !stripDirEntries {
232 dirHeader := jar.MetaDirFileHeader()
233 dirSource := bufferEntry{dirHeader, nil}
234 addMapping(jar.MetaDir, dirSource)
235 }
236
237 fh, buf, err := jar.ManifestFileContents(manifest)
238 if err != nil {
239 return err
240 }
241
242 fileSource := bufferEntry{fh, buf}
243 addMapping(jar.ManifestFile, fileSource)
244 }
245
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700246 for _, namedReader := range readers {
Nan Zhang13f4cf52017-09-19 18:42:01 -0700247 _, skipStripThisZip := zipsToNotStrip[namedReader.path]
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700248 for _, file := range namedReader.reader.File {
Colin Cross0cf45cd2017-10-04 17:04:16 -0700249 if !skipStripThisZip && shouldStripFile(emulateJar, file.Name) {
250 continue
Nan Zhangd5998cc2017-09-13 13:17:43 -0700251 }
Colin Cross635acc92017-09-12 22:50:46 -0700252
253 if stripDirEntries && file.FileInfo().IsDir() {
254 continue
255 }
256
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700257 // check for other files or directories destined for the same path
258 dest := file.Name
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700259
260 // make a new entry to add
261 source := zipEntry{path: zipEntryPath{zipName: namedReader.path, entryName: file.Name}, content: file}
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700262
Colin Cross635acc92017-09-12 22:50:46 -0700263 if existingSource := addMapping(dest, source); existingSource != nil {
Colin Cross34540312017-09-06 12:52:37 -0700264 // handle duplicates
Colin Cross635acc92017-09-12 22:50:46 -0700265 if existingSource.IsDir() != source.IsDir() {
Colin Cross34540312017-09-06 12:52:37 -0700266 return fmt.Errorf("Directory/file mismatch at %v from %v and %v\n",
Colin Cross635acc92017-09-12 22:50:46 -0700267 dest, existingSource, source)
Colin Cross34540312017-09-06 12:52:37 -0700268 }
269 if emulateJar &&
270 file.Name == jar.ManifestFile || file.Name == jar.ModuleInfoClass {
271 // Skip manifest and module info files that are not from the first input file
272 continue
273 }
Colin Cross635acc92017-09-12 22:50:46 -0700274 if !source.IsDir() {
Nan Zhangd5998cc2017-09-13 13:17:43 -0700275 if emulateJar {
Colin Cross635acc92017-09-12 22:50:46 -0700276 if existingSource.CRC32() != source.CRC32() {
Nan Zhangd5998cc2017-09-13 13:17:43 -0700277 fmt.Fprintf(os.Stdout, "WARNING: Duplicate path %v found in %v and %v\n",
Colin Cross635acc92017-09-12 22:50:46 -0700278 dest, existingSource, source)
Nan Zhangd5998cc2017-09-13 13:17:43 -0700279 }
280 } else {
281 return fmt.Errorf("Duplicate path %v found in %v and %v\n",
Colin Cross635acc92017-09-12 22:50:46 -0700282 dest, existingSource, source)
Nan Zhangd5998cc2017-09-13 13:17:43 -0700283 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700284 }
285 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700286 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700287 }
288
Colin Cross34540312017-09-06 12:52:37 -0700289 if emulateJar {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700290 jarSort(orderedMappings)
291 } else if sortEntries {
292 alphanumericSort(orderedMappings)
293 }
294
295 for _, entry := range orderedMappings {
Colin Cross635acc92017-09-12 22:50:46 -0700296 if err := entry.source.WriteToZip(entry.dest, writer); err != nil {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700297 return err
298 }
299 }
300
301 return nil
302}
303
Colin Cross0cf45cd2017-10-04 17:04:16 -0700304func shouldStripFile(emulateJar bool, name string) bool {
305 for _, dir := range stripDirs {
306 if strings.HasPrefix(name, dir+"/") {
307 if emulateJar {
308 if name != jar.MetaDir && name != jar.ManifestFile {
309 return true
310 }
311 } else {
312 return true
313 }
314 }
315 }
316 for _, pattern := range stripFiles {
317 if match, err := filepath.Match(pattern, filepath.Base(name)); err != nil {
318 panic(fmt.Errorf("%s: %s", err.Error(), pattern))
319 } else if match {
320 return true
321 }
322 }
323 return false
324}
325
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700326func jarSort(files []fileMapping) {
327 sort.SliceStable(files, func(i, j int) bool {
328 return jar.EntryNamesLess(files[i].dest, files[j].dest)
329 })
330}
331
332func alphanumericSort(files []fileMapping) {
333 sort.SliceStable(files, func(i, j int) bool {
334 return files[i].dest < files[j].dest
335 })
336}