blob: 2c5718063d55d36f417abc1d5fac5b91e1e9b438 [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 }
Colin Crossfa24df62023-11-01 11:18:45 -070099 entry := ze.inputZip.Entries()[ze.index]
100 entry.SetModTime(jar.DefaultTime)
101 return zw.CopyFrom(entry, dest)
Sasha Smundak1459a922019-07-16 18:45:24 -0700102}
103
104// a ZipEntryFromBuffer is a ZipEntryContents that pulls its content from a []byte
105type ZipEntryFromBuffer struct {
106 fh *zip.FileHeader
107 content []byte
108}
109
110func (be ZipEntryFromBuffer) String() string {
111 return "internal buffer"
112}
113
114func (be ZipEntryFromBuffer) IsDir() bool {
115 return be.fh.FileInfo().IsDir()
116}
117
118func (be ZipEntryFromBuffer) CRC32() uint32 {
119 return crc32.ChecksumIEEE(be.content)
120}
121
122func (be ZipEntryFromBuffer) Size() uint64 {
123 return uint64(len(be.content))
124}
125
126func (be ZipEntryFromBuffer) WriteToZip(dest string, zw *zip.Writer) error {
Colin Cross7592d5a2023-07-18 15:57:09 -0700127 w, err := zw.CreateHeaderAndroid(be.fh)
Sasha Smundak1459a922019-07-16 18:45:24 -0700128 if err != nil {
129 return err
130 }
131
132 if !be.IsDir() {
133 _, err = w.Write(be.content)
134 if err != nil {
135 return err
136 }
137 }
138
139 return nil
140}
141
142// Processing state.
143type OutputZip struct {
144 outputWriter *zip.Writer
145 stripDirEntries bool
146 emulateJar bool
147 sortEntries bool
148 ignoreDuplicates bool
149 excludeDirs []string
150 excludeFiles []string
151 sourceByDest map[string]ZipEntryContents
152}
153
154func NewOutputZip(outputWriter *zip.Writer, sortEntries, emulateJar, stripDirEntries, ignoreDuplicates bool) *OutputZip {
155 return &OutputZip{
156 outputWriter: outputWriter,
157 stripDirEntries: stripDirEntries,
158 emulateJar: emulateJar,
159 sortEntries: sortEntries,
160 sourceByDest: make(map[string]ZipEntryContents, 0),
161 ignoreDuplicates: ignoreDuplicates,
162 }
163}
164
165func (oz *OutputZip) setExcludeDirs(excludeDirs []string) {
166 oz.excludeDirs = make([]string, len(excludeDirs))
167 for i, dir := range excludeDirs {
168 oz.excludeDirs[i] = filepath.Clean(dir)
169 }
170}
171
172func (oz *OutputZip) setExcludeFiles(excludeFiles []string) {
173 oz.excludeFiles = excludeFiles
174}
175
176// Adds an entry with given name whose source is given ZipEntryContents. Returns old ZipEntryContents
177// if entry with given name already exists.
178func (oz *OutputZip) addZipEntry(name string, source ZipEntryContents) (ZipEntryContents, error) {
179 if existingSource, exists := oz.sourceByDest[name]; exists {
180 return existingSource, nil
181 }
182 oz.sourceByDest[name] = source
183 // Delay writing an entry if entries need to be rearranged.
184 if oz.emulateJar || oz.sortEntries {
185 return nil, nil
186 }
187 return nil, source.WriteToZip(name, oz.outputWriter)
188}
189
190// Adds an entry for the manifest (META-INF/MANIFEST.MF from the given file
191func (oz *OutputZip) addManifest(manifestPath string) error {
192 if !oz.stripDirEntries {
193 if _, err := oz.addZipEntry(jar.MetaDir, ZipEntryFromBuffer{jar.MetaDirFileHeader(), nil}); err != nil {
194 return err
195 }
196 }
197 contents, err := ioutil.ReadFile(manifestPath)
198 if err == nil {
199 fh, buf, err := jar.ManifestFileContents(contents)
200 if err == nil {
201 _, err = oz.addZipEntry(jar.ManifestFile, ZipEntryFromBuffer{fh, buf})
202 }
203 }
204 return err
205}
206
207// Adds an entry with given name and contents read from given file
208func (oz *OutputZip) addZipEntryFromFile(name string, path string) error {
209 buf, err := ioutil.ReadFile(path)
210 if err == nil {
211 fh := &zip.FileHeader{
212 Name: name,
213 Method: zip.Store,
214 UncompressedSize64: uint64(len(buf)),
215 }
216 fh.SetMode(0700)
217 fh.SetModTime(jar.DefaultTime)
218 _, err = oz.addZipEntry(name, ZipEntryFromBuffer{fh, buf})
219 }
220 return err
221}
222
223func (oz *OutputZip) addEmptyEntry(entry string) error {
224 var emptyBuf []byte
225 fh := &zip.FileHeader{
226 Name: entry,
227 Method: zip.Store,
228 UncompressedSize64: uint64(len(emptyBuf)),
229 }
230 fh.SetMode(0700)
231 fh.SetModTime(jar.DefaultTime)
232 _, err := oz.addZipEntry(entry, ZipEntryFromBuffer{fh, emptyBuf})
233 return err
234}
235
236// Returns true if given entry is to be excluded
237func (oz *OutputZip) isEntryExcluded(name string) bool {
238 for _, dir := range oz.excludeDirs {
239 dir = filepath.Clean(dir)
240 patterns := []string{
241 dir + "/", // the directory itself
242 dir + "/**/*", // files recursively in the directory
243 dir + "/**/*/", // directories recursively in the directory
244 }
245
246 for _, pattern := range patterns {
247 match, err := pathtools.Match(pattern, name)
248 if err != nil {
249 panic(fmt.Errorf("%s: %s", err.Error(), pattern))
250 }
251 if match {
252 if oz.emulateJar {
253 // When merging jar files, don't strip META-INF/MANIFEST.MF even if stripping META-INF is
254 // requested.
255 // TODO(ccross): which files does this affect?
256 if name != jar.MetaDir && name != jar.ManifestFile {
257 return true
258 }
259 }
260 return true
261 }
262 }
263 }
264
265 for _, pattern := range oz.excludeFiles {
266 match, err := pathtools.Match(pattern, name)
267 if err != nil {
268 panic(fmt.Errorf("%s: %s", err.Error(), pattern))
269 }
270 if match {
271 return true
272 }
273 }
274 return false
275}
276
277// Creates a zip entry whose contents is an entry from the given input zip.
278func (oz *OutputZip) copyEntry(inputZip InputZip, index int) error {
279 entry := NewZipEntryFromZip(inputZip, index)
280 if oz.stripDirEntries && entry.IsDir() {
281 return nil
282 }
283 existingEntry, err := oz.addZipEntry(entry.name, entry)
284 if err != nil {
285 return err
286 }
287 if existingEntry == nil {
288 return nil
289 }
290
291 // File types should match
292 if existingEntry.IsDir() != entry.IsDir() {
293 return fmt.Errorf("Directory/file mismatch at %v from %v and %v\n",
294 entry.name, existingEntry, entry)
295 }
296
297 if oz.ignoreDuplicates ||
298 // Skip manifest and module info files that are not from the first input file
299 (oz.emulateJar && entry.name == jar.ManifestFile || entry.name == jar.ModuleInfoClass) ||
300 // Identical entries
301 (existingEntry.CRC32() == entry.CRC32() && existingEntry.Size() == entry.Size()) ||
302 // Directory entries
303 entry.IsDir() {
304 return nil
305 }
306
307 return fmt.Errorf("Duplicate path %v found in %v and %v\n", entry.name, existingEntry, inputZip.Name())
308}
309
310func (oz *OutputZip) entriesArray() []string {
311 entries := make([]string, len(oz.sourceByDest))
312 i := 0
313 for entry := range oz.sourceByDest {
314 entries[i] = entry
315 i++
316 }
317 return entries
318}
319
320func (oz *OutputZip) jarSorted() []string {
321 entries := oz.entriesArray()
322 sort.SliceStable(entries, func(i, j int) bool { return jar.EntryNamesLess(entries[i], entries[j]) })
323 return entries
324}
325
326func (oz *OutputZip) alphanumericSorted() []string {
327 entries := oz.entriesArray()
328 sort.Strings(entries)
329 return entries
330}
331
332func (oz *OutputZip) writeEntries(entries []string) error {
333 for _, entry := range entries {
334 source, _ := oz.sourceByDest[entry]
335 if err := source.WriteToZip(entry, oz.outputWriter); err != nil {
336 return err
337 }
338 }
339 return nil
340}
341
342func (oz *OutputZip) getUninitializedPythonPackages(inputZips []InputZip) ([]string, error) {
343 // the runfiles packages needs to be populated with "__init__.py".
344 // the runfiles dirs have been treated as packages.
Anas Sulaiman437e9472024-02-09 21:06:49 +0000345 var allPackages []string // Using a slice to preserve input order.
346 seenPkgs := make(map[string]bool)
Sasha Smundak1459a922019-07-16 18:45:24 -0700347 initedPackages := make(map[string]bool)
348 getPackage := func(path string) string {
349 ret := filepath.Dir(path)
350 // filepath.Dir("abc") -> "." and filepath.Dir("/abc") -> "/".
351 if ret == "." || ret == "/" {
352 return ""
353 }
354 return ret
355 }
356
357 // put existing __init__.py files to a set first. This set is used for preventing
358 // generated __init__.py files from overwriting existing ones.
359 for _, inputZip := range inputZips {
360 if err := inputZip.Open(); err != nil {
361 return nil, err
362 }
363 for _, file := range inputZip.Entries() {
364 pyPkg := getPackage(file.Name)
Cole Faust5c503d12023-01-24 11:48:08 -0800365 baseName := filepath.Base(file.Name)
366 if baseName == "__init__.py" || baseName == "__init__.pyc" {
Sasha Smundak1459a922019-07-16 18:45:24 -0700367 if _, found := initedPackages[pyPkg]; found {
368 panic(fmt.Errorf("found __init__.py path duplicates during pars merging: %q", file.Name))
369 }
370 initedPackages[pyPkg] = true
371 }
372 for pyPkg != "" {
Anas Sulaiman437e9472024-02-09 21:06:49 +0000373 if _, found := seenPkgs[pyPkg]; found {
Sasha Smundak1459a922019-07-16 18:45:24 -0700374 break
375 }
Anas Sulaiman437e9472024-02-09 21:06:49 +0000376 seenPkgs[pyPkg] = true
377 allPackages = append(allPackages, pyPkg)
Sasha Smundak1459a922019-07-16 18:45:24 -0700378 pyPkg = getPackage(pyPkg)
379 }
380 }
381 }
382 noInitPackages := make([]string, 0)
Anas Sulaiman437e9472024-02-09 21:06:49 +0000383 for _, pyPkg := range allPackages {
Sasha Smundak1459a922019-07-16 18:45:24 -0700384 if _, found := initedPackages[pyPkg]; !found {
385 noInitPackages = append(noInitPackages, pyPkg)
386 }
387 }
388 return noInitPackages, nil
389}
390
391// An InputZip owned by the InputZipsManager. Opened ManagedInputZip's are chained in the open order.
392type ManagedInputZip struct {
393 owner *InputZipsManager
394 realInputZip InputZip
395 older *ManagedInputZip
396 newer *ManagedInputZip
397}
398
399// Maintains the array of ManagedInputZips, keeping track of open input ones. When an InputZip is opened,
400// may close some other InputZip to limit the number of open ones.
401type InputZipsManager struct {
402 inputZips []*ManagedInputZip
403 nOpenZips int
404 maxOpenZips int
405 openInputZips *ManagedInputZip
406}
407
408func (miz *ManagedInputZip) unlink() {
409 olderMiz := miz.older
410 newerMiz := miz.newer
411 if newerMiz.older != miz || olderMiz.newer != miz {
412 panic(fmt.Errorf("removing %p:%#v: broken list between %p:%#v and %p:%#v",
413 miz, miz, newerMiz, newerMiz, olderMiz, olderMiz))
414 }
415 olderMiz.newer = newerMiz
416 newerMiz.older = olderMiz
417 miz.newer = nil
418 miz.older = nil
419}
420
421func (miz *ManagedInputZip) link(olderMiz *ManagedInputZip) {
422 if olderMiz.newer != nil || olderMiz.older != nil {
423 panic(fmt.Errorf("inputZip is already open"))
424 }
425 oldOlderMiz := miz.older
426 if oldOlderMiz.newer != miz {
Colin Crossb5f6dc42019-09-11 09:48:37 -0700427 panic(fmt.Errorf("broken list between %p:%#v and %p:%#v", miz, miz, oldOlderMiz, oldOlderMiz))
Sasha Smundak1459a922019-07-16 18:45:24 -0700428 }
429 miz.older = olderMiz
430 olderMiz.older = oldOlderMiz
431 oldOlderMiz.newer = olderMiz
432 olderMiz.newer = miz
433}
434
435func NewInputZipsManager(nInputZips, maxOpenZips int) *InputZipsManager {
436 if maxOpenZips < 3 {
437 panic(fmt.Errorf("open zips limit should be above 3"))
438 }
Patrice Arruda8da724a2020-07-28 21:22:12 +0000439 // 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 -0700440 head := new(ManagedInputZip)
441 head.older = head
442 head.newer = head
443 return &InputZipsManager{
444 inputZips: make([]*ManagedInputZip, 0, nInputZips),
445 maxOpenZips: maxOpenZips,
446 openInputZips: head,
447 }
448}
449
450// InputZip factory
451func (izm *InputZipsManager) Manage(inz InputZip) InputZip {
452 iz := &ManagedInputZip{owner: izm, realInputZip: inz}
453 izm.inputZips = append(izm.inputZips, iz)
454 return iz
455}
456
457// Opens or reopens ManagedInputZip.
458func (izm *InputZipsManager) reopen(miz *ManagedInputZip) error {
459 if miz.realInputZip.IsOpen() {
460 if miz != izm.openInputZips {
461 miz.unlink()
462 izm.openInputZips.link(miz)
463 }
464 return nil
465 }
466 if izm.nOpenZips >= izm.maxOpenZips {
467 if err := izm.close(izm.openInputZips.older); err != nil {
468 return err
469 }
470 }
471 if err := miz.realInputZip.Open(); err != nil {
472 return err
473 }
474 izm.openInputZips.link(miz)
475 izm.nOpenZips++
476 return nil
477}
478
479func (izm *InputZipsManager) close(miz *ManagedInputZip) error {
480 if miz.IsOpen() {
481 err := miz.realInputZip.Close()
482 izm.nOpenZips--
483 miz.unlink()
484 return err
485 }
486 return nil
487}
488
489// Checks that openInputZips deque is valid
490func (izm *InputZipsManager) checkOpenZipsDeque() {
491 nReallyOpen := 0
492 el := izm.openInputZips
493 for {
494 elNext := el.older
495 if elNext.newer != el {
496 panic(fmt.Errorf("Element:\n %p: %v\nNext:\n %p %v", el, el, elNext, elNext))
497 }
498 if elNext == izm.openInputZips {
499 break
500 }
501 el = elNext
502 if !el.IsOpen() {
503 panic(fmt.Errorf("Found unopened element"))
504 }
505 nReallyOpen++
506 if nReallyOpen > izm.nOpenZips {
507 panic(fmt.Errorf("found %d open zips, should be %d", nReallyOpen, izm.nOpenZips))
508 }
509 }
510 if nReallyOpen > izm.nOpenZips {
511 panic(fmt.Errorf("found %d open zips, should be %d", nReallyOpen, izm.nOpenZips))
512 }
513}
514
515func (miz *ManagedInputZip) Name() string {
516 return miz.realInputZip.Name()
517}
518
519func (miz *ManagedInputZip) Open() error {
520 return miz.owner.reopen(miz)
521}
522
523func (miz *ManagedInputZip) Close() error {
524 return miz.owner.close(miz)
525}
526
527func (miz *ManagedInputZip) IsOpen() bool {
528 return miz.realInputZip.IsOpen()
529}
530
531func (miz *ManagedInputZip) Entries() []*zip.File {
532 if !miz.IsOpen() {
533 panic(fmt.Errorf("%s: is not open", miz.Name()))
534 }
535 return miz.realInputZip.Entries()
536}
537
538// Actual processing.
539func mergeZips(inputZips []InputZip, writer *zip.Writer, manifest, pyMain string,
540 sortEntries, emulateJar, emulatePar, stripDirEntries, ignoreDuplicates bool,
541 excludeFiles, excludeDirs []string, zipsToNotStrip map[string]bool) error {
542
543 out := NewOutputZip(writer, sortEntries, emulateJar, stripDirEntries, ignoreDuplicates)
544 out.setExcludeFiles(excludeFiles)
545 out.setExcludeDirs(excludeDirs)
546 if manifest != "" {
547 if err := out.addManifest(manifest); err != nil {
548 return err
549 }
550 }
551 if pyMain != "" {
552 if err := out.addZipEntryFromFile("__main__.py", pyMain); err != nil {
553 return err
554 }
555 }
556
557 if emulatePar {
558 noInitPackages, err := out.getUninitializedPythonPackages(inputZips)
559 if err != nil {
560 return err
561 }
562 for _, uninitializedPyPackage := range noInitPackages {
563 if err = out.addEmptyEntry(filepath.Join(uninitializedPyPackage, "__init__.py")); err != nil {
564 return err
565 }
566 }
567 }
568
Colin Cross7592d5a2023-07-18 15:57:09 -0700569 var jarServices jar.Services
570
Sasha Smundak1459a922019-07-16 18:45:24 -0700571 // Finally, add entries from all the input zips.
572 for _, inputZip := range inputZips {
573 _, copyFully := zipsToNotStrip[inputZip.Name()]
574 if err := inputZip.Open(); err != nil {
575 return err
576 }
577
578 for i, entry := range inputZip.Entries() {
Colin Cross7592d5a2023-07-18 15:57:09 -0700579 if emulateJar && jarServices.IsServiceFile(entry) {
580 // If this is a jar, collect service files to combine instead of adding them to the zip.
581 err := jarServices.AddServiceFile(entry)
582 if err != nil {
583 return err
584 }
585 continue
586 }
Sasha Smundak1459a922019-07-16 18:45:24 -0700587 if copyFully || !out.isEntryExcluded(entry.Name) {
588 if err := out.copyEntry(inputZip, i); err != nil {
589 return err
590 }
591 }
592 }
593 // Unless we need to rearrange the entries, the input zip can now be closed.
594 if !(emulateJar || sortEntries) {
595 if err := inputZip.Close(); err != nil {
596 return err
597 }
598 }
599 }
600
601 if emulateJar {
Colin Cross7592d5a2023-07-18 15:57:09 -0700602 // Combine all the service files into a single list of combined service files and add them to the zip.
603 for _, serviceFile := range jarServices.ServiceFiles() {
604 _, err := out.addZipEntry(serviceFile.Name, ZipEntryFromBuffer{
605 fh: serviceFile.FileHeader,
606 content: serviceFile.Contents,
607 })
608 if err != nil {
609 return err
610 }
611 }
Sasha Smundak1459a922019-07-16 18:45:24 -0700612 return out.writeEntries(out.jarSorted())
613 } else if sortEntries {
614 return out.writeEntries(out.alphanumericSorted())
615 }
616 return nil
617}
618
619// Process command line
Colin Cross0cf45cd2017-10-04 17:04:16 -0700620type fileList []string
Nan Zhangd5998cc2017-09-13 13:17:43 -0700621
Colin Cross0cf45cd2017-10-04 17:04:16 -0700622func (f *fileList) String() string {
Nan Zhangd5998cc2017-09-13 13:17:43 -0700623 return `""`
624}
625
Colin Cross0cf45cd2017-10-04 17:04:16 -0700626func (f *fileList) Set(name string) error {
627 *f = append(*f, filepath.Clean(name))
Nan Zhang13f4cf52017-09-19 18:42:01 -0700628
629 return nil
630}
631
Colin Cross0cf45cd2017-10-04 17:04:16 -0700632type zipsToNotStripSet map[string]bool
Nan Zhang13f4cf52017-09-19 18:42:01 -0700633
Colin Cross0cf45cd2017-10-04 17:04:16 -0700634func (s zipsToNotStripSet) String() string {
Nan Zhang13f4cf52017-09-19 18:42:01 -0700635 return `""`
636}
637
Sasha Smundak1459a922019-07-16 18:45:24 -0700638func (s zipsToNotStripSet) Set(path string) error {
639 s[path] = true
Nan Zhangd5998cc2017-09-13 13:17:43 -0700640 return nil
641}
642
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700643var (
Colin Crosse909e1e2017-11-22 14:09:40 -0800644 sortEntries = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)")
645 emulateJar = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
Nan Zhang5925b0f2017-12-19 15:13:40 -0800646 emulatePar = flag.Bool("p", false, "merge zip entries based on par format")
Sasha Smundak1459a922019-07-16 18:45:24 -0700647 excludeDirs fileList
648 excludeFiles fileList
Colin Crosse909e1e2017-11-22 14:09:40 -0800649 zipsToNotStrip = make(zipsToNotStripSet)
650 stripDirEntries = flag.Bool("D", false, "strip directory entries from the output zip file")
651 manifest = flag.String("m", "", "manifest file to insert in jar")
Nan Zhang1db85402017-12-18 13:20:23 -0800652 pyMain = flag.String("pm", "", "__main__.py file to insert in par")
Dan Willemsen263dde72018-11-15 19:15:02 -0800653 prefix = flag.String("prefix", "", "A file to prefix to the zip file")
Colin Crosse909e1e2017-11-22 14:09:40 -0800654 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 -0700655)
656
Nan Zhangd5998cc2017-09-13 13:17:43 -0700657func init() {
Sasha Smundak1459a922019-07-16 18:45:24 -0700658 flag.Var(&excludeDirs, "stripDir", "directories to be excluded from the output zip, accepts wildcards")
659 flag.Var(&excludeFiles, "stripFile", "files to be excluded from the output zip, accepts wildcards")
Colin Cross0cf45cd2017-10-04 17:04:16 -0700660 flag.Var(&zipsToNotStrip, "zipToNotStrip", "the input zip file which is not applicable for stripping")
Nan Zhangd5998cc2017-09-13 13:17:43 -0700661}
662
Sasha Smundak1459a922019-07-16 18:45:24 -0700663type FileInputZip struct {
664 name string
665 reader *zip.ReadCloser
666}
667
668func (fiz *FileInputZip) Name() string {
669 return fiz.name
670}
671
672func (fiz *FileInputZip) Close() error {
673 if fiz.IsOpen() {
674 reader := fiz.reader
675 fiz.reader = nil
676 return reader.Close()
677 }
678 return nil
679}
680
681func (fiz *FileInputZip) Entries() []*zip.File {
682 if !fiz.IsOpen() {
683 panic(fmt.Errorf("%s: is not open", fiz.Name()))
684 }
685 return fiz.reader.File
686}
687
688func (fiz *FileInputZip) IsOpen() bool {
689 return fiz.reader != nil
690}
691
692func (fiz *FileInputZip) Open() error {
693 if fiz.IsOpen() {
694 return nil
695 }
696 var err error
Sasha Smundak61724912020-01-22 10:21:43 -0800697 if fiz.reader, err = zip.OpenReader(fiz.Name()); err != nil {
698 return fmt.Errorf("%s: %s", fiz.Name(), err.Error())
699 }
700 return nil
Sasha Smundak1459a922019-07-16 18:45:24 -0700701}
702
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700703func main() {
704 flag.Usage = func() {
Sasha Smundak1459a922019-07-16 18:45:24 -0700705 fmt.Fprintln(os.Stderr, "usage: merge_zips [-jpsD] [-m manifest] [--prefix script] [-pm __main__.py] OutputZip [inputs...]")
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700706 flag.PrintDefaults()
707 }
708
709 // parse args
710 flag.Parse()
711 args := flag.Args()
Colin Cross5c6ecc12017-10-23 18:12:27 -0700712 if len(args) < 1 {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700713 flag.Usage()
714 os.Exit(1)
715 }
716 outputPath := args[0]
Sasha Smundak1459a922019-07-16 18:45:24 -0700717 inputs := make([]string, 0)
718 for _, input := range args[1:] {
719 if input[0] == '@' {
Colin Crossfd708b52021-03-23 14:16:05 -0700720 f, err := os.Open(strings.TrimPrefix(input[1:], "@"))
Sasha Smundak1459a922019-07-16 18:45:24 -0700721 if err != nil {
722 log.Fatal(err)
723 }
Colin Crossfd708b52021-03-23 14:16:05 -0700724
725 rspInputs, err := response.ReadRspFile(f)
726 f.Close()
727 if err != nil {
728 log.Fatal(err)
729 }
730 inputs = append(inputs, rspInputs...)
731 } else {
732 inputs = append(inputs, input)
Sasha Smundak1459a922019-07-16 18:45:24 -0700733 }
Sasha Smundak1459a922019-07-16 18:45:24 -0700734 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700735
736 log.SetFlags(log.Lshortfile)
737
738 // make writer
Sasha Smundak1459a922019-07-16 18:45:24 -0700739 outputZip, err := os.Create(outputPath)
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700740 if err != nil {
741 log.Fatal(err)
742 }
Sasha Smundak1459a922019-07-16 18:45:24 -0700743 defer outputZip.Close()
Dan Willemsen263dde72018-11-15 19:15:02 -0800744
745 var offset int64
746 if *prefix != "" {
747 prefixFile, err := os.Open(*prefix)
748 if err != nil {
749 log.Fatal(err)
750 }
Sasha Smundak1459a922019-07-16 18:45:24 -0700751 offset, err = io.Copy(outputZip, prefixFile)
Dan Willemsen263dde72018-11-15 19:15:02 -0800752 if err != nil {
753 log.Fatal(err)
754 }
755 }
756
Sasha Smundak1459a922019-07-16 18:45:24 -0700757 writer := zip.NewWriter(outputZip)
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700758 defer func() {
759 err := writer.Close()
760 if err != nil {
761 log.Fatal(err)
762 }
763 }()
Dan Willemsen263dde72018-11-15 19:15:02 -0800764 writer.SetOffset(offset)
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700765
Colin Cross635acc92017-09-12 22:50:46 -0700766 if *manifest != "" && !*emulateJar {
767 log.Fatal(errors.New("must specify -j when specifying a manifest via -m"))
768 }
769
Nan Zhang1db85402017-12-18 13:20:23 -0800770 if *pyMain != "" && !*emulatePar {
771 log.Fatal(errors.New("must specify -p when specifying a Python __main__.py via -pm"))
772 }
773
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700774 // do merge
Sasha Smundak1459a922019-07-16 18:45:24 -0700775 inputZipsManager := NewInputZipsManager(len(inputs), 1000)
776 inputZips := make([]InputZip, len(inputs))
777 for i, input := range inputs {
778 inputZips[i] = inputZipsManager.Manage(&FileInputZip{name: input})
779 }
780 err = mergeZips(inputZips, writer, *manifest, *pyMain, *sortEntries, *emulateJar, *emulatePar,
781 *stripDirEntries, *ignoreDuplicates, []string(excludeFiles), []string(excludeDirs),
782 map[string]bool(zipsToNotStrip))
Colin Cross635acc92017-09-12 22:50:46 -0700783 if err != nil {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700784 log.Fatal(err)
785 }
786}