blob: e17803607c0186f5b5a92326b5549320a4e210f0 [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)")
31 sortJava = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
32)
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
79 if err := mergeZips(readers, writer, *sortEntries, *sortJava); err != nil {
80 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
112func mergeZips(readers []namedZipReader, writer *zip.Writer, sortEntries bool, sortJava bool) error {
113
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
131 // handle duplicates
132 if exists {
133 wasDir := existingMapping.source.content.FileHeader.FileInfo().IsDir()
134 isDir := newMapping.source.content.FileHeader.FileInfo().IsDir()
135 if !wasDir || !isDir {
136 return fmt.Errorf("Duplicate path %v found in %v and %v\n",
137 dest, existingMapping.source.path, newMapping.source.path)
138 }
139 }
140
141 // save entry
142 mappingsByDest[mapKey] = newMapping
143 orderedMappings = append(orderedMappings, newMapping)
144 }
145
146 }
147
148 if sortJava {
149 jarSort(orderedMappings)
150 } else if sortEntries {
151 alphanumericSort(orderedMappings)
152 }
153
154 for _, entry := range orderedMappings {
155 if err := writer.CopyFrom(entry.source.content, entry.dest); err != nil {
156 return err
157 }
158 }
159
160 return nil
161}
162
163func jarSort(files []fileMapping) {
164 sort.SliceStable(files, func(i, j int) bool {
165 return jar.EntryNamesLess(files[i].dest, files[j].dest)
166 })
167}
168
169func alphanumericSort(files []fileMapping) {
170 sort.SliceStable(files, func(i, j int) bool {
171 return files[i].dest < files[j].dest
172 })
173}