Home | History | Annotate | Download | only in soong_jar
      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