blob: 556dad076ff38126fc207d9953b31c75bc22cf38 [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 Crosse909e1e2017-11-22 14:09:40 -080057 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)")
59 stripDirs fileList
60 stripFiles fileList
61 zipsToNotStrip = make(zipsToNotStripSet)
62 stripDirEntries = flag.Bool("D", false, "strip directory entries from the output zip file")
63 manifest = flag.String("m", "", "manifest file to insert in jar")
64 ignoreDuplicates = flag.Bool("ignore-duplicates", false, "take each entry from the first zip it exists in and don't warn")
Jeff Gaston8bab5f22017-09-01 13:34:28 -070065)
66
Nan Zhangd5998cc2017-09-13 13:17:43 -070067func init() {
Colin Cross0cf45cd2017-10-04 17:04:16 -070068 flag.Var(&stripDirs, "stripDir", "the prefix of file path to be excluded from the output zip")
69 flag.Var(&stripFiles, "stripFile", "filenames to be excluded from the output zip, accepts wildcards")
70 flag.Var(&zipsToNotStrip, "zipToNotStrip", "the input zip file which is not applicable for stripping")
Nan Zhangd5998cc2017-09-13 13:17:43 -070071}
72
Jeff Gaston8bab5f22017-09-01 13:34:28 -070073func main() {
74 flag.Usage = func() {
Colin Cross635acc92017-09-12 22:50:46 -070075 fmt.Fprintln(os.Stderr, "usage: merge_zips [-jsD] [-m manifest] output [inputs...]")
Jeff Gaston8bab5f22017-09-01 13:34:28 -070076 flag.PrintDefaults()
77 }
78
79 // parse args
80 flag.Parse()
81 args := flag.Args()
Colin Cross5c6ecc12017-10-23 18:12:27 -070082 if len(args) < 1 {
Jeff Gaston8bab5f22017-09-01 13:34:28 -070083 flag.Usage()
84 os.Exit(1)
85 }
86 outputPath := args[0]
87 inputs := args[1:]
88
89 log.SetFlags(log.Lshortfile)
90
91 // make writer
92 output, err := os.Create(outputPath)
93 if err != nil {
94 log.Fatal(err)
95 }
96 defer output.Close()
97 writer := zip.NewWriter(output)
98 defer func() {
99 err := writer.Close()
100 if err != nil {
101 log.Fatal(err)
102 }
103 }()
104
105 // make readers
106 readers := []namedZipReader{}
107 for _, input := range inputs {
108 reader, err := zip.OpenReader(input)
109 if err != nil {
110 log.Fatal(err)
111 }
112 defer reader.Close()
113 namedReader := namedZipReader{path: input, reader: reader}
114 readers = append(readers, namedReader)
115 }
116
Colin Cross635acc92017-09-12 22:50:46 -0700117 if *manifest != "" && !*emulateJar {
118 log.Fatal(errors.New("must specify -j when specifying a manifest via -m"))
119 }
120
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700121 // do merge
Colin Crosse909e1e2017-11-22 14:09:40 -0800122 err = mergeZips(readers, writer, *manifest, *sortEntries, *emulateJar, *stripDirEntries, *ignoreDuplicates)
Colin Cross635acc92017-09-12 22:50:46 -0700123 if err != nil {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700124 log.Fatal(err)
125 }
126}
127
128// a namedZipReader reads a .zip file and can say which file it's reading
129type namedZipReader struct {
130 path string
131 reader *zip.ReadCloser
132}
133
134// a zipEntryPath refers to a file contained in a zip
135type zipEntryPath struct {
136 zipName string
137 entryName string
138}
139
140func (p zipEntryPath) String() string {
141 return p.zipName + "/" + p.entryName
142}
143
Colin Cross635acc92017-09-12 22:50:46 -0700144// a zipEntry is a zipSource that pulls its content from another zip
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700145type zipEntry struct {
146 path zipEntryPath
147 content *zip.File
148}
149
Colin Cross635acc92017-09-12 22:50:46 -0700150func (ze zipEntry) String() string {
151 return ze.path.String()
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700152}
153
Colin Cross635acc92017-09-12 22:50:46 -0700154func (ze zipEntry) IsDir() bool {
155 return ze.content.FileInfo().IsDir()
156}
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700157
Colin Cross635acc92017-09-12 22:50:46 -0700158func (ze zipEntry) CRC32() uint32 {
159 return ze.content.FileHeader.CRC32
160}
161
162func (ze zipEntry) WriteToZip(dest string, zw *zip.Writer) error {
163 return zw.CopyFrom(ze.content, dest)
164}
165
166// a bufferEntry is a zipSource that pulls its content from a []byte
167type bufferEntry struct {
168 fh *zip.FileHeader
169 content []byte
170}
171
172func (be bufferEntry) String() string {
173 return "internal buffer"
174}
175
176func (be bufferEntry) IsDir() bool {
177 return be.fh.FileInfo().IsDir()
178}
179
180func (be bufferEntry) CRC32() uint32 {
181 return crc32.ChecksumIEEE(be.content)
182}
183
184func (be bufferEntry) WriteToZip(dest string, zw *zip.Writer) error {
185 w, err := zw.CreateHeader(be.fh)
186 if err != nil {
187 return err
188 }
189
190 if !be.IsDir() {
191 _, err = w.Write(be.content)
192 if err != nil {
193 return err
194 }
195 }
196
197 return nil
198}
199
200type zipSource interface {
201 String() string
202 IsDir() bool
203 CRC32() uint32
204 WriteToZip(dest string, zw *zip.Writer) error
205}
206
207// a fileMapping specifies to copy a zip entry from one place to another
208type fileMapping struct {
209 dest string
210 source zipSource
211}
212
213func mergeZips(readers []namedZipReader, writer *zip.Writer, manifest string,
Colin Crosse909e1e2017-11-22 14:09:40 -0800214 sortEntries, emulateJar, stripDirEntries, ignoreDuplicates bool) error {
Colin Cross635acc92017-09-12 22:50:46 -0700215
216 sourceByDest := make(map[string]zipSource, 0)
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700217 orderedMappings := []fileMapping{}
218
Colin Cross635acc92017-09-12 22:50:46 -0700219 // if dest already exists returns a non-null zipSource for the existing source
220 addMapping := func(dest string, source zipSource) zipSource {
221 mapKey := filepath.Clean(dest)
222 if existingSource, exists := sourceByDest[mapKey]; exists {
223 return existingSource
224 }
225
226 sourceByDest[mapKey] = source
227 orderedMappings = append(orderedMappings, fileMapping{source: source, dest: dest})
228 return nil
229 }
230
231 if manifest != "" {
232 if !stripDirEntries {
233 dirHeader := jar.MetaDirFileHeader()
234 dirSource := bufferEntry{dirHeader, nil}
235 addMapping(jar.MetaDir, dirSource)
236 }
237
238 fh, buf, err := jar.ManifestFileContents(manifest)
239 if err != nil {
240 return err
241 }
242
243 fileSource := bufferEntry{fh, buf}
244 addMapping(jar.ManifestFile, fileSource)
245 }
246
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700247 for _, namedReader := range readers {
Nan Zhang13f4cf52017-09-19 18:42:01 -0700248 _, skipStripThisZip := zipsToNotStrip[namedReader.path]
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700249 for _, file := range namedReader.reader.File {
Colin Cross0cf45cd2017-10-04 17:04:16 -0700250 if !skipStripThisZip && shouldStripFile(emulateJar, file.Name) {
251 continue
Nan Zhangd5998cc2017-09-13 13:17:43 -0700252 }
Colin Cross635acc92017-09-12 22:50:46 -0700253
254 if stripDirEntries && file.FileInfo().IsDir() {
255 continue
256 }
257
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700258 // check for other files or directories destined for the same path
259 dest := file.Name
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700260
261 // make a new entry to add
262 source := zipEntry{path: zipEntryPath{zipName: namedReader.path, entryName: file.Name}, content: file}
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700263
Colin Cross635acc92017-09-12 22:50:46 -0700264 if existingSource := addMapping(dest, source); existingSource != nil {
Colin Cross34540312017-09-06 12:52:37 -0700265 // handle duplicates
Colin Cross635acc92017-09-12 22:50:46 -0700266 if existingSource.IsDir() != source.IsDir() {
Colin Cross34540312017-09-06 12:52:37 -0700267 return fmt.Errorf("Directory/file mismatch at %v from %v and %v\n",
Colin Cross635acc92017-09-12 22:50:46 -0700268 dest, existingSource, source)
Colin Cross34540312017-09-06 12:52:37 -0700269 }
Colin Crosse909e1e2017-11-22 14:09:40 -0800270 if ignoreDuplicates {
271 continue
272 }
Colin Cross34540312017-09-06 12:52:37 -0700273 if emulateJar &&
274 file.Name == jar.ManifestFile || file.Name == jar.ModuleInfoClass {
275 // Skip manifest and module info files that are not from the first input file
276 continue
277 }
Colin Cross635acc92017-09-12 22:50:46 -0700278 if !source.IsDir() {
Nan Zhangd5998cc2017-09-13 13:17:43 -0700279 if emulateJar {
Colin Cross635acc92017-09-12 22:50:46 -0700280 if existingSource.CRC32() != source.CRC32() {
Nan Zhangd5998cc2017-09-13 13:17:43 -0700281 fmt.Fprintf(os.Stdout, "WARNING: 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 }
284 } else {
285 return fmt.Errorf("Duplicate path %v found in %v and %v\n",
Colin Cross635acc92017-09-12 22:50:46 -0700286 dest, existingSource, source)
Nan Zhangd5998cc2017-09-13 13:17:43 -0700287 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700288 }
289 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700290 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700291 }
292
Colin Cross34540312017-09-06 12:52:37 -0700293 if emulateJar {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700294 jarSort(orderedMappings)
295 } else if sortEntries {
296 alphanumericSort(orderedMappings)
297 }
298
299 for _, entry := range orderedMappings {
Colin Cross635acc92017-09-12 22:50:46 -0700300 if err := entry.source.WriteToZip(entry.dest, writer); err != nil {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700301 return err
302 }
303 }
304
305 return nil
306}
307
Colin Cross0cf45cd2017-10-04 17:04:16 -0700308func shouldStripFile(emulateJar bool, name string) bool {
309 for _, dir := range stripDirs {
310 if strings.HasPrefix(name, dir+"/") {
311 if emulateJar {
312 if name != jar.MetaDir && name != jar.ManifestFile {
313 return true
314 }
315 } else {
316 return true
317 }
318 }
319 }
320 for _, pattern := range stripFiles {
321 if match, err := filepath.Match(pattern, filepath.Base(name)); err != nil {
322 panic(fmt.Errorf("%s: %s", err.Error(), pattern))
323 } else if match {
324 return true
325 }
326 }
327 return false
328}
329
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700330func jarSort(files []fileMapping) {
331 sort.SliceStable(files, func(i, j int) bool {
332 return jar.EntryNamesLess(files[i].dest, files[j].dest)
333 })
334}
335
336func alphanumericSort(files []fileMapping) {
337 sort.SliceStable(files, func(i, j int) bool {
338 return files[i].dest < files[j].dest
339 })
340}