| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 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 |  | 
|  | 15 | package main | 
|  | 16 |  | 
|  | 17 | import ( | 
| Colin Cross | 635acc9 | 2017-09-12 22:50:46 -0700 | [diff] [blame] | 18 | "errors" | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 19 | "flag" | 
|  | 20 | "fmt" | 
| Colin Cross | 635acc9 | 2017-09-12 22:50:46 -0700 | [diff] [blame] | 21 | "hash/crc32" | 
| Dan Willemsen | 263dde7 | 2018-11-15 19:15:02 -0800 | [diff] [blame] | 22 | "io" | 
| Nan Zhang | 5925b0f | 2017-12-19 15:13:40 -0800 | [diff] [blame] | 23 | "io/ioutil" | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 24 | "log" | 
|  | 25 | "os" | 
| Nan Zhang | 13f4cf5 | 2017-09-19 18:42:01 -0700 | [diff] [blame] | 26 | "path/filepath" | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 27 | "sort" | 
| Colin Cross | fd708b5 | 2021-03-23 14:16:05 -0700 | [diff] [blame] | 28 | "strings" | 
|  | 29 |  | 
|  | 30 | "android/soong/response" | 
| Colin Cross | 4c03f68 | 2018-07-15 08:16:31 -0700 | [diff] [blame] | 31 |  | 
|  | 32 | "github.com/google/blueprint/pathtools" | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 33 |  | 
|  | 34 | "android/soong/jar" | 
|  | 35 | "android/soong/third_party/zip" | 
|  | 36 | ) | 
|  | 37 |  | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 38 | // Input zip: we can open it, close it, and obtain an array of entries | 
|  | 39 | type 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 | 
|  | 48 | type 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 | 
|  | 58 | type ZipEntryFromZip struct { | 
|  | 59 | inputZip InputZip | 
|  | 60 | index    int | 
|  | 61 | name     string | 
|  | 62 | isDir    bool | 
|  | 63 | crc32    uint32 | 
|  | 64 | size     uint64 | 
|  | 65 | } | 
|  | 66 |  | 
|  | 67 | func 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 |  | 
|  | 79 | func (ze ZipEntryFromZip) String() string { | 
|  | 80 | return fmt.Sprintf("%s!%s", ze.inputZip.Name(), ze.name) | 
|  | 81 | } | 
|  | 82 |  | 
|  | 83 | func (ze ZipEntryFromZip) IsDir() bool { | 
|  | 84 | return ze.isDir | 
|  | 85 | } | 
|  | 86 |  | 
|  | 87 | func (ze ZipEntryFromZip) CRC32() uint32 { | 
|  | 88 | return ze.crc32 | 
|  | 89 | } | 
|  | 90 |  | 
|  | 91 | func (ze ZipEntryFromZip) Size() uint64 { | 
|  | 92 | return ze.size | 
|  | 93 | } | 
|  | 94 |  | 
|  | 95 | func (ze ZipEntryFromZip) WriteToZip(dest string, zw *zip.Writer) error { | 
|  | 96 | if err := ze.inputZip.Open(); err != nil { | 
|  | 97 | return err | 
|  | 98 | } | 
| Colin Cross | fa24df6 | 2023-11-01 11:18:45 -0700 | [diff] [blame] | 99 | entry := ze.inputZip.Entries()[ze.index] | 
|  | 100 | entry.SetModTime(jar.DefaultTime) | 
|  | 101 | return zw.CopyFrom(entry, dest) | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 102 | } | 
|  | 103 |  | 
|  | 104 | // a ZipEntryFromBuffer is a ZipEntryContents that pulls its content from a []byte | 
|  | 105 | type ZipEntryFromBuffer struct { | 
|  | 106 | fh      *zip.FileHeader | 
|  | 107 | content []byte | 
|  | 108 | } | 
|  | 109 |  | 
|  | 110 | func (be ZipEntryFromBuffer) String() string { | 
|  | 111 | return "internal buffer" | 
|  | 112 | } | 
|  | 113 |  | 
|  | 114 | func (be ZipEntryFromBuffer) IsDir() bool { | 
|  | 115 | return be.fh.FileInfo().IsDir() | 
|  | 116 | } | 
|  | 117 |  | 
|  | 118 | func (be ZipEntryFromBuffer) CRC32() uint32 { | 
|  | 119 | return crc32.ChecksumIEEE(be.content) | 
|  | 120 | } | 
|  | 121 |  | 
|  | 122 | func (be ZipEntryFromBuffer) Size() uint64 { | 
|  | 123 | return uint64(len(be.content)) | 
|  | 124 | } | 
|  | 125 |  | 
|  | 126 | func (be ZipEntryFromBuffer) WriteToZip(dest string, zw *zip.Writer) error { | 
| Colin Cross | 7592d5a | 2023-07-18 15:57:09 -0700 | [diff] [blame] | 127 | w, err := zw.CreateHeaderAndroid(be.fh) | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 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. | 
|  | 143 | type 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 |  | 
|  | 154 | func 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 |  | 
|  | 165 | func (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 |  | 
|  | 172 | func (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. | 
|  | 178 | func (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 | 
|  | 191 | func (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 | 
|  | 208 | func (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 |  | 
|  | 223 | func (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 | 
|  | 237 | func (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. | 
|  | 278 | func (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 |  | 
|  | 310 | func (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 |  | 
|  | 320 | func (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 |  | 
|  | 326 | func (oz *OutputZip) alphanumericSorted() []string { | 
|  | 327 | entries := oz.entriesArray() | 
|  | 328 | sort.Strings(entries) | 
|  | 329 | return entries | 
|  | 330 | } | 
|  | 331 |  | 
|  | 332 | func (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 |  | 
|  | 342 | func (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 | allPackages := make(map[string]bool) | 
|  | 346 | initedPackages := make(map[string]bool) | 
|  | 347 | getPackage := func(path string) string { | 
|  | 348 | ret := filepath.Dir(path) | 
|  | 349 | // filepath.Dir("abc") -> "." and filepath.Dir("/abc") -> "/". | 
|  | 350 | if ret == "." || ret == "/" { | 
|  | 351 | return "" | 
|  | 352 | } | 
|  | 353 | return ret | 
|  | 354 | } | 
|  | 355 |  | 
|  | 356 | // put existing __init__.py files to a set first. This set is used for preventing | 
|  | 357 | // generated __init__.py files from overwriting existing ones. | 
|  | 358 | for _, inputZip := range inputZips { | 
|  | 359 | if err := inputZip.Open(); err != nil { | 
|  | 360 | return nil, err | 
|  | 361 | } | 
|  | 362 | for _, file := range inputZip.Entries() { | 
|  | 363 | pyPkg := getPackage(file.Name) | 
| Cole Faust | 5c503d1 | 2023-01-24 11:48:08 -0800 | [diff] [blame] | 364 | baseName := filepath.Base(file.Name) | 
|  | 365 | if baseName == "__init__.py" || baseName == "__init__.pyc" { | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 366 | if _, found := initedPackages[pyPkg]; found { | 
|  | 367 | panic(fmt.Errorf("found __init__.py path duplicates during pars merging: %q", file.Name)) | 
|  | 368 | } | 
|  | 369 | initedPackages[pyPkg] = true | 
|  | 370 | } | 
|  | 371 | for pyPkg != "" { | 
|  | 372 | if _, found := allPackages[pyPkg]; found { | 
|  | 373 | break | 
|  | 374 | } | 
|  | 375 | allPackages[pyPkg] = true | 
|  | 376 | pyPkg = getPackage(pyPkg) | 
|  | 377 | } | 
|  | 378 | } | 
|  | 379 | } | 
|  | 380 | noInitPackages := make([]string, 0) | 
|  | 381 | for pyPkg := range allPackages { | 
|  | 382 | if _, found := initedPackages[pyPkg]; !found { | 
|  | 383 | noInitPackages = append(noInitPackages, pyPkg) | 
|  | 384 | } | 
|  | 385 | } | 
|  | 386 | return noInitPackages, nil | 
|  | 387 | } | 
|  | 388 |  | 
|  | 389 | // An InputZip owned by the InputZipsManager. Opened ManagedInputZip's are chained in the open order. | 
|  | 390 | type ManagedInputZip struct { | 
|  | 391 | owner        *InputZipsManager | 
|  | 392 | realInputZip InputZip | 
|  | 393 | older        *ManagedInputZip | 
|  | 394 | newer        *ManagedInputZip | 
|  | 395 | } | 
|  | 396 |  | 
|  | 397 | // Maintains the array of ManagedInputZips, keeping track of open input ones. When an InputZip is opened, | 
|  | 398 | // may close some other InputZip to limit the number of open ones. | 
|  | 399 | type InputZipsManager struct { | 
|  | 400 | inputZips     []*ManagedInputZip | 
|  | 401 | nOpenZips     int | 
|  | 402 | maxOpenZips   int | 
|  | 403 | openInputZips *ManagedInputZip | 
|  | 404 | } | 
|  | 405 |  | 
|  | 406 | func (miz *ManagedInputZip) unlink() { | 
|  | 407 | olderMiz := miz.older | 
|  | 408 | newerMiz := miz.newer | 
|  | 409 | if newerMiz.older != miz || olderMiz.newer != miz { | 
|  | 410 | panic(fmt.Errorf("removing %p:%#v: broken list between %p:%#v and %p:%#v", | 
|  | 411 | miz, miz, newerMiz, newerMiz, olderMiz, olderMiz)) | 
|  | 412 | } | 
|  | 413 | olderMiz.newer = newerMiz | 
|  | 414 | newerMiz.older = olderMiz | 
|  | 415 | miz.newer = nil | 
|  | 416 | miz.older = nil | 
|  | 417 | } | 
|  | 418 |  | 
|  | 419 | func (miz *ManagedInputZip) link(olderMiz *ManagedInputZip) { | 
|  | 420 | if olderMiz.newer != nil || olderMiz.older != nil { | 
|  | 421 | panic(fmt.Errorf("inputZip is already open")) | 
|  | 422 | } | 
|  | 423 | oldOlderMiz := miz.older | 
|  | 424 | if oldOlderMiz.newer != miz { | 
| Colin Cross | b5f6dc4 | 2019-09-11 09:48:37 -0700 | [diff] [blame] | 425 | panic(fmt.Errorf("broken list between %p:%#v and %p:%#v", miz, miz, oldOlderMiz, oldOlderMiz)) | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 426 | } | 
|  | 427 | miz.older = olderMiz | 
|  | 428 | olderMiz.older = oldOlderMiz | 
|  | 429 | oldOlderMiz.newer = olderMiz | 
|  | 430 | olderMiz.newer = miz | 
|  | 431 | } | 
|  | 432 |  | 
|  | 433 | func NewInputZipsManager(nInputZips, maxOpenZips int) *InputZipsManager { | 
|  | 434 | if maxOpenZips < 3 { | 
|  | 435 | panic(fmt.Errorf("open zips limit should be above 3")) | 
|  | 436 | } | 
| Patrice Arruda | 8da724a | 2020-07-28 21:22:12 +0000 | [diff] [blame] | 437 | // In the fake element .older points to the most recently opened InputZip, and .newer points to the oldest. | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 438 | head := new(ManagedInputZip) | 
|  | 439 | head.older = head | 
|  | 440 | head.newer = head | 
|  | 441 | return &InputZipsManager{ | 
|  | 442 | inputZips:     make([]*ManagedInputZip, 0, nInputZips), | 
|  | 443 | maxOpenZips:   maxOpenZips, | 
|  | 444 | openInputZips: head, | 
|  | 445 | } | 
|  | 446 | } | 
|  | 447 |  | 
|  | 448 | // InputZip factory | 
|  | 449 | func (izm *InputZipsManager) Manage(inz InputZip) InputZip { | 
|  | 450 | iz := &ManagedInputZip{owner: izm, realInputZip: inz} | 
|  | 451 | izm.inputZips = append(izm.inputZips, iz) | 
|  | 452 | return iz | 
|  | 453 | } | 
|  | 454 |  | 
|  | 455 | // Opens or reopens ManagedInputZip. | 
|  | 456 | func (izm *InputZipsManager) reopen(miz *ManagedInputZip) error { | 
|  | 457 | if miz.realInputZip.IsOpen() { | 
|  | 458 | if miz != izm.openInputZips { | 
|  | 459 | miz.unlink() | 
|  | 460 | izm.openInputZips.link(miz) | 
|  | 461 | } | 
|  | 462 | return nil | 
|  | 463 | } | 
|  | 464 | if izm.nOpenZips >= izm.maxOpenZips { | 
|  | 465 | if err := izm.close(izm.openInputZips.older); err != nil { | 
|  | 466 | return err | 
|  | 467 | } | 
|  | 468 | } | 
|  | 469 | if err := miz.realInputZip.Open(); err != nil { | 
|  | 470 | return err | 
|  | 471 | } | 
|  | 472 | izm.openInputZips.link(miz) | 
|  | 473 | izm.nOpenZips++ | 
|  | 474 | return nil | 
|  | 475 | } | 
|  | 476 |  | 
|  | 477 | func (izm *InputZipsManager) close(miz *ManagedInputZip) error { | 
|  | 478 | if miz.IsOpen() { | 
|  | 479 | err := miz.realInputZip.Close() | 
|  | 480 | izm.nOpenZips-- | 
|  | 481 | miz.unlink() | 
|  | 482 | return err | 
|  | 483 | } | 
|  | 484 | return nil | 
|  | 485 | } | 
|  | 486 |  | 
|  | 487 | // Checks that openInputZips deque is valid | 
|  | 488 | func (izm *InputZipsManager) checkOpenZipsDeque() { | 
|  | 489 | nReallyOpen := 0 | 
|  | 490 | el := izm.openInputZips | 
|  | 491 | for { | 
|  | 492 | elNext := el.older | 
|  | 493 | if elNext.newer != el { | 
|  | 494 | panic(fmt.Errorf("Element:\n  %p: %v\nNext:\n  %p %v", el, el, elNext, elNext)) | 
|  | 495 | } | 
|  | 496 | if elNext == izm.openInputZips { | 
|  | 497 | break | 
|  | 498 | } | 
|  | 499 | el = elNext | 
|  | 500 | if !el.IsOpen() { | 
|  | 501 | panic(fmt.Errorf("Found unopened element")) | 
|  | 502 | } | 
|  | 503 | nReallyOpen++ | 
|  | 504 | if nReallyOpen > izm.nOpenZips { | 
|  | 505 | panic(fmt.Errorf("found %d open zips, should be %d", nReallyOpen, izm.nOpenZips)) | 
|  | 506 | } | 
|  | 507 | } | 
|  | 508 | if nReallyOpen > izm.nOpenZips { | 
|  | 509 | panic(fmt.Errorf("found %d open zips, should be %d", nReallyOpen, izm.nOpenZips)) | 
|  | 510 | } | 
|  | 511 | } | 
|  | 512 |  | 
|  | 513 | func (miz *ManagedInputZip) Name() string { | 
|  | 514 | return miz.realInputZip.Name() | 
|  | 515 | } | 
|  | 516 |  | 
|  | 517 | func (miz *ManagedInputZip) Open() error { | 
|  | 518 | return miz.owner.reopen(miz) | 
|  | 519 | } | 
|  | 520 |  | 
|  | 521 | func (miz *ManagedInputZip) Close() error { | 
|  | 522 | return miz.owner.close(miz) | 
|  | 523 | } | 
|  | 524 |  | 
|  | 525 | func (miz *ManagedInputZip) IsOpen() bool { | 
|  | 526 | return miz.realInputZip.IsOpen() | 
|  | 527 | } | 
|  | 528 |  | 
|  | 529 | func (miz *ManagedInputZip) Entries() []*zip.File { | 
|  | 530 | if !miz.IsOpen() { | 
|  | 531 | panic(fmt.Errorf("%s: is not open", miz.Name())) | 
|  | 532 | } | 
|  | 533 | return miz.realInputZip.Entries() | 
|  | 534 | } | 
|  | 535 |  | 
|  | 536 | // Actual processing. | 
|  | 537 | func mergeZips(inputZips []InputZip, writer *zip.Writer, manifest, pyMain string, | 
|  | 538 | sortEntries, emulateJar, emulatePar, stripDirEntries, ignoreDuplicates bool, | 
|  | 539 | excludeFiles, excludeDirs []string, zipsToNotStrip map[string]bool) error { | 
|  | 540 |  | 
|  | 541 | out := NewOutputZip(writer, sortEntries, emulateJar, stripDirEntries, ignoreDuplicates) | 
|  | 542 | out.setExcludeFiles(excludeFiles) | 
|  | 543 | out.setExcludeDirs(excludeDirs) | 
|  | 544 | if manifest != "" { | 
|  | 545 | if err := out.addManifest(manifest); err != nil { | 
|  | 546 | return err | 
|  | 547 | } | 
|  | 548 | } | 
|  | 549 | if pyMain != "" { | 
|  | 550 | if err := out.addZipEntryFromFile("__main__.py", pyMain); err != nil { | 
|  | 551 | return err | 
|  | 552 | } | 
|  | 553 | } | 
|  | 554 |  | 
|  | 555 | if emulatePar { | 
|  | 556 | noInitPackages, err := out.getUninitializedPythonPackages(inputZips) | 
|  | 557 | if err != nil { | 
|  | 558 | return err | 
|  | 559 | } | 
|  | 560 | for _, uninitializedPyPackage := range noInitPackages { | 
|  | 561 | if err = out.addEmptyEntry(filepath.Join(uninitializedPyPackage, "__init__.py")); err != nil { | 
|  | 562 | return err | 
|  | 563 | } | 
|  | 564 | } | 
|  | 565 | } | 
|  | 566 |  | 
| Colin Cross | 7592d5a | 2023-07-18 15:57:09 -0700 | [diff] [blame] | 567 | var jarServices jar.Services | 
|  | 568 |  | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 569 | // Finally, add entries from all the input zips. | 
|  | 570 | for _, inputZip := range inputZips { | 
|  | 571 | _, copyFully := zipsToNotStrip[inputZip.Name()] | 
|  | 572 | if err := inputZip.Open(); err != nil { | 
|  | 573 | return err | 
|  | 574 | } | 
|  | 575 |  | 
|  | 576 | for i, entry := range inputZip.Entries() { | 
| Colin Cross | 7592d5a | 2023-07-18 15:57:09 -0700 | [diff] [blame] | 577 | if emulateJar && jarServices.IsServiceFile(entry) { | 
|  | 578 | // If this is a jar, collect service files to combine  instead of adding them to the zip. | 
|  | 579 | err := jarServices.AddServiceFile(entry) | 
|  | 580 | if err != nil { | 
|  | 581 | return err | 
|  | 582 | } | 
|  | 583 | continue | 
|  | 584 | } | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 585 | if copyFully || !out.isEntryExcluded(entry.Name) { | 
|  | 586 | if err := out.copyEntry(inputZip, i); err != nil { | 
|  | 587 | return err | 
|  | 588 | } | 
|  | 589 | } | 
|  | 590 | } | 
|  | 591 | // Unless we need to rearrange the entries, the input zip can now be closed. | 
|  | 592 | if !(emulateJar || sortEntries) { | 
|  | 593 | if err := inputZip.Close(); err != nil { | 
|  | 594 | return err | 
|  | 595 | } | 
|  | 596 | } | 
|  | 597 | } | 
|  | 598 |  | 
|  | 599 | if emulateJar { | 
| Colin Cross | 7592d5a | 2023-07-18 15:57:09 -0700 | [diff] [blame] | 600 | // Combine all the service files into a single list of combined service files and add them to the zip. | 
|  | 601 | for _, serviceFile := range jarServices.ServiceFiles() { | 
|  | 602 | _, err := out.addZipEntry(serviceFile.Name, ZipEntryFromBuffer{ | 
|  | 603 | fh:      serviceFile.FileHeader, | 
|  | 604 | content: serviceFile.Contents, | 
|  | 605 | }) | 
|  | 606 | if err != nil { | 
|  | 607 | return err | 
|  | 608 | } | 
|  | 609 | } | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 610 | return out.writeEntries(out.jarSorted()) | 
|  | 611 | } else if sortEntries { | 
|  | 612 | return out.writeEntries(out.alphanumericSorted()) | 
|  | 613 | } | 
|  | 614 | return nil | 
|  | 615 | } | 
|  | 616 |  | 
|  | 617 | // Process command line | 
| Colin Cross | 0cf45cd | 2017-10-04 17:04:16 -0700 | [diff] [blame] | 618 | type fileList []string | 
| Nan Zhang | d5998cc | 2017-09-13 13:17:43 -0700 | [diff] [blame] | 619 |  | 
| Colin Cross | 0cf45cd | 2017-10-04 17:04:16 -0700 | [diff] [blame] | 620 | func (f *fileList) String() string { | 
| Nan Zhang | d5998cc | 2017-09-13 13:17:43 -0700 | [diff] [blame] | 621 | return `""` | 
|  | 622 | } | 
|  | 623 |  | 
| Colin Cross | 0cf45cd | 2017-10-04 17:04:16 -0700 | [diff] [blame] | 624 | func (f *fileList) Set(name string) error { | 
|  | 625 | *f = append(*f, filepath.Clean(name)) | 
| Nan Zhang | 13f4cf5 | 2017-09-19 18:42:01 -0700 | [diff] [blame] | 626 |  | 
|  | 627 | return nil | 
|  | 628 | } | 
|  | 629 |  | 
| Colin Cross | 0cf45cd | 2017-10-04 17:04:16 -0700 | [diff] [blame] | 630 | type zipsToNotStripSet map[string]bool | 
| Nan Zhang | 13f4cf5 | 2017-09-19 18:42:01 -0700 | [diff] [blame] | 631 |  | 
| Colin Cross | 0cf45cd | 2017-10-04 17:04:16 -0700 | [diff] [blame] | 632 | func (s zipsToNotStripSet) String() string { | 
| Nan Zhang | 13f4cf5 | 2017-09-19 18:42:01 -0700 | [diff] [blame] | 633 | return `""` | 
|  | 634 | } | 
|  | 635 |  | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 636 | func (s zipsToNotStripSet) Set(path string) error { | 
|  | 637 | s[path] = true | 
| Nan Zhang | d5998cc | 2017-09-13 13:17:43 -0700 | [diff] [blame] | 638 | return nil | 
|  | 639 | } | 
|  | 640 |  | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 641 | var ( | 
| Colin Cross | e909e1e | 2017-11-22 14:09:40 -0800 | [diff] [blame] | 642 | sortEntries      = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)") | 
|  | 643 | emulateJar       = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)") | 
| Nan Zhang | 5925b0f | 2017-12-19 15:13:40 -0800 | [diff] [blame] | 644 | emulatePar       = flag.Bool("p", false, "merge zip entries based on par format") | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 645 | excludeDirs      fileList | 
|  | 646 | excludeFiles     fileList | 
| Colin Cross | e909e1e | 2017-11-22 14:09:40 -0800 | [diff] [blame] | 647 | zipsToNotStrip   = make(zipsToNotStripSet) | 
|  | 648 | stripDirEntries  = flag.Bool("D", false, "strip directory entries from the output zip file") | 
|  | 649 | manifest         = flag.String("m", "", "manifest file to insert in jar") | 
| Nan Zhang | 1db8540 | 2017-12-18 13:20:23 -0800 | [diff] [blame] | 650 | pyMain           = flag.String("pm", "", "__main__.py file to insert in par") | 
| Dan Willemsen | 263dde7 | 2018-11-15 19:15:02 -0800 | [diff] [blame] | 651 | prefix           = flag.String("prefix", "", "A file to prefix to the zip file") | 
| Colin Cross | e909e1e | 2017-11-22 14:09:40 -0800 | [diff] [blame] | 652 | ignoreDuplicates = flag.Bool("ignore-duplicates", false, "take each entry from the first zip it exists in and don't warn") | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 653 | ) | 
|  | 654 |  | 
| Nan Zhang | d5998cc | 2017-09-13 13:17:43 -0700 | [diff] [blame] | 655 | func init() { | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 656 | flag.Var(&excludeDirs, "stripDir", "directories to be excluded from the output zip, accepts wildcards") | 
|  | 657 | flag.Var(&excludeFiles, "stripFile", "files to be excluded from the output zip, accepts wildcards") | 
| Colin Cross | 0cf45cd | 2017-10-04 17:04:16 -0700 | [diff] [blame] | 658 | flag.Var(&zipsToNotStrip, "zipToNotStrip", "the input zip file which is not applicable for stripping") | 
| Nan Zhang | d5998cc | 2017-09-13 13:17:43 -0700 | [diff] [blame] | 659 | } | 
|  | 660 |  | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 661 | type FileInputZip struct { | 
|  | 662 | name   string | 
|  | 663 | reader *zip.ReadCloser | 
|  | 664 | } | 
|  | 665 |  | 
|  | 666 | func (fiz *FileInputZip) Name() string { | 
|  | 667 | return fiz.name | 
|  | 668 | } | 
|  | 669 |  | 
|  | 670 | func (fiz *FileInputZip) Close() error { | 
|  | 671 | if fiz.IsOpen() { | 
|  | 672 | reader := fiz.reader | 
|  | 673 | fiz.reader = nil | 
|  | 674 | return reader.Close() | 
|  | 675 | } | 
|  | 676 | return nil | 
|  | 677 | } | 
|  | 678 |  | 
|  | 679 | func (fiz *FileInputZip) Entries() []*zip.File { | 
|  | 680 | if !fiz.IsOpen() { | 
|  | 681 | panic(fmt.Errorf("%s: is not open", fiz.Name())) | 
|  | 682 | } | 
|  | 683 | return fiz.reader.File | 
|  | 684 | } | 
|  | 685 |  | 
|  | 686 | func (fiz *FileInputZip) IsOpen() bool { | 
|  | 687 | return fiz.reader != nil | 
|  | 688 | } | 
|  | 689 |  | 
|  | 690 | func (fiz *FileInputZip) Open() error { | 
|  | 691 | if fiz.IsOpen() { | 
|  | 692 | return nil | 
|  | 693 | } | 
|  | 694 | var err error | 
| Sasha Smundak | 6172491 | 2020-01-22 10:21:43 -0800 | [diff] [blame] | 695 | if fiz.reader, err = zip.OpenReader(fiz.Name()); err != nil { | 
|  | 696 | return fmt.Errorf("%s: %s", fiz.Name(), err.Error()) | 
|  | 697 | } | 
|  | 698 | return nil | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 699 | } | 
|  | 700 |  | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 701 | func main() { | 
|  | 702 | flag.Usage = func() { | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 703 | fmt.Fprintln(os.Stderr, "usage: merge_zips [-jpsD] [-m manifest] [--prefix script] [-pm __main__.py] OutputZip [inputs...]") | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 704 | flag.PrintDefaults() | 
|  | 705 | } | 
|  | 706 |  | 
|  | 707 | // parse args | 
|  | 708 | flag.Parse() | 
|  | 709 | args := flag.Args() | 
| Colin Cross | 5c6ecc1 | 2017-10-23 18:12:27 -0700 | [diff] [blame] | 710 | if len(args) < 1 { | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 711 | flag.Usage() | 
|  | 712 | os.Exit(1) | 
|  | 713 | } | 
|  | 714 | outputPath := args[0] | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 715 | inputs := make([]string, 0) | 
|  | 716 | for _, input := range args[1:] { | 
|  | 717 | if input[0] == '@' { | 
| Colin Cross | fd708b5 | 2021-03-23 14:16:05 -0700 | [diff] [blame] | 718 | f, err := os.Open(strings.TrimPrefix(input[1:], "@")) | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 719 | if err != nil { | 
|  | 720 | log.Fatal(err) | 
|  | 721 | } | 
| Colin Cross | fd708b5 | 2021-03-23 14:16:05 -0700 | [diff] [blame] | 722 |  | 
|  | 723 | rspInputs, err := response.ReadRspFile(f) | 
|  | 724 | f.Close() | 
|  | 725 | if err != nil { | 
|  | 726 | log.Fatal(err) | 
|  | 727 | } | 
|  | 728 | inputs = append(inputs, rspInputs...) | 
|  | 729 | } else { | 
|  | 730 | inputs = append(inputs, input) | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 731 | } | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 732 | } | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 733 |  | 
|  | 734 | log.SetFlags(log.Lshortfile) | 
|  | 735 |  | 
|  | 736 | // make writer | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 737 | outputZip, err := os.Create(outputPath) | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 738 | if err != nil { | 
|  | 739 | log.Fatal(err) | 
|  | 740 | } | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 741 | defer outputZip.Close() | 
| Dan Willemsen | 263dde7 | 2018-11-15 19:15:02 -0800 | [diff] [blame] | 742 |  | 
|  | 743 | var offset int64 | 
|  | 744 | if *prefix != "" { | 
|  | 745 | prefixFile, err := os.Open(*prefix) | 
|  | 746 | if err != nil { | 
|  | 747 | log.Fatal(err) | 
|  | 748 | } | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 749 | offset, err = io.Copy(outputZip, prefixFile) | 
| Dan Willemsen | 263dde7 | 2018-11-15 19:15:02 -0800 | [diff] [blame] | 750 | if err != nil { | 
|  | 751 | log.Fatal(err) | 
|  | 752 | } | 
|  | 753 | } | 
|  | 754 |  | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 755 | writer := zip.NewWriter(outputZip) | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 756 | defer func() { | 
|  | 757 | err := writer.Close() | 
|  | 758 | if err != nil { | 
|  | 759 | log.Fatal(err) | 
|  | 760 | } | 
|  | 761 | }() | 
| Dan Willemsen | 263dde7 | 2018-11-15 19:15:02 -0800 | [diff] [blame] | 762 | writer.SetOffset(offset) | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 763 |  | 
| Colin Cross | 635acc9 | 2017-09-12 22:50:46 -0700 | [diff] [blame] | 764 | if *manifest != "" && !*emulateJar { | 
|  | 765 | log.Fatal(errors.New("must specify -j when specifying a manifest via -m")) | 
|  | 766 | } | 
|  | 767 |  | 
| Nan Zhang | 1db8540 | 2017-12-18 13:20:23 -0800 | [diff] [blame] | 768 | if *pyMain != "" && !*emulatePar { | 
|  | 769 | log.Fatal(errors.New("must specify -p when specifying a Python __main__.py via -pm")) | 
|  | 770 | } | 
|  | 771 |  | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 772 | // do merge | 
| Sasha Smundak | 1459a92 | 2019-07-16 18:45:24 -0700 | [diff] [blame] | 773 | inputZipsManager := NewInputZipsManager(len(inputs), 1000) | 
|  | 774 | inputZips := make([]InputZip, len(inputs)) | 
|  | 775 | for i, input := range inputs { | 
|  | 776 | inputZips[i] = inputZipsManager.Manage(&FileInputZip{name: input}) | 
|  | 777 | } | 
|  | 778 | err = mergeZips(inputZips, writer, *manifest, *pyMain, *sortEntries, *emulateJar, *emulatePar, | 
|  | 779 | *stripDirEntries, *ignoreDuplicates, []string(excludeFiles), []string(excludeDirs), | 
|  | 780 | map[string]bool(zipsToNotStrip)) | 
| Colin Cross | 635acc9 | 2017-09-12 22:50:46 -0700 | [diff] [blame] | 781 | if err != nil { | 
| Jeff Gaston | 8bab5f2 | 2017-09-01 13:34:28 -0700 | [diff] [blame] | 782 | log.Fatal(err) | 
|  | 783 | } | 
|  | 784 | } |