blob: 815059c900c78103ab7f56d46179cf75475b5dd9 [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")
Colin Cross06382992017-06-23 14:08:42 -070050 fmt.Fprintln(os.Stderr, "As a special exception, '**' is supported to specify all files in the input zip.")
Dan Willemsen82218f22017-06-19 16:35:00 -070051 fmt.Fprintln(os.Stderr, "")
52 fmt.Fprintln(os.Stderr, "Files will be copied with their existing compression from the input zipfile to")
Colin Cross06382992017-06-23 14:08:42 -070053 fmt.Fprintln(os.Stderr, "the output zipfile, in the order of filespec arguments.")
54 fmt.Fprintln(os.Stderr, "")
55 fmt.Fprintln(os.Stderr, "If no filepsec is provided all files are copied (equivalent to '**').")
Dan Willemsen82218f22017-06-19 16:35:00 -070056 }
57
Dan Willemsen3bf1a082016-08-03 00:35:25 -070058 flag.Parse()
59
Colin Cross06382992017-06-23 14:08:42 -070060 if *input == "" || *output == "" {
Dan Willemsen82218f22017-06-19 16:35:00 -070061 flag.Usage()
62 os.Exit(1)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070063 }
64
Dan Willemsen82218f22017-06-19 16:35:00 -070065 log.SetFlags(log.Lshortfile)
66
Dan Willemsen3bf1a082016-08-03 00:35:25 -070067 reader, err := zip.OpenReader(*input)
68 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070069 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070070 }
71 defer reader.Close()
72
73 output, err := os.Create(*output)
74 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070075 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070076 }
77 defer output.Close()
78
79 writer := zip.NewWriter(output)
80 defer func() {
81 err := writer.Close()
82 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070083 log.Fatal(err)
Dan Willemsen3bf1a082016-08-03 00:35:25 -070084 }
85 }()
86
Colin Cross8936b022017-06-23 13:00:17 -070087 if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime, flag.Args()); err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -070088 log.Fatal(err)
89 }
90}
91
Colin Cross8936b022017-06-23 13:00:17 -070092type pair struct {
93 *zip.File
94 newName string
95}
96
97func zip2zip(reader *zip.Reader, writer *zip.Writer, sortGlobs, sortJava, setTime bool, args []string) error {
Colin Cross06382992017-06-23 14:08:42 -070098 if len(args) == 0 {
99 // If no filespec is provided, default to copying everything
100 args = []string{"**"}
101 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700102 for _, arg := range args {
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700103 var input string
104 var output string
105
106 // Reserve escaping for future implementation, so make sure no
107 // one is using \ and expecting a certain behavior.
108 if strings.Contains(arg, "\\") {
Dan Willemsen82218f22017-06-19 16:35:00 -0700109 return fmt.Errorf("\\ characters are not currently supported")
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700110 }
111
112 args := strings.SplitN(arg, ":", 2)
113 input = args[0]
114 if len(args) == 2 {
115 output = args[1]
116 }
117
Dan Willemsen82218f22017-06-19 16:35:00 -0700118 matches := []pair{}
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700119 if strings.IndexAny(input, "*?[") >= 0 {
Dan Willemsen82218f22017-06-19 16:35:00 -0700120 matchAll := input == "**"
121 if !matchAll && strings.Contains(input, "**") {
122 return fmt.Errorf("** is only supported on its own, not with other characters")
123 }
124
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700125 for _, file := range reader.File {
Dan Willemsen82218f22017-06-19 16:35:00 -0700126 match := matchAll
127
128 if !match {
129 var err error
130 match, err = filepath.Match(input, file.Name)
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700131 if err != nil {
Dan Willemsen82218f22017-06-19 16:35:00 -0700132 return err
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700133 }
134 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700135
136 if match {
137 var newName string
138 if output == "" {
139 newName = file.Name
140 } else {
141 _, name := filepath.Split(file.Name)
142 newName = filepath.Join(output, name)
143 }
144 matches = append(matches, pair{file, newName})
145 }
146 }
147
Colin Cross8936b022017-06-23 13:00:17 -0700148 if sortJava {
149 jarSort(matches)
150 } else if sortGlobs {
Dan Willemsen82218f22017-06-19 16:35:00 -0700151 sort.SliceStable(matches, func(i, j int) bool {
152 return matches[i].newName < matches[j].newName
153 })
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700154 }
155 } else {
156 if output == "" {
157 output = input
158 }
159 for _, file := range reader.File {
160 if input == file.Name {
Dan Willemsen82218f22017-06-19 16:35:00 -0700161 matches = append(matches, pair{file, output})
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700162 break
163 }
164 }
165 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700166
167 for _, match := range matches {
168 if setTime {
169 match.File.SetModTime(staticTime)
170 }
171 if err := writer.CopyFrom(match.File, match.newName); err != nil {
172 return err
173 }
174 }
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700175 }
Dan Willemsen82218f22017-06-19 16:35:00 -0700176
177 return nil
Dan Willemsen3bf1a082016-08-03 00:35:25 -0700178}
Colin Cross8936b022017-06-23 13:00:17 -0700179
180func jarSort(files []pair) {
181 // Treats trailing * as a prefix match
182 match := func(pattern, name string) bool {
183 if strings.HasSuffix(pattern, "*") {
184 return strings.HasPrefix(name, strings.TrimSuffix(pattern, "*"))
185 } else {
186 return name == pattern
187 }
188 }
189
190 var jarOrder = []string{
191 "META-INF/",
192 "META-INF/MANIFEST.MF",
193 "META-INF/*",
194 "*",
195 }
196
197 index := func(name string) int {
198 for i, pattern := range jarOrder {
199 if match(pattern, name) {
200 return i
201 }
202 }
203 panic(fmt.Errorf("file %q did not match any pattern", name))
204 }
205
206 sort.SliceStable(files, func(i, j int) bool {
207 diff := index(files[i].newName) - index(files[j].newName)
208 if diff == 0 {
209 return files[i].newName < files[j].newName
210 } else {
211 return diff < 0
212 }
213 })
214}