blob: 491267b010b5168f0398b48844788c3fbe644ddc [file] [log] [blame]
Dan Willemsen3bf1a082016-08-03 00:35:25 -07001// Copyright 2016 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"
Colin Crossb1a5e9c2018-10-07 21:30:12 -070020 "io"
Dan Willemsen82218f22017-06-19 16:35:00 -070021 "log"
Dan Willemsen3bf1a082016-08-03 00:35:25 -070022 "os"
23 "path/filepath"
Dan Willemsen82218f22017-06-19 16:35:00 -070024 "sort"
Dan Willemsen3bf1a082016-08-03 00:35:25 -070025 "strings"
Dan Willemsen82218f22017-06-19 16:35:00 -070026 "time"
Dan Willemsen3bf1a082016-08-03 00:35:25 -070027
Colin Crossf3831d02017-11-22 12:53:08 -080028 "github.com/google/blueprint/pathtools"
29
Jeff Gaston01547b22017-08-21 20:13:28 -070030 "android/soong/jar"
Dan Willemsen3bf1a082016-08-03 00:35:25 -070031 "android/soong/third_party/zip"
32)
33
34var (
Dan Willemsen82218f22017-06-19 16:35:00 -070035 input = flag.String("i", "", "zip file to read from")
36 output = flag.String("o", "", "output file")
37 sortGlobs = flag.Bool("s", false, "sort matches from each glob (defaults to the order from the input zip file)")
Colin Cross8936b022017-06-23 13:00:17 -070038 sortJava = flag.Bool("j", false, "sort using jar ordering within each glob (META-INF/MANIFEST.MF first)")
Dan Willemsen82218f22017-06-19 16:35:00 -070039 setTime = flag.Bool("t", false, "set timestamps to 2009-01-01 00:00:00")
40
41 staticTime = time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC)
Colin Crossf3831d02017-11-22 12:53:08 -080042
Colin Crossb1a5e9c2018-10-07 21:30:12 -070043 excludes multiFlag
Colin Cross38247712018-10-09 10:19:54 -070044 includes multiFlag
Colin Crossb1a5e9c2018-10-07 21:30:12 -070045 uncompress multiFlag
Dan Willemsen3bf1a082016-08-03 00:35:25 -070046)
47
Colin Crossf3831d02017-11-22 12:53:08 -080048func init() {
49 flag.Var(&excludes, "x", "exclude a filespec from the output")
Colin Cross38247712018-10-09 10:19:54 -070050 flag.Var(&includes, "X", "include a filespec in the output that was previously excluded")
Colin Crossb1a5e9c2018-10-07 21:30:12 -070051 flag.Var(&uncompress, "0", "convert a filespec to uncompressed in the output")
Colin Crossf3831d02017-11-22 12:53:08 -080052}
53
Dan Willemsen3bf1a082016-08-03 00:35:25 -070054func main() {
Dan Willemsen82218f22017-06-19 16:35:00 -070055 flag.Usage = func() {
Colin Cross8936b022017-06-23 13:00:17 -070056 fmt.Fprintln(os.Stderr, "usage: zip2zip -i zipfile -o zipfile [-s|-j] [-t] [filespec]...")
Dan Willemsen82218f22017-06-19 16:35:00 -070057 flag.PrintDefaults()
58 fmt.Fprintln(os.Stderr, " filespec:")
59 fmt.Fprintln(os.Stderr, " <name>")
60 fmt.Fprintln(os.Stderr, " <in_name>:<out_name>")
Colin Crossf3831d02017-11-22 12:53:08 -080061 fmt.Fprintln(os.Stderr, " <glob>[:<out_dir>]")
Dan Willemsen82218f22017-06-19 16:35:00 -070062 fmt.Fprintln(os.Stderr, "")
Colin Crossf3831d02017-11-22 12:53:08 -080063 fmt.Fprintln(os.Stderr, "<glob> uses the rules at https://godoc.org/github.com/google/blueprint/pathtools/#Match")
Dan Willemsen82218f22017-06-19 16:35:00 -070064 fmt.Fprintln(os.Stderr, "")
65 fmt.Fprintln(os.Stderr, "Files will be copied with their existing compression from the input zipfile to")
Colin Cross06382992017-06-23 14:08:42 -070066 fmt.Fprintln(os.Stderr, "the output zipfile, in the order of filespec arguments.")
67 fmt.Fprintln(os.Stderr, "")
Colin Crossf3831d02017-11-22 12:53:08 -080068 fmt.Fprintln(os.Stderr, "If no filepsec is provided all files and directories are copied.")
Dan Willemsen82218f22017-06-19 16:35:00 -070069 }
70
Dan Willemsen3bf1a082016-08-03 00:35:25 -070071 flag.Parse()
72
Colin Cross06382992017-06-23 14:08:42 -070073 if *input == "" || *output == "" {
Dan Willemsen82218f22017-06-19 16:35:00 -070074 flag.Usage()
75 os.Exit(1)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070076 }
77
Dan Willemsen82218f22017-06-19 16:35:00 -070078 log.SetFlags(log.Lshortfile)
79
Dan Willemsen3bf1a082016-08-03 00:35:25 -070080 reader, err := zip.OpenReader(*input)
81 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070082 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070083 }
84 defer reader.Close()
85
86 output, err := os.Create(*output)
87 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070088 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070089 }
90 defer output.Close()
91
92 writer := zip.NewWriter(output)
93 defer func() {
94 err := writer.Close()
95 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070096 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070097 }
98 }()
99
Colin Crossf3831d02017-11-22 12:53:08 -0800100 if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime,
Colin Cross38247712018-10-09 10:19:54 -0700101 flag.Args(), excludes, includes, uncompress); err != nil {
Colin Crossf3831d02017-11-22 12:53:08 -0800102
Dan Willemsen82218f22017-06-19 16:35:00 -0700103 log.Fatal(err)
104 }
105}
106
Colin Cross8936b022017-06-23 13:00:17 -0700107type pair struct {
108 *zip.File
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700109 newName string
110 uncompress bool
Colin Cross8936b022017-06-23 13:00:17 -0700111}
112
Colin Crossf3831d02017-11-22 12:53:08 -0800113func zip2zip(reader *zip.Reader, writer *zip.Writer, sortOutput, sortJava, setTime bool,
Colin Cross38247712018-10-09 10:19:54 -0700114 args []string, excludes, includes multiFlag, uncompresses []string) error {
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700115
Colin Crossf3831d02017-11-22 12:53:08 -0800116 matches := []pair{}
117
118 sortMatches := func(matches []pair) {
119 if sortJava {
120 sort.SliceStable(matches, func(i, j int) bool {
121 return jar.EntryNamesLess(matches[i].newName, matches[j].newName)
122 })
123 } else if sortOutput {
124 sort.SliceStable(matches, func(i, j int) bool {
125 return matches[i].newName < matches[j].newName
126 })
127 }
128 }
129
Colin Cross38247712018-10-09 10:19:54 -0700130 for _, arg := range args {
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700131 // Reserve escaping for future implementation, so make sure no
132 // one is using \ and expecting a certain behavior.
Colin Cross38247712018-10-09 10:19:54 -0700133 if strings.Contains(arg, "\\") {
Dan Willemsen82218f22017-06-19 16:35:00 -0700134 return fmt.Errorf("\\ characters are not currently supported")
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700135 }
136
Colin Cross38247712018-10-09 10:19:54 -0700137 input, output := includeSplit(arg)
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700138
Colin Crossf3831d02017-11-22 12:53:08 -0800139 var includeMatches []pair
Dan Willemsen82218f22017-06-19 16:35:00 -0700140
Colin Crossf3831d02017-11-22 12:53:08 -0800141 for _, file := range reader.File {
142 var newName string
143 if match, err := pathtools.Match(input, file.Name); err != nil {
144 return err
145 } else if match {
146 if output == "" {
147 newName = file.Name
148 } else {
149 if pathtools.IsGlob(input) {
150 // If the input is a glob then the output is a directory.
Colin Cross714614c2018-11-01 13:27:58 -0700151 rel, err := filepath.Rel(constantPartOfPattern(input), file.Name)
152 if err != nil {
153 return err
154 } else if strings.HasPrefix("../", rel) {
155 return fmt.Errorf("globbed path %q was not in %q", file.Name, constantPartOfPattern(input))
156 }
157 newName = filepath.Join(output, rel)
Colin Crossf3831d02017-11-22 12:53:08 -0800158 } else {
159 // Otherwise it is a file.
160 newName = output
Dan Willemsen82218f22017-06-19 16:35:00 -0700161 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700162 }
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700163 includeMatches = append(includeMatches, pair{file, newName, false})
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700164 }
165 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700166
Colin Crossf3831d02017-11-22 12:53:08 -0800167 sortMatches(includeMatches)
168 matches = append(matches, includeMatches...)
169 }
170
Colin Cross38247712018-10-09 10:19:54 -0700171 if len(args) == 0 {
Colin Crossf3831d02017-11-22 12:53:08 -0800172 // implicitly match everything
173 for _, file := range reader.File {
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700174 matches = append(matches, pair{file, file.Name, false})
Colin Crossf3831d02017-11-22 12:53:08 -0800175 }
176 sortMatches(matches)
177 }
178
179 var matchesAfterExcludes []pair
180 seen := make(map[string]*zip.File)
181
182 for _, match := range matches {
Colin Cross38247712018-10-09 10:19:54 -0700183 // Filter out matches whose original file name matches an exclude filter, unless it also matches an
184 // include filter
185 if exclude, err := excludes.Match(match.File.Name); err != nil {
186 return err
187 } else if exclude {
188 if include, err := includes.Match(match.File.Name); err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -0700189 return err
Colin Cross38247712018-10-09 10:19:54 -0700190 } else if !include {
191 continue
Dan Willemsen82218f22017-06-19 16:35:00 -0700192 }
193 }
Colin Crossf3831d02017-11-22 12:53:08 -0800194
Colin Crossf3831d02017-11-22 12:53:08 -0800195 // Check for duplicate output names, ignoring ones that come from the same input zip entry.
196 if prev, exists := seen[match.newName]; exists {
197 if prev != match.File {
198 return fmt.Errorf("multiple entries for %q with different contents", match.newName)
199 }
200 continue
201 }
202 seen[match.newName] = match.File
203
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700204 for _, u := range uncompresses {
205 if uncompressMatch, err := pathtools.Match(u, match.newName); err != nil {
206 return err
207 } else if uncompressMatch {
208 match.uncompress = true
209 break
210 }
211 }
212
Colin Crossf3831d02017-11-22 12:53:08 -0800213 matchesAfterExcludes = append(matchesAfterExcludes, match)
214 }
215
216 for _, match := range matchesAfterExcludes {
217 if setTime {
218 match.File.SetModTime(staticTime)
219 }
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700220 if match.uncompress && match.File.FileHeader.Method != zip.Store {
221 fh := match.File.FileHeader
222 fh.Name = match.newName
223 fh.Method = zip.Store
224 fh.CompressedSize64 = fh.UncompressedSize64
225
226 zw, err := writer.CreateHeaderAndroid(&fh)
227 if err != nil {
228 return err
229 }
230
231 zr, err := match.File.Open()
232 if err != nil {
233 return err
234 }
235
236 _, err = io.Copy(zw, zr)
237 zr.Close()
238 if err != nil {
239 return err
240 }
241 } else {
242 err := writer.CopyFrom(match.File, match.newName)
243 if err != nil {
244 return err
245 }
Colin Crossf3831d02017-11-22 12:53:08 -0800246 }
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700247 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700248
249 return nil
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700250}
Colin Cross8936b022017-06-23 13:00:17 -0700251
Colin Crossf3831d02017-11-22 12:53:08 -0800252func includeSplit(s string) (string, string) {
253 split := strings.SplitN(s, ":", 2)
254 if len(split) == 2 {
255 return split[0], split[1]
256 } else {
257 return split[0], ""
258 }
259}
260
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700261type multiFlag []string
Colin Crossf3831d02017-11-22 12:53:08 -0800262
Colin Cross38247712018-10-09 10:19:54 -0700263func (m *multiFlag) String() string {
264 return strings.Join(*m, " ")
Colin Crossf3831d02017-11-22 12:53:08 -0800265}
266
Colin Cross38247712018-10-09 10:19:54 -0700267func (m *multiFlag) Set(s string) error {
268 *m = append(*m, s)
Colin Crossf3831d02017-11-22 12:53:08 -0800269 return nil
Colin Cross8936b022017-06-23 13:00:17 -0700270}
Colin Cross38247712018-10-09 10:19:54 -0700271
272func (m *multiFlag) Match(s string) (bool, error) {
273 if m == nil {
274 return false, nil
275 }
276 for _, f := range *m {
277 if match, err := pathtools.Match(f, s); err != nil {
278 return false, err
279 } else if match {
280 return true, nil
281 }
282 }
283 return false, nil
284}
Colin Cross714614c2018-11-01 13:27:58 -0700285
286func constantPartOfPattern(pattern string) string {
287 ret := ""
288 for pattern != "" {
289 var first string
290 first, pattern = splitFirst(pattern)
291 if pathtools.IsGlob(first) {
292 return ret
293 }
294 ret = filepath.Join(ret, first)
295 }
296 return ret
297}
298
299func splitFirst(path string) (string, string) {
300 i := strings.IndexRune(path, filepath.Separator)
301 if i < 0 {
302 return path, ""
303 }
304 return path[:i], path[i+1:]
305}