1 // Copyright 2015 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 "archive/zip" 19 "flag" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "strings" 26 "time" 27 ) 28 29 type fileArg struct { 30 relativeRoot, file string 31 } 32 33 type fileArgs []fileArg 34 35 func (l *fileArgs) String() string { 36 return `""` 37 } 38 39 func (l *fileArgs) Set(s string) error { 40 if *relativeRoot == "" { 41 return fmt.Errorf("must pass -C before -f") 42 } 43 44 *l = append(*l, fileArg{*relativeRoot, s}) 45 return nil 46 } 47 48 func (l *fileArgs) Get() interface{} { 49 return l 50 } 51 52 var ( 53 out = flag.String("o", "", "file to write jar file to") 54 manifest = flag.String("m", "", "input manifest file name") 55 directories = flag.Bool("d", false, "include directories in jar") 56 relativeRoot = flag.String("C", "", "path to use as relative root of files in next -f or -l argument") 57 listFiles fileArgs 58 files fileArgs 59 ) 60 61 func init() { 62 flag.Var(&listFiles, "l", "file containing list of .class files") 63 flag.Var(&files, "f", "file to include in jar") 64 } 65 66 func usage() { 67 fmt.Fprintf(os.Stderr, "usage: soong_jar -o jarfile [-m manifest] -C dir [-f|-l file]...\n") 68 flag.PrintDefaults() 69 os.Exit(2) 70 } 71 72 type zipWriter struct { 73 time time.Time 74 createdDirs map[string]bool 75 directories bool 76 77 w *zip.Writer 78 } 79 80 func main() { 81 flag.Parse() 82 83 if *out == "" { 84 fmt.Fprintf(os.Stderr, "error: -o is required\n") 85 usage() 86 } 87 88 w := &zipWriter{ 89 time: time.Now(), 90 createdDirs: make(map[string]bool), 91 directories: *directories, 92 } 93 94 // TODO: Go's zip implementation doesn't support increasing the compression level yet 95 err := w.write(*out, listFiles, *manifest) 96 if err != nil { 97 fmt.Fprintln(os.Stderr, err.Error()) 98 os.Exit(1) 99 } 100 } 101 102 func (z *zipWriter) write(out string, listFiles fileArgs, manifest string) error { 103 f, err := os.Create(out) 104 if err != nil { 105 return err 106 } 107 108 defer f.Close() 109 defer func() { 110 if err != nil { 111 os.Remove(out) 112 } 113 }() 114 115 z.w = zip.NewWriter(f) 116 defer z.w.Close() 117 118 for _, listFile := range listFiles { 119 err = z.writeListFile(listFile) 120 if err != nil { 121 return err 122 } 123 } 124 125 for _, file := range files { 126 err = z.writeRelFile(file.relativeRoot, file.file) 127 if err != nil { 128 return err 129 } 130 } 131 132 if manifest != "" { 133 err = z.writeFile("META-INF/MANIFEST.MF", manifest) 134 if err != nil { 135 return err 136 } 137 } 138 139 return nil 140 } 141 142 func (z *zipWriter) writeListFile(listFile fileArg) error { 143 list, err := ioutil.ReadFile(listFile.file) 144 if err != nil { 145 return err 146 } 147 148 files := strings.Split(string(list), "\n") 149 150 for _, file := range files { 151 file = strings.TrimSpace(file) 152 if file == "" { 153 continue 154 } 155 err = z.writeRelFile(listFile.relativeRoot, file) 156 if err != nil { 157 return err 158 } 159 } 160 161 return nil 162 } 163 164 func (z *zipWriter) writeRelFile(root, file string) error { 165 rel, err := filepath.Rel(root, file) 166 if err != nil { 167 return err 168 } 169 170 err = z.writeFile(rel, file) 171 if err != nil { 172 return err 173 } 174 175 return nil 176 } 177 178 func (z *zipWriter) writeFile(rel, file string) error { 179 if s, _ := os.Stat(file); s.IsDir() { 180 if z.directories { 181 return z.writeDirectory(file) 182 } 183 return nil 184 } 185 186 if z.directories { 187 dir, _ := filepath.Split(rel) 188 err := z.writeDirectory(dir) 189 if err != nil { 190 return err 191 } 192 } 193 194 fileHeader := &zip.FileHeader{ 195 Name: rel, 196 Method: zip.Deflate, 197 } 198 fileHeader.SetModTime(z.time) 199 200 out, err := z.w.CreateHeader(fileHeader) 201 if err != nil { 202 return err 203 } 204 205 in, err := os.Open(file) 206 if err != nil { 207 return err 208 } 209 defer in.Close() 210 211 _, err = io.Copy(out, in) 212 if err != nil { 213 return err 214 } 215 216 return nil 217 } 218 219 func (z *zipWriter) writeDirectory(dir string) error { 220 for dir != "" && !z.createdDirs[dir] { 221 z.createdDirs[dir] = true 222 223 dirHeader := &zip.FileHeader{ 224 Name: dir, 225 } 226 dirHeader.SetMode(os.ModeDir) 227 dirHeader.SetModTime(z.time) 228 229 _, err := z.w.CreateHeader(dirHeader) 230 if err != nil { 231 return err 232 } 233 234 dir, _ = filepath.Split(dir) 235 } 236 237 return nil 238 } 239