blob: 5ab96568d9d3b6b9351d3114b3d2f718adf11efd [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 {
Colin Cross38247712018-10-09 10:19:54 -0700131 input, output := includeSplit(arg)
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700132
Colin Crossf3831d02017-11-22 12:53:08 -0800133 var includeMatches []pair
Dan Willemsen82218f22017-06-19 16:35:00 -0700134
Colin Crossf3831d02017-11-22 12:53:08 -0800135 for _, file := range reader.File {
136 var newName string
137 if match, err := pathtools.Match(input, file.Name); err != nil {
138 return err
139 } else if match {
140 if output == "" {
141 newName = file.Name
142 } else {
143 if pathtools.IsGlob(input) {
144 // If the input is a glob then the output is a directory.
Colin Cross714614c2018-11-01 13:27:58 -0700145 rel, err := filepath.Rel(constantPartOfPattern(input), file.Name)
146 if err != nil {
147 return err
148 } else if strings.HasPrefix("../", rel) {
149 return fmt.Errorf("globbed path %q was not in %q", file.Name, constantPartOfPattern(input))
150 }
151 newName = filepath.Join(output, rel)
Colin Crossf3831d02017-11-22 12:53:08 -0800152 } else {
153 // Otherwise it is a file.
154 newName = output
Dan Willemsen82218f22017-06-19 16:35:00 -0700155 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700156 }
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700157 includeMatches = append(includeMatches, pair{file, newName, false})
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700158 }
159 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700160
Colin Crossf3831d02017-11-22 12:53:08 -0800161 sortMatches(includeMatches)
162 matches = append(matches, includeMatches...)
163 }
164
Colin Cross38247712018-10-09 10:19:54 -0700165 if len(args) == 0 {
Colin Crossf3831d02017-11-22 12:53:08 -0800166 // implicitly match everything
167 for _, file := range reader.File {
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700168 matches = append(matches, pair{file, file.Name, false})
Colin Crossf3831d02017-11-22 12:53:08 -0800169 }
170 sortMatches(matches)
171 }
172
173 var matchesAfterExcludes []pair
174 seen := make(map[string]*zip.File)
175
176 for _, match := range matches {
Colin Cross38247712018-10-09 10:19:54 -0700177 // Filter out matches whose original file name matches an exclude filter, unless it also matches an
178 // include filter
179 if exclude, err := excludes.Match(match.File.Name); err != nil {
180 return err
181 } else if exclude {
182 if include, err := includes.Match(match.File.Name); err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -0700183 return err
Colin Cross38247712018-10-09 10:19:54 -0700184 } else if !include {
185 continue
Dan Willemsen82218f22017-06-19 16:35:00 -0700186 }
187 }
Colin Crossf3831d02017-11-22 12:53:08 -0800188
Colin Crossf3831d02017-11-22 12:53:08 -0800189 // Check for duplicate output names, ignoring ones that come from the same input zip entry.
190 if prev, exists := seen[match.newName]; exists {
191 if prev != match.File {
192 return fmt.Errorf("multiple entries for %q with different contents", match.newName)
193 }
194 continue
195 }
196 seen[match.newName] = match.File
197
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700198 for _, u := range uncompresses {
199 if uncompressMatch, err := pathtools.Match(u, match.newName); err != nil {
200 return err
201 } else if uncompressMatch {
202 match.uncompress = true
203 break
204 }
205 }
206
Colin Crossf3831d02017-11-22 12:53:08 -0800207 matchesAfterExcludes = append(matchesAfterExcludes, match)
208 }
209
210 for _, match := range matchesAfterExcludes {
211 if setTime {
212 match.File.SetModTime(staticTime)
213 }
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700214 if match.uncompress && match.File.FileHeader.Method != zip.Store {
215 fh := match.File.FileHeader
216 fh.Name = match.newName
217 fh.Method = zip.Store
218 fh.CompressedSize64 = fh.UncompressedSize64
219
220 zw, err := writer.CreateHeaderAndroid(&fh)
221 if err != nil {
222 return err
223 }
224
225 zr, err := match.File.Open()
226 if err != nil {
227 return err
228 }
229
230 _, err = io.Copy(zw, zr)
231 zr.Close()
232 if err != nil {
233 return err
234 }
235 } else {
236 err := writer.CopyFrom(match.File, match.newName)
237 if err != nil {
238 return err
239 }
Colin Crossf3831d02017-11-22 12:53:08 -0800240 }
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700241 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700242
243 return nil
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700244}
Colin Cross8936b022017-06-23 13:00:17 -0700245
Colin Crossf3831d02017-11-22 12:53:08 -0800246func includeSplit(s string) (string, string) {
247 split := strings.SplitN(s, ":", 2)
248 if len(split) == 2 {
249 return split[0], split[1]
250 } else {
251 return split[0], ""
252 }
253}
254
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700255type multiFlag []string
Colin Crossf3831d02017-11-22 12:53:08 -0800256
Colin Cross38247712018-10-09 10:19:54 -0700257func (m *multiFlag) String() string {
258 return strings.Join(*m, " ")
Colin Crossf3831d02017-11-22 12:53:08 -0800259}
260
Colin Cross38247712018-10-09 10:19:54 -0700261func (m *multiFlag) Set(s string) error {
262 *m = append(*m, s)
Colin Crossf3831d02017-11-22 12:53:08 -0800263 return nil
Colin Cross8936b022017-06-23 13:00:17 -0700264}
Colin Cross38247712018-10-09 10:19:54 -0700265
266func (m *multiFlag) Match(s string) (bool, error) {
267 if m == nil {
268 return false, nil
269 }
270 for _, f := range *m {
271 if match, err := pathtools.Match(f, s); err != nil {
272 return false, err
273 } else if match {
274 return true, nil
275 }
276 }
277 return false, nil
278}
Colin Cross714614c2018-11-01 13:27:58 -0700279
280func constantPartOfPattern(pattern string) string {
281 ret := ""
282 for pattern != "" {
283 var first string
284 first, pattern = splitFirst(pattern)
285 if pathtools.IsGlob(first) {
286 return ret
287 }
288 ret = filepath.Join(ret, first)
289 }
290 return ret
291}
292
293func splitFirst(path string) (string, string) {
294 i := strings.IndexRune(path, filepath.Separator)
295 if i < 0 {
296 return path, ""
297 }
298 return path[:i], path[i+1:]
299}