blob: d3b349c5249507435ea3e9e81880664d18ab0425 [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
44 uncompress multiFlag
Dan Willemsen3bf1a082016-08-03 00:35:25 -070045)
46
Colin Crossf3831d02017-11-22 12:53:08 -080047func init() {
48 flag.Var(&excludes, "x", "exclude a filespec from the output")
Colin Crossb1a5e9c2018-10-07 21:30:12 -070049 flag.Var(&uncompress, "0", "convert a filespec to uncompressed in the output")
Colin Crossf3831d02017-11-22 12:53:08 -080050}
51
Dan Willemsen3bf1a082016-08-03 00:35:25 -070052func main() {
Dan Willemsen82218f22017-06-19 16:35:00 -070053 flag.Usage = func() {
Colin Cross8936b022017-06-23 13:00:17 -070054 fmt.Fprintln(os.Stderr, "usage: zip2zip -i zipfile -o zipfile [-s|-j] [-t] [filespec]...")
Dan Willemsen82218f22017-06-19 16:35:00 -070055 flag.PrintDefaults()
56 fmt.Fprintln(os.Stderr, " filespec:")
57 fmt.Fprintln(os.Stderr, " <name>")
58 fmt.Fprintln(os.Stderr, " <in_name>:<out_name>")
Colin Crossf3831d02017-11-22 12:53:08 -080059 fmt.Fprintln(os.Stderr, " <glob>[:<out_dir>]")
Dan Willemsen82218f22017-06-19 16:35:00 -070060 fmt.Fprintln(os.Stderr, "")
Colin Crossf3831d02017-11-22 12:53:08 -080061 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 -070062 fmt.Fprintln(os.Stderr, "")
63 fmt.Fprintln(os.Stderr, "Files will be copied with their existing compression from the input zipfile to")
Colin Cross06382992017-06-23 14:08:42 -070064 fmt.Fprintln(os.Stderr, "the output zipfile, in the order of filespec arguments.")
65 fmt.Fprintln(os.Stderr, "")
Colin Crossf3831d02017-11-22 12:53:08 -080066 fmt.Fprintln(os.Stderr, "If no filepsec is provided all files and directories are copied.")
Dan Willemsen82218f22017-06-19 16:35:00 -070067 }
68
Dan Willemsen3bf1a082016-08-03 00:35:25 -070069 flag.Parse()
70
Colin Cross06382992017-06-23 14:08:42 -070071 if *input == "" || *output == "" {
Dan Willemsen82218f22017-06-19 16:35:00 -070072 flag.Usage()
73 os.Exit(1)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070074 }
75
Dan Willemsen82218f22017-06-19 16:35:00 -070076 log.SetFlags(log.Lshortfile)
77
Dan Willemsen3bf1a082016-08-03 00:35:25 -070078 reader, err := zip.OpenReader(*input)
79 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070080 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070081 }
82 defer reader.Close()
83
84 output, err := os.Create(*output)
85 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070086 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070087 }
88 defer output.Close()
89
90 writer := zip.NewWriter(output)
91 defer func() {
92 err := writer.Close()
93 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070094 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070095 }
96 }()
97
Colin Crossf3831d02017-11-22 12:53:08 -080098 if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime,
Colin Crossb1a5e9c2018-10-07 21:30:12 -070099 flag.Args(), excludes, uncompress); err != nil {
Colin Crossf3831d02017-11-22 12:53:08 -0800100
Dan Willemsen82218f22017-06-19 16:35:00 -0700101 log.Fatal(err)
102 }
103}
104
Colin Cross8936b022017-06-23 13:00:17 -0700105type pair struct {
106 *zip.File
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700107 newName string
108 uncompress bool
Colin Cross8936b022017-06-23 13:00:17 -0700109}
110
Colin Crossf3831d02017-11-22 12:53:08 -0800111func zip2zip(reader *zip.Reader, writer *zip.Writer, sortOutput, sortJava, setTime bool,
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700112 includes, excludes, uncompresses []string) error {
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700113
Colin Crossf3831d02017-11-22 12:53:08 -0800114 matches := []pair{}
115
116 sortMatches := func(matches []pair) {
117 if sortJava {
118 sort.SliceStable(matches, func(i, j int) bool {
119 return jar.EntryNamesLess(matches[i].newName, matches[j].newName)
120 })
121 } else if sortOutput {
122 sort.SliceStable(matches, func(i, j int) bool {
123 return matches[i].newName < matches[j].newName
124 })
125 }
126 }
127
128 for _, include := range includes {
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700129 // Reserve escaping for future implementation, so make sure no
130 // one is using \ and expecting a certain behavior.
Colin Crossf3831d02017-11-22 12:53:08 -0800131 if strings.Contains(include, "\\") {
Dan Willemsen82218f22017-06-19 16:35:00 -0700132 return fmt.Errorf("\\ characters are not currently supported")
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700133 }
134
Colin Crossf3831d02017-11-22 12:53:08 -0800135 input, output := includeSplit(include)
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700136
Colin Crossf3831d02017-11-22 12:53:08 -0800137 var includeMatches []pair
Dan Willemsen82218f22017-06-19 16:35:00 -0700138
Colin Crossf3831d02017-11-22 12:53:08 -0800139 for _, file := range reader.File {
140 var newName string
141 if match, err := pathtools.Match(input, file.Name); err != nil {
142 return err
143 } else if match {
144 if output == "" {
145 newName = file.Name
146 } else {
147 if pathtools.IsGlob(input) {
148 // If the input is a glob then the output is a directory.
Dan Willemsen82218f22017-06-19 16:35:00 -0700149 _, name := filepath.Split(file.Name)
150 newName = filepath.Join(output, name)
Colin Crossf3831d02017-11-22 12:53:08 -0800151 } else {
152 // Otherwise it is a file.
153 newName = output
Dan Willemsen82218f22017-06-19 16:35:00 -0700154 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700155 }
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700156 includeMatches = append(includeMatches, pair{file, newName, false})
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700157 }
158 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700159
Colin Crossf3831d02017-11-22 12:53:08 -0800160 sortMatches(includeMatches)
161 matches = append(matches, includeMatches...)
162 }
163
164 if len(includes) == 0 {
165 // implicitly match everything
166 for _, file := range reader.File {
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700167 matches = append(matches, pair{file, file.Name, false})
Colin Crossf3831d02017-11-22 12:53:08 -0800168 }
169 sortMatches(matches)
170 }
171
172 var matchesAfterExcludes []pair
173 seen := make(map[string]*zip.File)
174
175 for _, match := range matches {
176 // Filter out matches whose original file name matches an exclude filter
177 excluded := false
178 for _, exclude := range excludes {
179 if excludeMatch, err := pathtools.Match(exclude, match.File.Name); err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -0700180 return err
Colin Crossf3831d02017-11-22 12:53:08 -0800181 } else if excludeMatch {
182 excluded = true
183 break
Dan Willemsen82218f22017-06-19 16:35:00 -0700184 }
185 }
Colin Crossf3831d02017-11-22 12:53:08 -0800186
187 if excluded {
188 continue
189 }
190
191 // Check for duplicate output names, ignoring ones that come from the same input zip entry.
192 if prev, exists := seen[match.newName]; exists {
193 if prev != match.File {
194 return fmt.Errorf("multiple entries for %q with different contents", match.newName)
195 }
196 continue
197 }
198 seen[match.newName] = match.File
199
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700200 for _, u := range uncompresses {
201 if uncompressMatch, err := pathtools.Match(u, match.newName); err != nil {
202 return err
203 } else if uncompressMatch {
204 match.uncompress = true
205 break
206 }
207 }
208
Colin Crossf3831d02017-11-22 12:53:08 -0800209 matchesAfterExcludes = append(matchesAfterExcludes, match)
210 }
211
212 for _, match := range matchesAfterExcludes {
213 if setTime {
214 match.File.SetModTime(staticTime)
215 }
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700216 if match.uncompress && match.File.FileHeader.Method != zip.Store {
217 fh := match.File.FileHeader
218 fh.Name = match.newName
219 fh.Method = zip.Store
220 fh.CompressedSize64 = fh.UncompressedSize64
221
222 zw, err := writer.CreateHeaderAndroid(&fh)
223 if err != nil {
224 return err
225 }
226
227 zr, err := match.File.Open()
228 if err != nil {
229 return err
230 }
231
232 _, err = io.Copy(zw, zr)
233 zr.Close()
234 if err != nil {
235 return err
236 }
237 } else {
238 err := writer.CopyFrom(match.File, match.newName)
239 if err != nil {
240 return err
241 }
Colin Crossf3831d02017-11-22 12:53:08 -0800242 }
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700243 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700244
245 return nil
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700246}
Colin Cross8936b022017-06-23 13:00:17 -0700247
Colin Crossf3831d02017-11-22 12:53:08 -0800248func includeSplit(s string) (string, string) {
249 split := strings.SplitN(s, ":", 2)
250 if len(split) == 2 {
251 return split[0], split[1]
252 } else {
253 return split[0], ""
254 }
255}
256
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700257type multiFlag []string
Colin Crossf3831d02017-11-22 12:53:08 -0800258
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700259func (e *multiFlag) String() string {
Colin Crossf3831d02017-11-22 12:53:08 -0800260 return strings.Join(*e, " ")
261}
262
Colin Crossb1a5e9c2018-10-07 21:30:12 -0700263func (e *multiFlag) Set(s string) error {
Colin Crossf3831d02017-11-22 12:53:08 -0800264 *e = append(*e, s)
265 return nil
Colin Cross8936b022017-06-23 13:00:17 -0700266}