Supported minor features in soong_zip

1. Added ability to keep the mixed "-f"/"-l" order as same as
command-line flags order.

2. Added "-s" flag to specify which target file within zip is stored uncompressed.

Test: manual
Change-Id: I338b25a7bd6bf1b7e9cc29ad3324575167630fb7
diff --git a/cmd/soong_zip/soong_zip.go b/cmd/soong_zip/soong_zip.go
index 3e62016..8407788 100644
--- a/cmd/soong_zip/soong_zip.go
+++ b/cmd/soong_zip/soong_zip.go
@@ -55,33 +55,76 @@
 }
 
 type fileArg struct {
-	rootPrefix, relativeRoot, file string
+	pathPrefixInZip, sourcePrefixToStrip string
+	sourceFiles                          []string
 }
 
 type pathMapping struct {
 	dest, src string
+	zipMethod uint16
+}
+
+type uniqueSet map[string]bool
+
+func (u *uniqueSet) String() string {
+	return `""`
+}
+
+func (u *uniqueSet) Set(s string) error {
+	if _, found := (*u)[s]; found {
+		return fmt.Errorf("File %q was specified twice as a file to not deflate", s)
+	} else {
+		(*u)[s] = true
+	}
+
+	return nil
 }
 
 type fileArgs []fileArg
 
-func (l *fileArgs) String() string {
+type file struct{}
+
+type listFiles struct{}
+
+func (f *file) String() string {
 	return `""`
 }
 
-func (l *fileArgs) Set(s string) error {
+func (f *file) Set(s string) error {
 	if *relativeRoot == "" {
 		return fmt.Errorf("must pass -C before -f or -l")
 	}
 
-	*l = append(*l,
-		fileArg{rootPrefix: filepath.Clean(*rootPrefix),
-			relativeRoot: filepath.Clean(*relativeRoot),
-			file:         s})
+	fArgs = append(fArgs, fileArg{
+		pathPrefixInZip:     filepath.Clean(*rootPrefix),
+		sourcePrefixToStrip: filepath.Clean(*relativeRoot),
+		sourceFiles:         []string{s},
+	})
+
 	return nil
 }
 
-func (l *fileArgs) Get() interface{} {
-	return l
+func (l *listFiles) String() string {
+	return `""`
+}
+
+func (l *listFiles) Set(s string) error {
+	if *relativeRoot == "" {
+		return fmt.Errorf("must pass -C before -f or -l")
+	}
+
+	list, err := ioutil.ReadFile(s)
+	if err != nil {
+		return err
+	}
+
+	fArgs = append(fArgs, fileArg{
+		pathPrefixInZip:     filepath.Clean(*rootPrefix),
+		sourcePrefixToStrip: filepath.Clean(*relativeRoot),
+		sourceFiles:         strings.Split(string(list), "\n"),
+	})
+
+	return nil
 }
 
 var (
@@ -93,16 +136,17 @@
 	parallelJobs = flag.Int("j", runtime.NumCPU(), "number of parallel threads to use")
 	compLevel    = flag.Int("L", 5, "deflate compression level (0-9)")
 
-	listFiles fileArgs
-	files     fileArgs
+	fArgs            fileArgs
+	nonDeflatedFiles = make(uniqueSet)
 
 	cpuProfile = flag.String("cpuprofile", "", "write cpu profile to file")
 	traceFile  = flag.String("trace", "", "write trace to file")
 )
 
 func init() {
-	flag.Var(&listFiles, "l", "file containing list of .class files")
-	flag.Var(&files, "f", "file to include in zip")
+	flag.Var(&listFiles{}, "l", "file containing list of .class files")
+	flag.Var(&file{}, "f", "file to include in zip")
+	flag.Var(&nonDeflatedFiles, "s", "file path to be stored within the zip without compression")
 }
 
 func usage() {
@@ -176,30 +220,15 @@
 	pathMappings := []pathMapping{}
 	set := make(map[string]string)
 
-	// load listFiles, which specify other files to include.
-	for _, l := range listFiles {
-		list, err := ioutil.ReadFile(l.file)
-		if err != nil {
-			fmt.Fprintln(os.Stderr, err.Error())
-			os.Exit(1)
-		}
-		srcs := strings.Split(string(list), "\n")
-		for _, src := range srcs {
-			if err := fillPathPairs(l.rootPrefix, l.relativeRoot, src,
-				set, &pathMappings); err != nil {
+	for _, fa := range fArgs {
+		for _, src := range fa.sourceFiles {
+			if err := fillPathPairs(fa.pathPrefixInZip,
+				fa.sourcePrefixToStrip, src, set, &pathMappings); err != nil {
 				log.Fatal(err)
 			}
 		}
 	}
 
-	// also include the usual files that are to be added directly.
-	for _, f := range files {
-		if err := fillPathPairs(f.rootPrefix, f.relativeRoot,
-			f.file, set, &pathMappings); err != nil {
-			log.Fatal(err)
-		}
-	}
-
 	err := w.write(*out, pathMappings, *manifest)
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err.Error())
@@ -227,7 +256,12 @@
 		set[dest] = src
 	}
 
-	*pathMappings = append(*pathMappings, pathMapping{dest: dest, src: src})
+	zipMethod := zip.Deflate
+	if _, found := nonDeflatedFiles[dest]; found {
+		zipMethod = zip.Store
+	}
+	*pathMappings = append(*pathMappings,
+		pathMapping{dest: dest, src: src, zipMethod: zipMethod})
 
 	return nil
 }
@@ -269,7 +303,7 @@
 		defer close(z.writeOps)
 
 		for _, ele := range pathMappings {
-			err = z.writeFile(ele.dest, ele.src)
+			err = z.writeFile(ele.dest, ele.src, ele.zipMethod)
 			if err != nil {
 				z.errors <- err
 				return
@@ -277,7 +311,7 @@
 		}
 
 		if manifest != "" {
-			err = z.writeFile("META-INF/MANIFEST.MF", manifest)
+			err = z.writeFile("META-INF/MANIFEST.MF", manifest, zip.Deflate)
 			if err != nil {
 				z.errors <- err
 				return
@@ -371,7 +405,7 @@
 	}
 }
 
-func (z *zipWriter) writeFile(dest, src string) error {
+func (z *zipWriter) writeFile(dest, src string, method uint16) error {
 	var fileSize int64
 	var executable bool
 
@@ -407,7 +441,7 @@
 	ze := &zipEntry{
 		fh: &zip.FileHeader{
 			Name:   dest,
-			Method: zip.Deflate,
+			Method: method,
 
 			UncompressedSize64: uint64(fileSize),
 		},
@@ -424,7 +458,7 @@
 
 	exec := z.rateLimit.RequestExecution()
 
-	if fileSize >= minParallelFileSize {
+	if method == zip.Deflate && fileSize >= minParallelFileSize {
 		wg := new(sync.WaitGroup)
 
 		// Allocate enough buffer to hold all readers. We'll limit
@@ -555,33 +589,55 @@
 		return
 	}
 
-	compressed, err := z.compressBlock(r, nil, true)
+	readFile := func(r *os.File) ([]byte, error) {
+		_, err = r.Seek(0, 0)
+		if err != nil {
+			return nil, err
+		}
+
+		buf, err := ioutil.ReadAll(r)
+		if err != nil {
+			return nil, err
+		}
+
+		return buf, nil
+	}
 
 	ze.futureReaders = make(chan chan io.Reader, 1)
 	futureReader := make(chan io.Reader, 1)
 	ze.futureReaders <- futureReader
 	close(ze.futureReaders)
 
-	if uint64(compressed.Len()) < ze.fh.UncompressedSize64 {
-		futureReader <- compressed
-		bufSize = compressed.Len()
+	if ze.fh.Method == zip.Deflate {
+		compressed, err := z.compressBlock(r, nil, true)
+		if err != nil {
+			z.errors <- err
+			return
+		}
+		if uint64(compressed.Len()) < ze.fh.UncompressedSize64 {
+			futureReader <- compressed
+			bufSize = compressed.Len()
+		} else {
+			buf, err := readFile(r)
+			if err != nil {
+				z.errors <- err
+				return
+			}
+			ze.fh.Method = zip.Store
+			futureReader <- bytes.NewReader(buf)
+			bufSize = int(ze.fh.UncompressedSize64)
+		}
 	} else {
-		_, err = r.Seek(0, 0)
+		buf, err := readFile(r)
 		if err != nil {
 			z.errors <- err
 			return
 		}
-
-		buf, err := ioutil.ReadAll(r)
-		if err != nil {
-			z.errors <- err
-			return
-		}
-
 		ze.fh.Method = zip.Store
 		futureReader <- bytes.NewReader(buf)
 		bufSize = int(ze.fh.UncompressedSize64)
 	}
+
 	exec.Finish(bufSize)
 	close(futureReader)