1// 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 "errors" 19 "flag" 20 "fmt" 21 "hash/crc32" 22 "io" 23 "io/ioutil" 24 "log" 25 "os" 26 "path/filepath" 27 "sort" 28 "strings" 29 30 "android/soong/response" 31 32 "github.com/google/blueprint/pathtools" 33 34 "android/soong/jar" 35 "android/soong/third_party/zip" 36) 37 38// 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 entry := ze.inputZip.Entries()[ze.index] 100 entry.SetModTime(jar.DefaultTime) 101 return zw.CopyFrom(entry, dest) 102} 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 { 127 w, err := zw.CreateHeaderAndroid(be.fh) 128 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. 345 var allPackages []string // Using a slice to preserve input order. 346 seenPkgs := make(map[string]bool) 347 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) 365 baseName := filepath.Base(file.Name) 366 if baseName == "__init__.py" || baseName == "__init__.pyc" { 367 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 != "" { 373 if _, found := seenPkgs[pyPkg]; found { 374 break 375 } 376 seenPkgs[pyPkg] = true 377 allPackages = append(allPackages, pyPkg) 378 pyPkg = getPackage(pyPkg) 379 } 380 } 381 } 382 noInitPackages := make([]string, 0) 383 for _, pyPkg := range allPackages { 384 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 { 427 panic(fmt.Errorf("broken list between %p:%#v and %p:%#v", miz, miz, oldOlderMiz, oldOlderMiz)) 428 } 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 } 439 // In the fake element .older points to the most recently opened InputZip, and .newer points to the oldest. 440 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 569 var jarServices jar.Services 570 571 // 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() { 579 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 } 587 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 { 602 // 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 } 612 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 620type fileList []string 621 622func (f *fileList) String() string { 623 return `""` 624} 625 626func (f *fileList) Set(name string) error { 627 *f = append(*f, filepath.Clean(name)) 628 629 return nil 630} 631 632type zipsToNotStripSet map[string]bool 633 634func (s zipsToNotStripSet) String() string { 635 return `""` 636} 637 638func (s zipsToNotStripSet) Set(path string) error { 639 s[path] = true 640 return nil 641} 642 643var ( 644 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)") 646 emulatePar = flag.Bool("p", false, "merge zip entries based on par format") 647 excludeDirs fileList 648 excludeFiles fileList 649 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") 652 pyMain = flag.String("pm", "", "__main__.py file to insert in par") 653 prefix = flag.String("prefix", "", "A file to prefix to the zip file") 654 ignoreDuplicates = flag.Bool("ignore-duplicates", false, "take each entry from the first zip it exists in and don't warn") 655) 656 657func init() { 658 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") 660 flag.Var(&zipsToNotStrip, "zipToNotStrip", "the input zip file which is not applicable for stripping") 661} 662 663type 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 697 if fiz.reader, err = zip.OpenReader(fiz.Name()); err != nil { 698 return fmt.Errorf("%s: %s", fiz.Name(), err.Error()) 699 } 700 return nil 701} 702 703func main() { 704 flag.Usage = func() { 705 fmt.Fprintln(os.Stderr, "usage: merge_zips [-jpsD] [-m manifest] [--prefix script] [-pm __main__.py] OutputZip [inputs...]") 706 flag.PrintDefaults() 707 } 708 709 // parse args 710 flag.Parse() 711 args := flag.Args() 712 if len(args) < 1 { 713 flag.Usage() 714 os.Exit(1) 715 } 716 outputPath := args[0] 717 inputs := make([]string, 0) 718 for _, input := range args[1:] { 719 if input[0] == '@' { 720 f, err := os.Open(strings.TrimPrefix(input[1:], "@")) 721 if err != nil { 722 log.Fatal(err) 723 } 724 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) 733 } 734 } 735 736 log.SetFlags(log.Lshortfile) 737 738 // make writer 739 outputZip, err := os.Create(outputPath) 740 if err != nil { 741 log.Fatal(err) 742 } 743 defer outputZip.Close() 744 745 var offset int64 746 if *prefix != "" { 747 prefixFile, err := os.Open(*prefix) 748 if err != nil { 749 log.Fatal(err) 750 } 751 offset, err = io.Copy(outputZip, prefixFile) 752 if err != nil { 753 log.Fatal(err) 754 } 755 } 756 757 writer := zip.NewWriter(outputZip) 758 defer func() { 759 err := writer.Close() 760 if err != nil { 761 log.Fatal(err) 762 } 763 }() 764 writer.SetOffset(offset) 765 766 if *manifest != "" && !*emulateJar { 767 log.Fatal(errors.New("must specify -j when specifying a manifest via -m")) 768 } 769 770 if *pyMain != "" && !*emulatePar { 771 log.Fatal(errors.New("must specify -p when specifying a Python __main__.py via -pm")) 772 } 773 774 // do merge 775 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)) 783 if err != nil { 784 log.Fatal(err) 785 } 786} 787