blob: a70a9d158323d1f297499000b79f418e712868aa [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"
Dan Willemsen263dde72018-11-15 19:15:02 -080022 "io"
Nan Zhang5925b0f2017-12-19 15:13:40 -080023 "io/ioutil"
Jeff Gaston8bab5f22017-09-01 13:34:28 -070024 "log"
25 "os"
Nan Zhang13f4cf52017-09-19 18:42:01 -070026 "path/filepath"
Jeff Gaston8bab5f22017-09-01 13:34:28 -070027 "sort"
Colin Crossfd708b52021-03-23 14:16:05 -070028 "strings"
29
30 "android/soong/response"
Colin Cross4c03f682018-07-15 08:16:31 -070031
32 "github.com/google/blueprint/pathtools"
Jeff Gaston8bab5f22017-09-01 13:34:28 -070033
34 "android/soong/jar"
35 "android/soong/third_party/zip"
36)
37
Sasha Smundak1459a922019-07-16 18:45:24 -070038// Input zip: we can open it, close it, and obtain an array of entries
39type InputZip interface {
40 Name() string
41 Open() error
42 Close() error
43 Entries() []*zip.File
44 IsOpen() bool
45}
46
47// An entry that can be written to the output zip
48type ZipEntryContents interface {
49 String() string
50 IsDir() bool
51 CRC32() uint32
52 Size() uint64
53 WriteToZip(dest string, zw *zip.Writer) error
54}
55
56// a ZipEntryFromZip is a ZipEntryContents that pulls its content from another zip
57// identified by the input zip and the index of the entry in its entries array
58type ZipEntryFromZip struct {
59 inputZip InputZip
60 index int
61 name string
62 isDir bool
63 crc32 uint32
64 size uint64
65}
66
67func NewZipEntryFromZip(inputZip InputZip, entryIndex int) *ZipEntryFromZip {
68 fi := inputZip.Entries()[entryIndex]
69 newEntry := ZipEntryFromZip{inputZip: inputZip,
70 index: entryIndex,
71 name: fi.Name,
72 isDir: fi.FileInfo().IsDir(),
73 crc32: fi.CRC32,
74 size: fi.UncompressedSize64,
75 }
76 return &newEntry
77}
78
79func (ze ZipEntryFromZip) String() string {
80 return fmt.Sprintf("%s!%s", ze.inputZip.Name(), ze.name)
81}
82
83func (ze ZipEntryFromZip) IsDir() bool {
84 return ze.isDir
85}
86
87func (ze ZipEntryFromZip) CRC32() uint32 {
88 return ze.crc32
89}
90
91func (ze ZipEntryFromZip) Size() uint64 {
92 return ze.size
93}
94
95func (ze ZipEntryFromZip) WriteToZip(dest string, zw *zip.Writer) error {
96 if err := ze.inputZip.Open(); err != nil {
97 return err
98 }
99 return zw.CopyFrom(ze.inputZip.Entries()[ze.index], dest)
100}
101
102// a ZipEntryFromBuffer is a ZipEntryContents that pulls its content from a []byte
103type ZipEntryFromBuffer struct {
104 fh *zip.FileHeader
105 content []byte
106}
107
108func (be ZipEntryFromBuffer) String() string {
109 return "internal buffer"
110}
111
112func (be ZipEntryFromBuffer) IsDir() bool {
113 return be.fh.FileInfo().IsDir()
114}
115
116func (be ZipEntryFromBuffer) CRC32() uint32 {
117 return crc32.ChecksumIEEE(be.content)
118}
119
120func (be ZipEntryFromBuffer) Size() uint64 {
121 return uint64(len(be.content))
122}
123
124func (be ZipEntryFromBuffer) WriteToZip(dest string, zw *zip.Writer) error {
Colin Cross7592d5a2023-07-18 15:57:09 -0700125 w, err := zw.CreateHeaderAndroid(be.fh)
Sasha Smundak1459a922019-07-16 18:45:24 -0700126 if err != nil {
127 return err
128 }
129
130 if !be.IsDir() {
131 _, err = w.Write(be.content)
132 if err != nil {
133 return err
134 }
135 }
136
137 return nil
138}
139
140// Processing state.
141type OutputZip struct {
142 outputWriter *zip.Writer
143 stripDirEntries bool
144 emulateJar bool
145 sortEntries bool
146 ignoreDuplicates bool
147 excludeDirs []string
148 excludeFiles []string
149 sourceByDest map[string]ZipEntryContents
150}
151
152func NewOutputZip(outputWriter *zip.Writer, sortEntries, emulateJar, stripDirEntries, ignoreDuplicates bool) *OutputZip {
153 return &OutputZip{
154 outputWriter: outputWriter,
155 stripDirEntries: stripDirEntries,
156 emulateJar: emulateJar,
157 sortEntries: sortEntries,
158 sourceByDest: make(map[string]ZipEntryContents, 0),
159 ignoreDuplicates: ignoreDuplicates,
160 }
161}
162
163func (oz *OutputZip) setExcludeDirs(excludeDirs []string) {
164 oz.excludeDirs = make([]string, len(excludeDirs))
165 for i, dir := range excludeDirs {
166 oz.excludeDirs[i] = filepath.Clean(dir)
167 }
168}
169
170func (oz *OutputZip) setExcludeFiles(excludeFiles []string) {
171 oz.excludeFiles = excludeFiles
172}
173
174// Adds an entry with given name whose source is given ZipEntryContents. Returns old ZipEntryContents
175// if entry with given name already exists.
176func (oz *OutputZip) addZipEntry(name string, source ZipEntryContents) (ZipEntryContents, error) {
177 if existingSource, exists := oz.sourceByDest[name]; exists {
178 return existingSource, nil
179 }
180 oz.sourceByDest[name] = source
181 // Delay writing an entry if entries need to be rearranged.
182 if oz.emulateJar || oz.sortEntries {
183 return nil, nil
184 }
185 return nil, source.WriteToZip(name, oz.outputWriter)
186}
187
188// Adds an entry for the manifest (META-INF/MANIFEST.MF from the given file
189func (oz *OutputZip) addManifest(manifestPath string) error {
190 if !oz.stripDirEntries {
191 if _, err := oz.addZipEntry(jar.MetaDir, ZipEntryFromBuffer{jar.MetaDirFileHeader(), nil}); err != nil {
192 return err
193 }
194 }
195 contents, err := ioutil.ReadFile(manifestPath)
196 if err == nil {
197 fh, buf, err := jar.ManifestFileContents(contents)
198 if err == nil {
199 _, err = oz.addZipEntry(jar.ManifestFile, ZipEntryFromBuffer{fh, buf})
200 }
201 }
202 return err
203}
204
205// Adds an entry with given name and contents read from given file
206func (oz *OutputZip) addZipEntryFromFile(name string, path string) error {
207 buf, err := ioutil.ReadFile(path)
208 if err == nil {
209 fh := &zip.FileHeader{
210 Name: name,
211 Method: zip.Store,
212 UncompressedSize64: uint64(len(buf)),
213 }
214 fh.SetMode(0700)
215 fh.SetModTime(jar.DefaultTime)
216 _, err = oz.addZipEntry(name, ZipEntryFromBuffer{fh, buf})
217 }
218 return err
219}
220
221func (oz *OutputZip) addEmptyEntry(entry string) error {
222 var emptyBuf []byte
223 fh := &zip.FileHeader{
224 Name: entry,
225 Method: zip.Store,
226 UncompressedSize64: uint64(len(emptyBuf)),
227 }
228 fh.SetMode(0700)
229 fh.SetModTime(jar.DefaultTime)
230 _, err := oz.addZipEntry(entry, ZipEntryFromBuffer{fh, emptyBuf})
231 return err
232}
233
234// Returns true if given entry is to be excluded
235func (oz *OutputZip) isEntryExcluded(name string) bool {
236 for _, dir := range oz.excludeDirs {
237 dir = filepath.Clean(dir)
238 patterns := []string{
239 dir + "/", // the directory itself
240 dir + "/**/*", // files recursively in the directory
241 dir + "/**/*/", // directories recursively in the directory
242 }
243
244 for _, pattern := range patterns {
245 match, err := pathtools.Match(pattern, name)
246 if err != nil {
247 panic(fmt.Errorf("%s: %s", err.Error(), pattern))
248 }
249 if match {
250 if oz.emulateJar {
251 // When merging jar files, don't strip META-INF/MANIFEST.MF even if stripping META-INF is
252 // requested.
253 // TODO(ccross): which files does this affect?
254 if name != jar.MetaDir && name != jar.ManifestFile {
255 return true
256 }
257 }
258 return true
259 }
260 }
261 }
262
263 for _, pattern := range oz.excludeFiles {
264 match, err := pathtools.Match(pattern, name)
265 if err != nil {
266 panic(fmt.Errorf("%s: %s", err.Error(), pattern))
267 }
268 if match {
269 return true
270 }
271 }
272 return false
273}
274
275// Creates a zip entry whose contents is an entry from the given input zip.
276func (oz *OutputZip) copyEntry(inputZip InputZip, index int) error {
277 entry := NewZipEntryFromZip(inputZip, index)
278 if oz.stripDirEntries && entry.IsDir() {
279 return nil
280 }
281 existingEntry, err := oz.addZipEntry(entry.name, entry)
282 if err != nil {
283 return err
284 }
285 if existingEntry == nil {
286 return nil
287 }
288
289 // File types should match
290 if existingEntry.IsDir() != entry.IsDir() {
291 return fmt.Errorf("Directory/file mismatch at %v from %v and %v\n",
292 entry.name, existingEntry, entry)
293 }
294
295 if oz.ignoreDuplicates ||
296 // Skip manifest and module info files that are not from the first input file
297 (oz.emulateJar && entry.name == jar.ManifestFile || entry.name == jar.ModuleInfoClass) ||
298 // Identical entries
299 (existingEntry.CRC32() == entry.CRC32() && existingEntry.Size() == entry.Size()) ||
300 // Directory entries
301 entry.IsDir() {
302 return nil
303 }
304
305 return fmt.Errorf("Duplicate path %v found in %v and %v\n", entry.name, existingEntry, inputZip.Name())
306}
307
308func (oz *OutputZip) entriesArray() []string {
309 entries := make([]string, len(oz.sourceByDest))
310 i := 0
311 for entry := range oz.sourceByDest {
312 entries[i] = entry
313 i++
314 }
315 return entries
316}
317
318func (oz *OutputZip) jarSorted() []string {
319 entries := oz.entriesArray()
320 sort.SliceStable(entries, func(i, j int) bool { return jar.EntryNamesLess(entries[i], entries[j]) })
321 return entries
322}
323
324func (oz *OutputZip) alphanumericSorted() []string {
325 entries := oz.entriesArray()
326 sort.Strings(entries)
327 return entries
328}
329
330func (oz *OutputZip) writeEntries(entries []string) error {
331 for _, entry := range entries {
332 source, _ := oz.sourceByDest[entry]
333 if err := source.WriteToZip(entry, oz.outputWriter); err != nil {
334 return err
335 }
336 }
337 return nil
338}
339
340func (oz *OutputZip) getUninitializedPythonPackages(inputZips []InputZip) ([]string, error) {
341 // the runfiles packages needs to be populated with "__init__.py".
342 // the runfiles dirs have been treated as packages.
343 allPackages := make(map[string]bool)
344 initedPackages := make(map[string]bool)
345 getPackage := func(path string) string {
346 ret := filepath.Dir(path)
347 // filepath.Dir("abc") -> "." and filepath.Dir("/abc") -> "/".
348 if ret == "." || ret == "/" {
349 return ""
350 }
351 return ret
352 }
353
354 // put existing __init__.py files to a set first. This set is used for preventing
355 // generated __init__.py files from overwriting existing ones.
356 for _, inputZip := range inputZips {
357 if err := inputZip.Open(); err != nil {
358 return nil, err
359 }
360 for _, file := range inputZip.Entries() {
361 pyPkg := getPackage(file.Name)
Cole Faust5c503d12023-01-24 11:48:08 -0800362 baseName := filepath.Base(file.Name)
363 if baseName == "__init__.py" || baseName == "__init__.pyc" {
Sasha Smundak1459a922019-07-16 18:45:24 -0700364 if _, found := initedPackages[pyPkg]; found {
365 panic(fmt.Errorf("found __init__.py path duplicates during pars merging: %q", file.Name))
366 }
367 initedPackages[pyPkg] = true
368 }
369 for pyPkg != "" {
370 if _, found := allPackages[pyPkg]; found {
371 break
372 }
373 allPackages[pyPkg] = true
374 pyPkg = getPackage(pyPkg)
375 }
376 }
377 }
378 noInitPackages := make([]string, 0)
379 for pyPkg := range allPackages {
380 if _, found := initedPackages[pyPkg]; !found {
381 noInitPackages = append(noInitPackages, pyPkg)
382 }
383 }
384 return noInitPackages, nil
385}
386
387// An InputZip owned by the InputZipsManager. Opened ManagedInputZip's are chained in the open order.
388type ManagedInputZip struct {
389 owner *InputZipsManager
390 realInputZip InputZip
391 older *ManagedInputZip
392 newer *ManagedInputZip
393}
394
395// Maintains the array of ManagedInputZips, keeping track of open input ones. When an InputZip is opened,
396// may close some other InputZip to limit the number of open ones.
397type InputZipsManager struct {
398 inputZips []*ManagedInputZip
399 nOpenZips int
400 maxOpenZips int
401 openInputZips *ManagedInputZip
402}
403
404func (miz *ManagedInputZip) unlink() {
405 olderMiz := miz.older
406 newerMiz := miz.newer
407 if newerMiz.older != miz || olderMiz.newer != miz {
408 panic(fmt.Errorf("removing %p:%#v: broken list between %p:%#v and %p:%#v",
409 miz, miz, newerMiz, newerMiz, olderMiz, olderMiz))
410 }
411 olderMiz.newer = newerMiz
412 newerMiz.older = olderMiz
413 miz.newer = nil
414 miz.older = nil
415}
416
417func (miz *ManagedInputZip) link(olderMiz *ManagedInputZip) {
418 if olderMiz.newer != nil || olderMiz.older != nil {
419 panic(fmt.Errorf("inputZip is already open"))
420 }
421 oldOlderMiz := miz.older
422 if oldOlderMiz.newer != miz {
Colin Crossb5f6dc42019-09-11 09:48:37 -0700423 panic(fmt.Errorf("broken list between %p:%#v and %p:%#v", miz, miz, oldOlderMiz, oldOlderMiz))
Sasha Smundak1459a922019-07-16 18:45:24 -0700424 }
425 miz.older = olderMiz
426 olderMiz.older = oldOlderMiz
427 oldOlderMiz.newer = olderMiz
428 olderMiz.newer = miz
429}
430
431func NewInputZipsManager(nInputZips, maxOpenZips int) *InputZipsManager {
432 if maxOpenZips < 3 {
433 panic(fmt.Errorf("open zips limit should be above 3"))
434 }
Patrice Arruda8da724a2020-07-28 21:22:12 +0000435 // In the fake element .older points to the most recently opened InputZip, and .newer points to the oldest.
Sasha Smundak1459a922019-07-16 18:45:24 -0700436 head := new(ManagedInputZip)
437 head.older = head
438 head.newer = head
439 return &InputZipsManager{
440 inputZips: make([]*ManagedInputZip, 0, nInputZips),
441 maxOpenZips: maxOpenZips,
442 openInputZips: head,
443 }
444}
445
446// InputZip factory
447func (izm *InputZipsManager) Manage(inz InputZip) InputZip {
448 iz := &ManagedInputZip{owner: izm, realInputZip: inz}
449 izm.inputZips = append(izm.inputZips, iz)
450 return iz
451}
452
453// Opens or reopens ManagedInputZip.
454func (izm *InputZipsManager) reopen(miz *ManagedInputZip) error {
455 if miz.realInputZip.IsOpen() {
456 if miz != izm.openInputZips {
457 miz.unlink()
458 izm.openInputZips.link(miz)
459 }
460 return nil
461 }
462 if izm.nOpenZips >= izm.maxOpenZips {
463 if err := izm.close(izm.openInputZips.older); err != nil {
464 return err
465 }
466 }
467 if err := miz.realInputZip.Open(); err != nil {
468 return err
469 }
470 izm.openInputZips.link(miz)
471 izm.nOpenZips++
472 return nil
473}
474
475func (izm *InputZipsManager) close(miz *ManagedInputZip) error {
476 if miz.IsOpen() {
477 err := miz.realInputZip.Close()
478 izm.nOpenZips--
479 miz.unlink()
480 return err
481 }
482 return nil
483}
484
485// Checks that openInputZips deque is valid
486func (izm *InputZipsManager) checkOpenZipsDeque() {
487 nReallyOpen := 0
488 el := izm.openInputZips
489 for {
490 elNext := el.older
491 if elNext.newer != el {
492 panic(fmt.Errorf("Element:\n %p: %v\nNext:\n %p %v", el, el, elNext, elNext))
493 }
494 if elNext == izm.openInputZips {
495 break
496 }
497 el = elNext
498 if !el.IsOpen() {
499 panic(fmt.Errorf("Found unopened element"))
500 }
501 nReallyOpen++
502 if nReallyOpen > izm.nOpenZips {
503 panic(fmt.Errorf("found %d open zips, should be %d", nReallyOpen, izm.nOpenZips))
504 }
505 }
506 if nReallyOpen > izm.nOpenZips {
507 panic(fmt.Errorf("found %d open zips, should be %d", nReallyOpen, izm.nOpenZips))
508 }
509}
510
511func (miz *ManagedInputZip) Name() string {
512 return miz.realInputZip.Name()
513}
514
515func (miz *ManagedInputZip) Open() error {
516 return miz.owner.reopen(miz)
517}
518
519func (miz *ManagedInputZip) Close() error {
520 return miz.owner.close(miz)
521}
522
523func (miz *ManagedInputZip) IsOpen() bool {
524 return miz.realInputZip.IsOpen()
525}
526
527func (miz *ManagedInputZip) Entries() []*zip.File {
528 if !miz.IsOpen() {
529 panic(fmt.Errorf("%s: is not open", miz.Name()))
530 }
531 return miz.realInputZip.Entries()
532}
533
534// Actual processing.
535func mergeZips(inputZips []InputZip, writer *zip.Writer, manifest, pyMain string,
536 sortEntries, emulateJar, emulatePar, stripDirEntries, ignoreDuplicates bool,
537 excludeFiles, excludeDirs []string, zipsToNotStrip map[string]bool) error {
538
539 out := NewOutputZip(writer, sortEntries, emulateJar, stripDirEntries, ignoreDuplicates)
540 out.setExcludeFiles(excludeFiles)
541 out.setExcludeDirs(excludeDirs)
542 if manifest != "" {
543 if err := out.addManifest(manifest); err != nil {
544 return err
545 }
546 }
547 if pyMain != "" {
548 if err := out.addZipEntryFromFile("__main__.py", pyMain); err != nil {
549 return err
550 }
551 }
552
553 if emulatePar {
554 noInitPackages, err := out.getUninitializedPythonPackages(inputZips)
555 if err != nil {
556 return err
557 }
558 for _, uninitializedPyPackage := range noInitPackages {
559 if err = out.addEmptyEntry(filepath.Join(uninitializedPyPackage, "__init__.py")); err != nil {
560 return err
561 }
562 }
563 }
564
Colin Cross7592d5a2023-07-18 15:57:09 -0700565 var jarServices jar.Services
566
Sasha Smundak1459a922019-07-16 18:45:24 -0700567 // Finally, add entries from all the input zips.
568 for _, inputZip := range inputZips {
569 _, copyFully := zipsToNotStrip[inputZip.Name()]
570 if err := inputZip.Open(); err != nil {
571 return err
572 }
573
574 for i, entry := range inputZip.Entries() {
Colin Cross7592d5a2023-07-18 15:57:09 -0700575 if emulateJar && jarServices.IsServiceFile(entry) {
576 // If this is a jar, collect service files to combine instead of adding them to the zip.
577 err := jarServices.AddServiceFile(entry)
578 if err != nil {
579 return err
580 }
581 continue
582 }
Sasha Smundak1459a922019-07-16 18:45:24 -0700583 if copyFully || !out.isEntryExcluded(entry.Name) {
584 if err := out.copyEntry(inputZip, i); err != nil {
585 return err
586 }
587 }
588 }
589 // Unless we need to rearrange the entries, the input zip can now be closed.
590 if !(emulateJar || sortEntries) {
591 if err := inputZip.Close(); err != nil {
592 return err
593 }
594 }
595 }
596
597 if emulateJar {
Colin Cross7592d5a2023-07-18 15:57:09 -0700598 // Combine all the service files into a single list of combined service files and add them to the zip.
599 for _, serviceFile := range jarServices.ServiceFiles() {
600 _, err := out.addZipEntry(serviceFile.Name, ZipEntryFromBuffer{
601 fh: serviceFile.FileHeader,
602 content: serviceFile.Contents,
603 })
604 if err != nil {
605 return err
606 }
607 }
Sasha Smundak1459a922019-07-16 18:45:24 -0700608 return out.writeEntries(out.jarSorted())
609 } else if sortEntries {
610 return out.writeEntries(out.alphanumericSorted())
611 }
612 return nil
613}
614
615// Process command line
Colin Cross0cf45cd2017-10-04 17:04:16 -0700616type fileList []string
Nan Zhangd5998cc2017-09-13 13:17:43 -0700617
Colin Cross0cf45cd2017-10-04 17:04:16 -0700618func (f *fileList) String() string {
Nan Zhangd5998cc2017-09-13 13:17:43 -0700619 return `""`
620}
621
Colin Cross0cf45cd2017-10-04 17:04:16 -0700622func (f *fileList) Set(name string) error {
623 *f = append(*f, filepath.Clean(name))
Nan Zhang13f4cf52017-09-19 18:42:01 -0700624
625 return nil
626}
627
Colin Cross0cf45cd2017-10-04 17:04:16 -0700628type zipsToNotStripSet map[string]bool
Nan Zhang13f4cf52017-09-19 18:42:01 -0700629
Colin Cross0cf45cd2017-10-04 17:04:16 -0700630func (s zipsToNotStripSet) String() string {
Nan Zhang13f4cf52017-09-19 18:42:01 -0700631 return `""`
632}
633
Sasha Smundak1459a922019-07-16 18:45:24 -0700634func (s zipsToNotStripSet) Set(path string) error {
635 s[path] = true
Nan Zhangd5998cc2017-09-13 13:17:43 -0700636 return nil
637}
638
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700639var (
Colin Crosse909e1e2017-11-22 14:09:40 -0800640 sortEntries = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)")
641 emulateJar = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
Nan Zhang5925b0f2017-12-19 15:13:40 -0800642 emulatePar = flag.Bool("p", false, "merge zip entries based on par format")
Sasha Smundak1459a922019-07-16 18:45:24 -0700643 excludeDirs fileList
644 excludeFiles fileList
Colin Crosse909e1e2017-11-22 14:09:40 -0800645 zipsToNotStrip = make(zipsToNotStripSet)
646 stripDirEntries = flag.Bool("D", false, "strip directory entries from the output zip file")
647 manifest = flag.String("m", "", "manifest file to insert in jar")
Nan Zhang1db85402017-12-18 13:20:23 -0800648 pyMain = flag.String("pm", "", "__main__.py file to insert in par")
Dan Willemsen263dde72018-11-15 19:15:02 -0800649 prefix = flag.String("prefix", "", "A file to prefix to the zip file")
Colin Crosse909e1e2017-11-22 14:09:40 -0800650 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 -0700651)
652
Nan Zhangd5998cc2017-09-13 13:17:43 -0700653func init() {
Sasha Smundak1459a922019-07-16 18:45:24 -0700654 flag.Var(&excludeDirs, "stripDir", "directories to be excluded from the output zip, accepts wildcards")
655 flag.Var(&excludeFiles, "stripFile", "files to be excluded from the output zip, accepts wildcards")
Colin Cross0cf45cd2017-10-04 17:04:16 -0700656 flag.Var(&zipsToNotStrip, "zipToNotStrip", "the input zip file which is not applicable for stripping")
Nan Zhangd5998cc2017-09-13 13:17:43 -0700657}
658
Sasha Smundak1459a922019-07-16 18:45:24 -0700659type FileInputZip struct {
660 name string
661 reader *zip.ReadCloser
662}
663
664func (fiz *FileInputZip) Name() string {
665 return fiz.name
666}
667
668func (fiz *FileInputZip) Close() error {
669 if fiz.IsOpen() {
670 reader := fiz.reader
671 fiz.reader = nil
672 return reader.Close()
673 }
674 return nil
675}
676
677func (fiz *FileInputZip) Entries() []*zip.File {
678 if !fiz.IsOpen() {
679 panic(fmt.Errorf("%s: is not open", fiz.Name()))
680 }
681 return fiz.reader.File
682}
683
684func (fiz *FileInputZip) IsOpen() bool {
685 return fiz.reader != nil
686}
687
688func (fiz *FileInputZip) Open() error {
689 if fiz.IsOpen() {
690 return nil
691 }
692 var err error
Sasha Smundak61724912020-01-22 10:21:43 -0800693 if fiz.reader, err = zip.OpenReader(fiz.Name()); err != nil {
694 return fmt.Errorf("%s: %s", fiz.Name(), err.Error())
695 }
696 return nil
Sasha Smundak1459a922019-07-16 18:45:24 -0700697}
698
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700699func main() {
700 flag.Usage = func() {
Sasha Smundak1459a922019-07-16 18:45:24 -0700701 fmt.Fprintln(os.Stderr, "usage: merge_zips [-jpsD] [-m manifest] [--prefix script] [-pm __main__.py] OutputZip [inputs...]")
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700702 flag.PrintDefaults()
703 }
704
705 // parse args
706 flag.Parse()
707 args := flag.Args()
Colin Cross5c6ecc12017-10-23 18:12:27 -0700708 if len(args) < 1 {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700709 flag.Usage()
710 os.Exit(1)
711 }
712 outputPath := args[0]
Sasha Smundak1459a922019-07-16 18:45:24 -0700713 inputs := make([]string, 0)
714 for _, input := range args[1:] {
715 if input[0] == '@' {
Colin Crossfd708b52021-03-23 14:16:05 -0700716 f, err := os.Open(strings.TrimPrefix(input[1:], "@"))
Sasha Smundak1459a922019-07-16 18:45:24 -0700717 if err != nil {
718 log.Fatal(err)
719 }
Colin Crossfd708b52021-03-23 14:16:05 -0700720
721 rspInputs, err := response.ReadRspFile(f)
722 f.Close()
723 if err != nil {
724 log.Fatal(err)
725 }
726 inputs = append(inputs, rspInputs...)
727 } else {
728 inputs = append(inputs, input)
Sasha Smundak1459a922019-07-16 18:45:24 -0700729 }
Sasha Smundak1459a922019-07-16 18:45:24 -0700730 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700731
732 log.SetFlags(log.Lshortfile)
733
734 // make writer
Sasha Smundak1459a922019-07-16 18:45:24 -0700735 outputZip, err := os.Create(outputPath)
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700736 if err != nil {
737 log.Fatal(err)
738 }
Sasha Smundak1459a922019-07-16 18:45:24 -0700739 defer outputZip.Close()
Dan Willemsen263dde72018-11-15 19:15:02 -0800740
741 var offset int64
742 if *prefix != "" {
743 prefixFile, err := os.Open(*prefix)
744 if err != nil {
745 log.Fatal(err)
746 }
Sasha Smundak1459a922019-07-16 18:45:24 -0700747 offset, err = io.Copy(outputZip, prefixFile)
Dan Willemsen263dde72018-11-15 19:15:02 -0800748 if err != nil {
749 log.Fatal(err)
750 }
751 }
752
Sasha Smundak1459a922019-07-16 18:45:24 -0700753 writer := zip.NewWriter(outputZip)
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700754 defer func() {
755 err := writer.Close()
756 if err != nil {
757 log.Fatal(err)
758 }
759 }()
Dan Willemsen263dde72018-11-15 19:15:02 -0800760 writer.SetOffset(offset)
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700761
Colin Cross635acc92017-09-12 22:50:46 -0700762 if *manifest != "" && !*emulateJar {
763 log.Fatal(errors.New("must specify -j when specifying a manifest via -m"))
764 }
765
Nan Zhang1db85402017-12-18 13:20:23 -0800766 if *pyMain != "" && !*emulatePar {
767 log.Fatal(errors.New("must specify -p when specifying a Python __main__.py via -pm"))
768 }
769
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700770 // do merge
Sasha Smundak1459a922019-07-16 18:45:24 -0700771 inputZipsManager := NewInputZipsManager(len(inputs), 1000)
772 inputZips := make([]InputZip, len(inputs))
773 for i, input := range inputs {
774 inputZips[i] = inputZipsManager.Manage(&FileInputZip{name: input})
775 }
776 err = mergeZips(inputZips, writer, *manifest, *pyMain, *sortEntries, *emulateJar, *emulatePar,
777 *stripDirEntries, *ignoreDuplicates, []string(excludeFiles), []string(excludeDirs),
778 map[string]bool(zipsToNotStrip))
Colin Cross635acc92017-09-12 22:50:46 -0700779 if err != nil {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700780 log.Fatal(err)
781 }
782}