blob: e8ea9b949937d0002fdd492cc156c826e1e7866d [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"
Dan Willemsen82218f22017-06-19 16:35:00 -070020 "log"
Dan Willemsen3bf1a082016-08-03 00:35:25 -070021 "os"
22 "path/filepath"
Dan Willemsen82218f22017-06-19 16:35:00 -070023 "sort"
Dan Willemsen3bf1a082016-08-03 00:35:25 -070024 "strings"
Dan Willemsen82218f22017-06-19 16:35:00 -070025 "time"
Dan Willemsen3bf1a082016-08-03 00:35:25 -070026
Colin Crossf3831d02017-11-22 12:53:08 -080027 "github.com/google/blueprint/pathtools"
28
Jeff Gaston01547b22017-08-21 20:13:28 -070029 "android/soong/jar"
Dan Willemsen3bf1a082016-08-03 00:35:25 -070030 "android/soong/third_party/zip"
31)
32
33var (
Dan Willemsen82218f22017-06-19 16:35:00 -070034 input = flag.String("i", "", "zip file to read from")
35 output = flag.String("o", "", "output file")
36 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 -070037 sortJava = flag.Bool("j", false, "sort using jar ordering within each glob (META-INF/MANIFEST.MF first)")
Dan Willemsen82218f22017-06-19 16:35:00 -070038 setTime = flag.Bool("t", false, "set timestamps to 2009-01-01 00:00:00")
39
40 staticTime = time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC)
Colin Crossf3831d02017-11-22 12:53:08 -080041
42 excludes excludeArgs
Dan Willemsen3bf1a082016-08-03 00:35:25 -070043)
44
Colin Crossf3831d02017-11-22 12:53:08 -080045func init() {
46 flag.Var(&excludes, "x", "exclude a filespec from the output")
47}
48
Dan Willemsen3bf1a082016-08-03 00:35:25 -070049func main() {
Dan Willemsen82218f22017-06-19 16:35:00 -070050 flag.Usage = func() {
Colin Cross8936b022017-06-23 13:00:17 -070051 fmt.Fprintln(os.Stderr, "usage: zip2zip -i zipfile -o zipfile [-s|-j] [-t] [filespec]...")
Dan Willemsen82218f22017-06-19 16:35:00 -070052 flag.PrintDefaults()
53 fmt.Fprintln(os.Stderr, " filespec:")
54 fmt.Fprintln(os.Stderr, " <name>")
55 fmt.Fprintln(os.Stderr, " <in_name>:<out_name>")
Colin Crossf3831d02017-11-22 12:53:08 -080056 fmt.Fprintln(os.Stderr, " <glob>[:<out_dir>]")
Dan Willemsen82218f22017-06-19 16:35:00 -070057 fmt.Fprintln(os.Stderr, "")
Colin Crossf3831d02017-11-22 12:53:08 -080058 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 -070059 fmt.Fprintln(os.Stderr, "")
60 fmt.Fprintln(os.Stderr, "Files will be copied with their existing compression from the input zipfile to")
Colin Cross06382992017-06-23 14:08:42 -070061 fmt.Fprintln(os.Stderr, "the output zipfile, in the order of filespec arguments.")
62 fmt.Fprintln(os.Stderr, "")
Colin Crossf3831d02017-11-22 12:53:08 -080063 fmt.Fprintln(os.Stderr, "If no filepsec is provided all files and directories are copied.")
Dan Willemsen82218f22017-06-19 16:35:00 -070064 }
65
Dan Willemsen3bf1a082016-08-03 00:35:25 -070066 flag.Parse()
67
Colin Cross06382992017-06-23 14:08:42 -070068 if *input == "" || *output == "" {
Dan Willemsen82218f22017-06-19 16:35:00 -070069 flag.Usage()
70 os.Exit(1)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070071 }
72
Dan Willemsen82218f22017-06-19 16:35:00 -070073 log.SetFlags(log.Lshortfile)
74
Dan Willemsen3bf1a082016-08-03 00:35:25 -070075 reader, err := zip.OpenReader(*input)
76 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070077 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070078 }
79 defer reader.Close()
80
81 output, err := os.Create(*output)
82 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070083 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070084 }
85 defer output.Close()
86
87 writer := zip.NewWriter(output)
88 defer func() {
89 err := writer.Close()
90 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070091 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070092 }
93 }()
94
Colin Crossf3831d02017-11-22 12:53:08 -080095 if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime,
96 flag.Args(), excludes); err != nil {
97
Dan Willemsen82218f22017-06-19 16:35:00 -070098 log.Fatal(err)
99 }
100}
101
Colin Cross8936b022017-06-23 13:00:17 -0700102type pair struct {
103 *zip.File
104 newName string
105}
106
Colin Crossf3831d02017-11-22 12:53:08 -0800107func zip2zip(reader *zip.Reader, writer *zip.Writer, sortOutput, sortJava, setTime bool,
108 includes []string, excludes []string) error {
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700109
Colin Crossf3831d02017-11-22 12:53:08 -0800110 matches := []pair{}
111
112 sortMatches := func(matches []pair) {
113 if sortJava {
114 sort.SliceStable(matches, func(i, j int) bool {
115 return jar.EntryNamesLess(matches[i].newName, matches[j].newName)
116 })
117 } else if sortOutput {
118 sort.SliceStable(matches, func(i, j int) bool {
119 return matches[i].newName < matches[j].newName
120 })
121 }
122 }
123
124 for _, include := range includes {
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700125 // Reserve escaping for future implementation, so make sure no
126 // one is using \ and expecting a certain behavior.
Colin Crossf3831d02017-11-22 12:53:08 -0800127 if strings.Contains(include, "\\") {
Dan Willemsen82218f22017-06-19 16:35:00 -0700128 return fmt.Errorf("\\ characters are not currently supported")
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700129 }
130
Colin Crossf3831d02017-11-22 12:53:08 -0800131 input, output := includeSplit(include)
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.
Dan Willemsen82218f22017-06-19 16:35:00 -0700145 _, name := filepath.Split(file.Name)
146 newName = filepath.Join(output, name)
Colin Crossf3831d02017-11-22 12:53:08 -0800147 } else {
148 // Otherwise it is a file.
149 newName = output
Dan Willemsen82218f22017-06-19 16:35:00 -0700150 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700151 }
Colin Crossf3831d02017-11-22 12:53:08 -0800152 includeMatches = append(includeMatches, pair{file, newName})
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700153 }
154 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700155
Colin Crossf3831d02017-11-22 12:53:08 -0800156 sortMatches(includeMatches)
157 matches = append(matches, includeMatches...)
158 }
159
160 if len(includes) == 0 {
161 // implicitly match everything
162 for _, file := range reader.File {
163 matches = append(matches, pair{file, file.Name})
164 }
165 sortMatches(matches)
166 }
167
168 var matchesAfterExcludes []pair
169 seen := make(map[string]*zip.File)
170
171 for _, match := range matches {
172 // Filter out matches whose original file name matches an exclude filter
173 excluded := false
174 for _, exclude := range excludes {
175 if excludeMatch, err := pathtools.Match(exclude, match.File.Name); err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -0700176 return err
Colin Crossf3831d02017-11-22 12:53:08 -0800177 } else if excludeMatch {
178 excluded = true
179 break
Dan Willemsen82218f22017-06-19 16:35:00 -0700180 }
181 }
Colin Crossf3831d02017-11-22 12:53:08 -0800182
183 if excluded {
184 continue
185 }
186
187 // Check for duplicate output names, ignoring ones that come from the same input zip entry.
188 if prev, exists := seen[match.newName]; exists {
189 if prev != match.File {
190 return fmt.Errorf("multiple entries for %q with different contents", match.newName)
191 }
192 continue
193 }
194 seen[match.newName] = match.File
195
196 matchesAfterExcludes = append(matchesAfterExcludes, match)
197 }
198
199 for _, match := range matchesAfterExcludes {
200 if setTime {
201 match.File.SetModTime(staticTime)
202 }
203 if err := writer.CopyFrom(match.File, match.newName); err != nil {
204 return err
205 }
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700206 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700207
208 return nil
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700209}
Colin Cross8936b022017-06-23 13:00:17 -0700210
Colin Crossf3831d02017-11-22 12:53:08 -0800211func includeSplit(s string) (string, string) {
212 split := strings.SplitN(s, ":", 2)
213 if len(split) == 2 {
214 return split[0], split[1]
215 } else {
216 return split[0], ""
217 }
218}
219
220type excludeArgs []string
221
222func (e *excludeArgs) String() string {
223 return strings.Join(*e, " ")
224}
225
226func (e *excludeArgs) Set(s string) error {
227 *e = append(*e, s)
228 return nil
Colin Cross8936b022017-06-23 13:00:17 -0700229}