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// soong_zip is a utility used during the build to create a zip archive by pulling the entries from
16// various sources:
17//  * explicitly specified files
18//  * files whose paths are read from a file
19//  * directories traversed recursively
20// It can optionally change the recorded path of an entry.
21
22package main
23
24import (
25	"flag"
26	"fmt"
27	"os"
28	"runtime"
29	"runtime/pprof"
30	"runtime/trace"
31	"strconv"
32	"strings"
33
34	"android/soong/response"
35	"android/soong/zip"
36)
37
38type uniqueSet map[string]bool
39
40func (u *uniqueSet) String() string {
41	return `""`
42}
43
44func (u *uniqueSet) Set(s string) error {
45	if _, found := (*u)[s]; found {
46		return fmt.Errorf("File %q was specified twice as a file to not deflate", s)
47	} else {
48		(*u)[s] = true
49	}
50
51	return nil
52}
53
54type file struct{}
55
56func (file) String() string { return `""` }
57
58func (file) Set(s string) error {
59	fileArgsBuilder.File(s)
60	return nil
61}
62
63type listFiles struct{}
64
65func (listFiles) String() string { return `""` }
66
67func (listFiles) Set(s string) error {
68	fileArgsBuilder.List(s)
69	return nil
70}
71
72type rspFiles struct{}
73
74func (rspFiles) String() string { return `""` }
75
76func (rspFiles) Set(s string) error {
77	fileArgsBuilder.RspFile(s)
78	return nil
79}
80
81type dir struct{}
82
83func (dir) String() string { return `""` }
84
85func (dir) Set(s string) error {
86	fileArgsBuilder.Dir(s)
87	return nil
88}
89
90type relativeRoot struct{}
91
92func (relativeRoot) String() string { return "" }
93
94func (relativeRoot) Set(s string) error {
95	fileArgsBuilder.SourcePrefixToStrip(s)
96	return nil
97}
98
99type junkPaths struct{}
100
101func (junkPaths) IsBoolFlag() bool { return true }
102func (junkPaths) String() string   { return "" }
103
104func (junkPaths) Set(s string) error {
105	v, err := strconv.ParseBool(s)
106	fileArgsBuilder.JunkPaths(v)
107	return err
108}
109
110type rootPrefix struct{}
111
112func (rootPrefix) String() string { return "" }
113
114func (rootPrefix) Set(s string) error {
115	fileArgsBuilder.PathPrefixInZip(s)
116	return nil
117}
118
119var (
120	fileArgsBuilder  = zip.NewFileArgsBuilder()
121	nonDeflatedFiles = make(uniqueSet)
122)
123
124func main() {
125	var expandedArgs []string
126	for _, arg := range os.Args {
127		if strings.HasPrefix(arg, "@") {
128			f, err := os.Open(strings.TrimPrefix(arg, "@"))
129			if err != nil {
130				fmt.Fprintln(os.Stderr, err.Error())
131				os.Exit(1)
132			}
133
134			respArgs, err := response.ReadRspFile(f)
135			f.Close()
136			if err != nil {
137				fmt.Fprintln(os.Stderr, err.Error())
138				os.Exit(1)
139			}
140			expandedArgs = append(expandedArgs, respArgs...)
141		} else {
142			expandedArgs = append(expandedArgs, arg)
143		}
144	}
145
146	flags := flag.NewFlagSet("flags", flag.ExitOnError)
147	flags.Usage = func() {
148		fmt.Fprintf(os.Stderr, "usage: soong_zip -o zipfile [-m manifest] [-C dir] [-f|-l file] [-D dir]...\n")
149		flags.PrintDefaults()
150		os.Exit(2)
151	}
152
153	out := flags.String("o", "", "file to write zip file to")
154	manifest := flags.String("m", "", "input jar manifest file name")
155	directories := flags.Bool("d", false, "include directories in zip")
156	compLevel := flags.Int("L", 5, "deflate compression level (0-9)")
157	emulateJar := flags.Bool("jar", false, "modify the resultant .zip to emulate the output of 'jar'")
158	writeIfChanged := flags.Bool("write_if_changed", false, "only update resultant .zip if it has changed")
159	ignoreMissingFiles := flags.Bool("ignore_missing_files", false, "continue if a requested file does not exist")
160	symlinks := flags.Bool("symlinks", true, "store symbolic links in zip instead of following them")
161	srcJar := flags.Bool("srcjar", false, "move .java files to locations that match their package statement")
162
163	parallelJobs := flags.Int("parallel", runtime.NumCPU(), "number of parallel threads to use")
164	cpuProfile := flags.String("cpuprofile", "", "write cpu profile to file")
165	traceFile := flags.String("trace", "", "write trace to file")
166
167	flags.Var(&rootPrefix{}, "P", "path prefix within the zip at which to place files")
168	flags.Var(&listFiles{}, "l", "file containing list of files to zip")
169	flags.Var(&rspFiles{}, "r", "file containing list of files to zip with Ninja rsp file escaping")
170	flags.Var(&dir{}, "D", "directory to include in zip")
171	flags.Var(&file{}, "f", "file to include in zip")
172	flags.Var(&nonDeflatedFiles, "s", "file path to be stored within the zip without compression")
173	flags.Var(&relativeRoot{}, "C", "path to use as relative root of files in following -f, -l, or -D arguments")
174	flags.Var(&junkPaths{}, "j", "junk paths, zip files without directory names")
175
176	flags.Parse(expandedArgs[1:])
177
178	if flags.NArg() > 0 {
179		fmt.Fprintf(os.Stderr, "unexpected arguments %s\n", strings.Join(flags.Args(), " "))
180		flags.Usage()
181	}
182
183	if *cpuProfile != "" {
184		f, err := os.Create(*cpuProfile)
185		if err != nil {
186			fmt.Fprintln(os.Stderr, err.Error())
187			os.Exit(1)
188		}
189		defer f.Close()
190		pprof.StartCPUProfile(f)
191		defer pprof.StopCPUProfile()
192	}
193
194	if *traceFile != "" {
195		f, err := os.Create(*traceFile)
196		if err != nil {
197			fmt.Fprintln(os.Stderr, err.Error())
198			os.Exit(1)
199		}
200		defer f.Close()
201		err = trace.Start(f)
202		if err != nil {
203			fmt.Fprintln(os.Stderr, err.Error())
204			os.Exit(1)
205		}
206		defer trace.Stop()
207	}
208
209	if fileArgsBuilder.Error() != nil {
210		fmt.Fprintln(os.Stderr, fileArgsBuilder.Error())
211		os.Exit(1)
212	}
213
214	err := zip.Zip(zip.ZipArgs{
215		FileArgs:                 fileArgsBuilder.FileArgs(),
216		OutputFilePath:           *out,
217		EmulateJar:               *emulateJar,
218		SrcJar:                   *srcJar,
219		AddDirectoryEntriesToZip: *directories,
220		CompressionLevel:         *compLevel,
221		ManifestSourcePath:       *manifest,
222		NumParallelJobs:          *parallelJobs,
223		NonDeflatedFiles:         nonDeflatedFiles,
224		WriteIfChanged:           *writeIfChanged,
225		StoreSymlinks:            *symlinks,
226		IgnoreMissingFiles:       *ignoreMissingFiles,
227	})
228	if err != nil {
229		fmt.Fprintln(os.Stderr, "error:", err.Error())
230		os.Exit(1)
231	}
232}
233