| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 1 | // 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 |  | 
|  | 15 | package main | 
|  | 16 |  | 
|  | 17 | import ( | 
|  | 18 | "flag" | 
|  | 19 | "fmt" | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 20 | "log" | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 21 | "os" | 
|  | 22 | "path/filepath" | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 23 | "sort" | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 24 | "strings" | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 25 | "time" | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 26 |  | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 27 | "github.com/google/blueprint/pathtools" | 
|  | 28 |  | 
| Jeff Gaston | 01547b2 | 2017-08-21 20:13:28 -0700 | [diff] [blame] | 29 | "android/soong/jar" | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 30 | "android/soong/third_party/zip" | 
|  | 31 | ) | 
|  | 32 |  | 
|  | 33 | var ( | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 34 | 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 Cross | 8936b02 | 2017-06-23 13:00:17 -0700 | [diff] [blame] | 37 | sortJava  = flag.Bool("j", false, "sort using jar ordering within each glob (META-INF/MANIFEST.MF first)") | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 38 | 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 Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 41 |  | 
|  | 42 | excludes excludeArgs | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 43 | ) | 
|  | 44 |  | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 45 | func init() { | 
|  | 46 | flag.Var(&excludes, "x", "exclude a filespec from the output") | 
|  | 47 | } | 
|  | 48 |  | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 49 | func main() { | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 50 | flag.Usage = func() { | 
| Colin Cross | 8936b02 | 2017-06-23 13:00:17 -0700 | [diff] [blame] | 51 | fmt.Fprintln(os.Stderr, "usage: zip2zip -i zipfile -o zipfile [-s|-j] [-t] [filespec]...") | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 52 | flag.PrintDefaults() | 
|  | 53 | fmt.Fprintln(os.Stderr, "  filespec:") | 
|  | 54 | fmt.Fprintln(os.Stderr, "    <name>") | 
|  | 55 | fmt.Fprintln(os.Stderr, "    <in_name>:<out_name>") | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 56 | fmt.Fprintln(os.Stderr, "    <glob>[:<out_dir>]") | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 57 | fmt.Fprintln(os.Stderr, "") | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 58 | fmt.Fprintln(os.Stderr, "<glob> uses the rules at https://godoc.org/github.com/google/blueprint/pathtools/#Match") | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 59 | fmt.Fprintln(os.Stderr, "") | 
|  | 60 | fmt.Fprintln(os.Stderr, "Files will be copied with their existing compression from the input zipfile to") | 
| Colin Cross | 0638299 | 2017-06-23 14:08:42 -0700 | [diff] [blame] | 61 | fmt.Fprintln(os.Stderr, "the output zipfile, in the order of filespec arguments.") | 
|  | 62 | fmt.Fprintln(os.Stderr, "") | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 63 | fmt.Fprintln(os.Stderr, "If no filepsec is provided all files and directories are copied.") | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 64 | } | 
|  | 65 |  | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 66 | flag.Parse() | 
|  | 67 |  | 
| Colin Cross | 0638299 | 2017-06-23 14:08:42 -0700 | [diff] [blame] | 68 | if *input == "" || *output == "" { | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 69 | flag.Usage() | 
|  | 70 | os.Exit(1) | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 71 | } | 
|  | 72 |  | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 73 | log.SetFlags(log.Lshortfile) | 
|  | 74 |  | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 75 | reader, err := zip.OpenReader(*input) | 
|  | 76 | if err != nil { | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 77 | log.Fatal(err) | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 78 | } | 
|  | 79 | defer reader.Close() | 
|  | 80 |  | 
|  | 81 | output, err := os.Create(*output) | 
|  | 82 | if err != nil { | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 83 | log.Fatal(err) | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 84 | } | 
|  | 85 | defer output.Close() | 
|  | 86 |  | 
|  | 87 | writer := zip.NewWriter(output) | 
|  | 88 | defer func() { | 
|  | 89 | err := writer.Close() | 
|  | 90 | if err != nil { | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 91 | log.Fatal(err) | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 92 | } | 
|  | 93 | }() | 
|  | 94 |  | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 95 | if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime, | 
|  | 96 | flag.Args(), excludes); err != nil { | 
|  | 97 |  | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 98 | log.Fatal(err) | 
|  | 99 | } | 
|  | 100 | } | 
|  | 101 |  | 
| Colin Cross | 8936b02 | 2017-06-23 13:00:17 -0700 | [diff] [blame] | 102 | type pair struct { | 
|  | 103 | *zip.File | 
|  | 104 | newName string | 
|  | 105 | } | 
|  | 106 |  | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 107 | func zip2zip(reader *zip.Reader, writer *zip.Writer, sortOutput, sortJava, setTime bool, | 
|  | 108 | includes []string, excludes []string) error { | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 109 |  | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 110 | 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 Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 125 | // Reserve escaping for future implementation, so make sure no | 
|  | 126 | // one is using \ and expecting a certain behavior. | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 127 | if strings.Contains(include, "\\") { | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 128 | return fmt.Errorf("\\ characters are not currently supported") | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 129 | } | 
|  | 130 |  | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 131 | input, output := includeSplit(include) | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 132 |  | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 133 | var includeMatches []pair | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 134 |  | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 135 | 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 Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 145 | _, name := filepath.Split(file.Name) | 
|  | 146 | newName = filepath.Join(output, name) | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 147 | } else { | 
|  | 148 | // Otherwise it is a file. | 
|  | 149 | newName = output | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 150 | } | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 151 | } | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 152 | includeMatches = append(includeMatches, pair{file, newName}) | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 153 | } | 
|  | 154 | } | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 155 |  | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 156 | 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 Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 176 | return err | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 177 | } else if excludeMatch { | 
|  | 178 | excluded = true | 
|  | 179 | break | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 180 | } | 
|  | 181 | } | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 182 |  | 
|  | 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 Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 206 | } | 
| Dan Willemsen | 82218f2 | 2017-06-19 16:35:00 -0700 | [diff] [blame] | 207 |  | 
|  | 208 | return nil | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 209 | } | 
| Colin Cross | 8936b02 | 2017-06-23 13:00:17 -0700 | [diff] [blame] | 210 |  | 
| Colin Cross | f3831d0 | 2017-11-22 12:53:08 -0800 | [diff] [blame] | 211 | func 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 |  | 
|  | 220 | type excludeArgs []string | 
|  | 221 |  | 
|  | 222 | func (e *excludeArgs) String() string { | 
|  | 223 | return strings.Join(*e, " ") | 
|  | 224 | } | 
|  | 225 |  | 
|  | 226 | func (e *excludeArgs) Set(s string) error { | 
|  | 227 | *e = append(*e, s) | 
|  | 228 | return nil | 
| Colin Cross | 8936b02 | 2017-06-23 13:00:17 -0700 | [diff] [blame] | 229 | } |