blob: fb2fa620e7b123d498aaaf96e37c0b5c65041201 [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
27 "android/soong/third_party/zip"
28)
29
30var (
Dan Willemsen82218f22017-06-19 16:35:00 -070031 input = flag.String("i", "", "zip file to read from")
32 output = flag.String("o", "", "output file")
33 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 -070034 sortJava = flag.Bool("j", false, "sort using jar ordering within each glob (META-INF/MANIFEST.MF first)")
Dan Willemsen82218f22017-06-19 16:35:00 -070035 setTime = flag.Bool("t", false, "set timestamps to 2009-01-01 00:00:00")
36
37 staticTime = time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070038)
39
Dan Willemsen3bf1a082016-08-03 00:35:25 -070040func main() {
Dan Willemsen82218f22017-06-19 16:35:00 -070041 flag.Usage = func() {
Colin Cross8936b022017-06-23 13:00:17 -070042 fmt.Fprintln(os.Stderr, "usage: zip2zip -i zipfile -o zipfile [-s|-j] [-t] [filespec]...")
Dan Willemsen82218f22017-06-19 16:35:00 -070043 flag.PrintDefaults()
44 fmt.Fprintln(os.Stderr, " filespec:")
45 fmt.Fprintln(os.Stderr, " <name>")
46 fmt.Fprintln(os.Stderr, " <in_name>:<out_name>")
47 fmt.Fprintln(os.Stderr, " <glob>:<out_dir>/")
48 fmt.Fprintln(os.Stderr, "")
49 fmt.Fprintln(os.Stderr, "<glob> uses the rules at https://golang.org/pkg/path/filepath/#Match")
50 fmt.Fprintln(os.Stderr, "As a special exception, '**' is supported to specify all files in the input zip")
51 fmt.Fprintln(os.Stderr, "")
52 fmt.Fprintln(os.Stderr, "Files will be copied with their existing compression from the input zipfile to")
53 fmt.Fprintln(os.Stderr, "the output zipfile, in the order of filespec arguments")
54 }
55
Dan Willemsen3bf1a082016-08-03 00:35:25 -070056 flag.Parse()
57
58 if flag.NArg() == 0 || *input == "" || *output == "" {
Dan Willemsen82218f22017-06-19 16:35:00 -070059 flag.Usage()
60 os.Exit(1)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070061 }
62
Dan Willemsen82218f22017-06-19 16:35:00 -070063 log.SetFlags(log.Lshortfile)
64
Dan Willemsen3bf1a082016-08-03 00:35:25 -070065 reader, err := zip.OpenReader(*input)
66 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070067 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070068 }
69 defer reader.Close()
70
71 output, err := os.Create(*output)
72 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070073 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070074 }
75 defer output.Close()
76
77 writer := zip.NewWriter(output)
78 defer func() {
79 err := writer.Close()
80 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070081 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070082 }
83 }()
84
Colin Cross8936b022017-06-23 13:00:17 -070085 if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime, flag.Args()); err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070086 log.Fatal(err)
87 }
88}
89
Colin Cross8936b022017-06-23 13:00:17 -070090type pair struct {
91 *zip.File
92 newName string
93}
94
95func zip2zip(reader *zip.Reader, writer *zip.Writer, sortGlobs, sortJava, setTime bool, args []string) error {
Dan Willemsen82218f22017-06-19 16:35:00 -070096 for _, arg := range args {
Dan Willemsen3bf1a082016-08-03 00:35:25 -070097 var input string
98 var output string
99
100 // Reserve escaping for future implementation, so make sure no
101 // one is using \ and expecting a certain behavior.
102 if strings.Contains(arg, "\\") {
Dan Willemsen82218f22017-06-19 16:35:00 -0700103 return fmt.Errorf("\\ characters are not currently supported")
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700104 }
105
106 args := strings.SplitN(arg, ":", 2)
107 input = args[0]
108 if len(args) == 2 {
109 output = args[1]
110 }
111
Dan Willemsen82218f22017-06-19 16:35:00 -0700112 matches := []pair{}
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700113 if strings.IndexAny(input, "*?[") >= 0 {
Dan Willemsen82218f22017-06-19 16:35:00 -0700114 matchAll := input == "**"
115 if !matchAll && strings.Contains(input, "**") {
116 return fmt.Errorf("** is only supported on its own, not with other characters")
117 }
118
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700119 for _, file := range reader.File {
Dan Willemsen82218f22017-06-19 16:35:00 -0700120 match := matchAll
121
122 if !match {
123 var err error
124 match, err = filepath.Match(input, file.Name)
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700125 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -0700126 return err
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700127 }
128 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700129
130 if match {
131 var newName string
132 if output == "" {
133 newName = file.Name
134 } else {
135 _, name := filepath.Split(file.Name)
136 newName = filepath.Join(output, name)
137 }
138 matches = append(matches, pair{file, newName})
139 }
140 }
141
Colin Cross8936b022017-06-23 13:00:17 -0700142 if sortJava {
143 jarSort(matches)
144 } else if sortGlobs {
Dan Willemsen82218f22017-06-19 16:35:00 -0700145 sort.SliceStable(matches, func(i, j int) bool {
146 return matches[i].newName < matches[j].newName
147 })
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700148 }
149 } else {
150 if output == "" {
151 output = input
152 }
153 for _, file := range reader.File {
154 if input == file.Name {
Dan Willemsen82218f22017-06-19 16:35:00 -0700155 matches = append(matches, pair{file, output})
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700156 break
157 }
158 }
159 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700160
161 for _, match := range matches {
162 if setTime {
163 match.File.SetModTime(staticTime)
164 }
165 if err := writer.CopyFrom(match.File, match.newName); err != nil {
166 return err
167 }
168 }
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700169 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700170
171 return nil
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700172}
Colin Cross8936b022017-06-23 13:00:17 -0700173
174func jarSort(files []pair) {
175 // Treats trailing * as a prefix match
176 match := func(pattern, name string) bool {
177 if strings.HasSuffix(pattern, "*") {
178 return strings.HasPrefix(name, strings.TrimSuffix(pattern, "*"))
179 } else {
180 return name == pattern
181 }
182 }
183
184 var jarOrder = []string{
185 "META-INF/",
186 "META-INF/MANIFEST.MF",
187 "META-INF/*",
188 "*",
189 }
190
191 index := func(name string) int {
192 for i, pattern := range jarOrder {
193 if match(pattern, name) {
194 return i
195 }
196 }
197 panic(fmt.Errorf("file %q did not match any pattern", name))
198 }
199
200 sort.SliceStable(files, func(i, j int) bool {
201 diff := index(files[i].newName) - index(files[j].newName)
202 if diff == 0 {
203 return files[i].newName < files[j].newName
204 } else {
205 return diff < 0
206 }
207 })
208}