blob: 3c1cb9a18102064576408aaec02d8fe8480cebc5 [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 (
18 "flag"
19 "fmt"
20 "log"
21 "os"
22 "sort"
23 "strings"
24
25 "android/soong/jar"
26 "android/soong/third_party/zip"
27)
28
Nan Zhangd5998cc2017-09-13 13:17:43 -070029type strip struct{}
30
31func (s *strip) String() string {
32 return `""`
33}
34
35func (s *strip) Set(path_prefix string) error {
36 strippings = append(strippings, path_prefix)
37
38 return nil
39}
40
Jeff Gaston8bab5f22017-09-01 13:34:28 -070041var (
42 sortEntries = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)")
Colin Cross34540312017-09-06 12:52:37 -070043 emulateJar = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
Nan Zhangd5998cc2017-09-13 13:17:43 -070044 strippings []string
Jeff Gaston8bab5f22017-09-01 13:34:28 -070045)
46
Nan Zhangd5998cc2017-09-13 13:17:43 -070047func init() {
48 flag.Var(&strip{}, "strip", "the prefix of file path to be excluded from the output zip")
49}
50
Jeff Gaston8bab5f22017-09-01 13:34:28 -070051func main() {
52 flag.Usage = func() {
53 fmt.Fprintln(os.Stderr, "usage: merge_zips [-j] output [inputs...]")
54 flag.PrintDefaults()
55 }
56
57 // parse args
58 flag.Parse()
59 args := flag.Args()
60 if len(args) < 2 {
61 flag.Usage()
62 os.Exit(1)
63 }
64 outputPath := args[0]
65 inputs := args[1:]
66
67 log.SetFlags(log.Lshortfile)
68
69 // make writer
70 output, err := os.Create(outputPath)
71 if err != nil {
72 log.Fatal(err)
73 }
74 defer output.Close()
75 writer := zip.NewWriter(output)
76 defer func() {
77 err := writer.Close()
78 if err != nil {
79 log.Fatal(err)
80 }
81 }()
82
83 // make readers
84 readers := []namedZipReader{}
85 for _, input := range inputs {
86 reader, err := zip.OpenReader(input)
87 if err != nil {
88 log.Fatal(err)
89 }
90 defer reader.Close()
91 namedReader := namedZipReader{path: input, reader: reader}
92 readers = append(readers, namedReader)
93 }
94
95 // do merge
Colin Cross34540312017-09-06 12:52:37 -070096 if err := mergeZips(readers, writer, *sortEntries, *emulateJar); err != nil {
Jeff Gaston8bab5f22017-09-01 13:34:28 -070097 log.Fatal(err)
98 }
99}
100
101// a namedZipReader reads a .zip file and can say which file it's reading
102type namedZipReader struct {
103 path string
104 reader *zip.ReadCloser
105}
106
107// a zipEntryPath refers to a file contained in a zip
108type zipEntryPath struct {
109 zipName string
110 entryName string
111}
112
113func (p zipEntryPath) String() string {
114 return p.zipName + "/" + p.entryName
115}
116
117// a zipEntry knows the location and content of a file within a zip
118type zipEntry struct {
119 path zipEntryPath
120 content *zip.File
121}
122
123// a fileMapping specifies to copy a zip entry from one place to another
124type fileMapping struct {
125 source zipEntry
126 dest string
127}
128
Colin Cross34540312017-09-06 12:52:37 -0700129func mergeZips(readers []namedZipReader, writer *zip.Writer, sortEntries bool, emulateJar bool) error {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700130
131 mappingsByDest := make(map[string]fileMapping, 0)
132 orderedMappings := []fileMapping{}
133
134 for _, namedReader := range readers {
Nan Zhangd5998cc2017-09-13 13:17:43 -0700135 FileLoop:
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700136 for _, file := range namedReader.reader.File {
Nan Zhangd5998cc2017-09-13 13:17:43 -0700137 for _, path_prefix := range strippings {
138 if strings.HasPrefix(file.Name, path_prefix) {
139 continue FileLoop
140 }
141 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700142 // check for other files or directories destined for the same path
143 dest := file.Name
144 mapKey := dest
145 if strings.HasSuffix(mapKey, "/") {
146 mapKey = mapKey[:len(mapKey)-1]
147 }
148 existingMapping, exists := mappingsByDest[mapKey]
149
150 // make a new entry to add
151 source := zipEntry{path: zipEntryPath{zipName: namedReader.path, entryName: file.Name}, content: file}
152 newMapping := fileMapping{source: source, dest: dest}
153
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700154 if exists {
Colin Cross34540312017-09-06 12:52:37 -0700155 // handle duplicates
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700156 wasDir := existingMapping.source.content.FileHeader.FileInfo().IsDir()
157 isDir := newMapping.source.content.FileHeader.FileInfo().IsDir()
Colin Cross34540312017-09-06 12:52:37 -0700158 if wasDir != isDir {
159 return fmt.Errorf("Directory/file mismatch at %v from %v and %v\n",
160 dest, existingMapping.source.path, newMapping.source.path)
161 }
162 if emulateJar &&
163 file.Name == jar.ManifestFile || file.Name == jar.ModuleInfoClass {
164 // Skip manifest and module info files that are not from the first input file
165 continue
166 }
167 if !isDir {
Nan Zhangd5998cc2017-09-13 13:17:43 -0700168 if emulateJar {
169 if existingMapping.source.content.CRC32 != newMapping.source.content.CRC32 {
170 fmt.Fprintf(os.Stdout, "WARNING: Duplicate path %v found in %v and %v\n",
171 dest, existingMapping.source.path, newMapping.source.path)
172 }
173 } else {
174 return fmt.Errorf("Duplicate path %v found in %v and %v\n",
175 dest, existingMapping.source.path, newMapping.source.path)
176 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700177 }
Colin Cross34540312017-09-06 12:52:37 -0700178 } else {
179 // save entry
180 mappingsByDest[mapKey] = newMapping
181 orderedMappings = append(orderedMappings, newMapping)
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700182 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700183 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700184 }
185
Colin Cross34540312017-09-06 12:52:37 -0700186 if emulateJar {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700187 jarSort(orderedMappings)
188 } else if sortEntries {
189 alphanumericSort(orderedMappings)
190 }
191
192 for _, entry := range orderedMappings {
193 if err := writer.CopyFrom(entry.source.content, entry.dest); err != nil {
194 return err
195 }
196 }
197
198 return nil
199}
200
201func jarSort(files []fileMapping) {
202 sort.SliceStable(files, func(i, j int) bool {
203 return jar.EntryNamesLess(files[i].dest, files[j].dest)
204 })
205}
206
207func alphanumericSort(files []fileMapping) {
208 sort.SliceStable(files, func(i, j int) bool {
209 return files[i].dest < files[j].dest
210 })
211}