blob: 7874a41b7ba0add2a27b8016801ebcf7eca10df0 [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
29var (
30 sortEntries = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)")
Colin Cross34540312017-09-06 12:52:37 -070031 emulateJar = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
Jeff Gaston8bab5f22017-09-01 13:34:28 -070032)
33
34func main() {
35 flag.Usage = func() {
36 fmt.Fprintln(os.Stderr, "usage: merge_zips [-j] output [inputs...]")
37 flag.PrintDefaults()
38 }
39
40 // parse args
41 flag.Parse()
42 args := flag.Args()
43 if len(args) < 2 {
44 flag.Usage()
45 os.Exit(1)
46 }
47 outputPath := args[0]
48 inputs := args[1:]
49
50 log.SetFlags(log.Lshortfile)
51
52 // make writer
53 output, err := os.Create(outputPath)
54 if err != nil {
55 log.Fatal(err)
56 }
57 defer output.Close()
58 writer := zip.NewWriter(output)
59 defer func() {
60 err := writer.Close()
61 if err != nil {
62 log.Fatal(err)
63 }
64 }()
65
66 // make readers
67 readers := []namedZipReader{}
68 for _, input := range inputs {
69 reader, err := zip.OpenReader(input)
70 if err != nil {
71 log.Fatal(err)
72 }
73 defer reader.Close()
74 namedReader := namedZipReader{path: input, reader: reader}
75 readers = append(readers, namedReader)
76 }
77
78 // do merge
Colin Cross34540312017-09-06 12:52:37 -070079 if err := mergeZips(readers, writer, *sortEntries, *emulateJar); err != nil {
Jeff Gaston8bab5f22017-09-01 13:34:28 -070080 log.Fatal(err)
81 }
82}
83
84// a namedZipReader reads a .zip file and can say which file it's reading
85type namedZipReader struct {
86 path string
87 reader *zip.ReadCloser
88}
89
90// a zipEntryPath refers to a file contained in a zip
91type zipEntryPath struct {
92 zipName string
93 entryName string
94}
95
96func (p zipEntryPath) String() string {
97 return p.zipName + "/" + p.entryName
98}
99
100// a zipEntry knows the location and content of a file within a zip
101type zipEntry struct {
102 path zipEntryPath
103 content *zip.File
104}
105
106// a fileMapping specifies to copy a zip entry from one place to another
107type fileMapping struct {
108 source zipEntry
109 dest string
110}
111
Colin Cross34540312017-09-06 12:52:37 -0700112func mergeZips(readers []namedZipReader, writer *zip.Writer, sortEntries bool, emulateJar bool) error {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700113
114 mappingsByDest := make(map[string]fileMapping, 0)
115 orderedMappings := []fileMapping{}
116
117 for _, namedReader := range readers {
118 for _, file := range namedReader.reader.File {
119 // check for other files or directories destined for the same path
120 dest := file.Name
121 mapKey := dest
122 if strings.HasSuffix(mapKey, "/") {
123 mapKey = mapKey[:len(mapKey)-1]
124 }
125 existingMapping, exists := mappingsByDest[mapKey]
126
127 // make a new entry to add
128 source := zipEntry{path: zipEntryPath{zipName: namedReader.path, entryName: file.Name}, content: file}
129 newMapping := fileMapping{source: source, dest: dest}
130
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700131 if exists {
Colin Cross34540312017-09-06 12:52:37 -0700132 // handle duplicates
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700133 wasDir := existingMapping.source.content.FileHeader.FileInfo().IsDir()
134 isDir := newMapping.source.content.FileHeader.FileInfo().IsDir()
Colin Cross34540312017-09-06 12:52:37 -0700135 if wasDir != isDir {
136 return fmt.Errorf("Directory/file mismatch at %v from %v and %v\n",
137 dest, existingMapping.source.path, newMapping.source.path)
138 }
139 if emulateJar &&
140 file.Name == jar.ManifestFile || file.Name == jar.ModuleInfoClass {
141 // Skip manifest and module info files that are not from the first input file
142 continue
143 }
144 if !isDir {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700145 return fmt.Errorf("Duplicate path %v found in %v and %v\n",
146 dest, existingMapping.source.path, newMapping.source.path)
147 }
Colin Cross34540312017-09-06 12:52:37 -0700148 } else {
149 // save entry
150 mappingsByDest[mapKey] = newMapping
151 orderedMappings = append(orderedMappings, newMapping)
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700152 }
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700153 }
154
155 }
156
Colin Cross34540312017-09-06 12:52:37 -0700157 if emulateJar {
Jeff Gaston8bab5f22017-09-01 13:34:28 -0700158 jarSort(orderedMappings)
159 } else if sortEntries {
160 alphanumericSort(orderedMappings)
161 }
162
163 for _, entry := range orderedMappings {
164 if err := writer.CopyFrom(entry.source.content, entry.dest); err != nil {
165 return err
166 }
167 }
168
169 return nil
170}
171
172func jarSort(files []fileMapping) {
173 sort.SliceStable(files, func(i, j int) bool {
174 return jar.EntryNamesLess(files[i].dest, files[j].dest)
175 })
176}
177
178func alphanumericSort(files []fileMapping) {
179 sort.SliceStable(files, func(i, j int) bool {
180 return files[i].dest < files[j].dest
181 })
182}