1// Copyright 2017 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 java
16
17import (
18	"path/filepath"
19	"sort"
20	"strconv"
21	"strings"
22
23	"github.com/google/blueprint"
24
25	"android/soong/android"
26)
27
28func isPathValueResource(res android.Path) bool {
29	subDir := filepath.Dir(res.String())
30	subDir, lastDir := filepath.Split(subDir)
31	return strings.HasPrefix(lastDir, "values")
32}
33
34// Convert input resource file path to output file path.
35// values-[config]/<file>.xml -> values-[config]_<file>.arsc.flat;
36// For other resource file, just replace the last "/" with "_" and add .flat extension.
37func pathToAapt2Path(ctx android.ModuleContext, res android.Path) android.WritablePath {
38
39	name := res.Base()
40	if isPathValueResource(res) {
41		name = strings.TrimSuffix(name, ".xml") + ".arsc"
42	}
43	subDir := filepath.Dir(res.String())
44	subDir, lastDir := filepath.Split(subDir)
45	name = lastDir + "_" + name + ".flat"
46	return android.PathForModuleOut(ctx, "aapt2", subDir, name)
47}
48
49// pathsToAapt2Paths Calls pathToAapt2Path on each entry of the given Paths, i.e. []Path.
50func pathsToAapt2Paths(ctx android.ModuleContext, resPaths android.Paths) android.WritablePaths {
51	outPaths := make(android.WritablePaths, len(resPaths))
52
53	for i, res := range resPaths {
54		outPaths[i] = pathToAapt2Path(ctx, res)
55	}
56
57	return outPaths
58}
59
60// Shard resource files for efficiency. See aapt2Compile for details.
61const AAPT2_SHARD_SIZE = 100
62
63var aapt2CompileRule = pctx.AndroidStaticRule("aapt2Compile",
64	blueprint.RuleParams{
65		Command:     `${config.Aapt2Cmd} compile -o $outDir $cFlags $in`,
66		CommandDeps: []string{"${config.Aapt2Cmd}"},
67	},
68	"outDir", "cFlags")
69
70// aapt2Compile compiles resources and puts the results in the requested directory.
71func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Paths,
72	flags []string, productToFilter string) android.WritablePaths {
73	if productToFilter != "" && productToFilter != "default" {
74		// --filter-product leaves only product-specific resources. Product-specific resources only exist
75		// in value resources (values/*.xml), so filter value resource files only. Ignore other types of
76		// resources as they don't need to be in product characteristics RRO (and they will cause aapt2
77		// compile errors)
78		filteredPaths := android.Paths{}
79		for _, path := range paths {
80			if isPathValueResource(path) {
81				filteredPaths = append(filteredPaths, path)
82			}
83		}
84		paths = filteredPaths
85		flags = append([]string{"--filter-product " + productToFilter}, flags...)
86	}
87
88	// Shard the input paths so that they can be processed in parallel. If we shard them into too
89	// small chunks, the additional cost of spinning up aapt2 outweighs the performance gain. The
90	// current shard size, 100, seems to be a good balance between the added cost and the gain.
91	// The aapt2 compile actions are trivially short, but each action in ninja takes on the order of
92	// ~10 ms to run. frameworks/base/core/res/res has >10k resource files, so compiling each one
93	// with an individual action could take 100 CPU seconds. Sharding them reduces the overhead of
94	// starting actions by a factor of 100, at the expense of recompiling more files when one
95	// changes.  Since the individual compiles are trivial it's a good tradeoff.
96	shards := android.ShardPaths(paths, AAPT2_SHARD_SIZE)
97
98	ret := make(android.WritablePaths, 0, len(paths))
99
100	for i, shard := range shards {
101		// This should be kept in sync with pathToAapt2Path. The aapt2 compile command takes an
102		// output directory path, but not output file paths. So, outPaths is just where we expect
103		// the output files will be located.
104		outPaths := pathsToAapt2Paths(ctx, shard)
105		ret = append(ret, outPaths...)
106
107		shardDesc := ""
108		if i != 0 {
109			shardDesc = " " + strconv.Itoa(i+1)
110		}
111
112		ctx.Build(pctx, android.BuildParams{
113			Rule:        aapt2CompileRule,
114			Description: "aapt2 compile " + dir.String() + shardDesc,
115			Inputs:      shard,
116			Outputs:     outPaths,
117			Args: map[string]string{
118				// The aapt2 compile command takes an output directory path, but not output file paths.
119				// outPaths specified above is only used for dependency management purposes. In order for
120				// the outPaths values to match the actual outputs from aapt2, the dir parameter value
121				// must be a common prefix path of the paths values, and the top-level path segment used
122				// below, "aapt2", must always be kept in sync with the one in pathToAapt2Path.
123				// TODO(b/174505750): Make this easier and robust to use.
124				"outDir": android.PathForModuleOut(ctx, "aapt2", dir.String()).String(),
125				"cFlags": strings.Join(flags, " "),
126			},
127		})
128	}
129
130	sort.Slice(ret, func(i, j int) bool {
131		return ret[i].String() < ret[j].String()
132	})
133	return ret
134}
135
136var aapt2CompileZipRule = pctx.AndroidStaticRule("aapt2CompileZip",
137	blueprint.RuleParams{
138		Command: `${config.ZipSyncCmd} -d $resZipDir $zipSyncFlags $in && ` +
139			`${config.Aapt2Cmd} compile -o $out $cFlags --dir $resZipDir`,
140		CommandDeps: []string{
141			"${config.Aapt2Cmd}",
142			"${config.ZipSyncCmd}",
143		},
144	}, "cFlags", "resZipDir", "zipSyncFlags")
145
146// Unzips the given compressed file and compiles the resource source files in it. The zipPrefix
147// parameter points to the subdirectory in the zip file where the resource files are located.
148func aapt2CompileZip(ctx android.ModuleContext, flata android.WritablePath, zip android.Path, zipPrefix string,
149	flags []string) {
150
151	if zipPrefix != "" {
152		zipPrefix = "--zip-prefix " + zipPrefix
153	}
154	ctx.Build(pctx, android.BuildParams{
155		Rule:        aapt2CompileZipRule,
156		Description: "aapt2 compile zip",
157		Input:       zip,
158		Output:      flata,
159		Args: map[string]string{
160			"cFlags":       strings.Join(flags, " "),
161			"resZipDir":    android.PathForModuleOut(ctx, "aapt2", "reszip", flata.Base()).String(),
162			"zipSyncFlags": zipPrefix,
163		},
164	})
165}
166
167var aapt2LinkRule = pctx.AndroidStaticRule("aapt2Link",
168	blueprint.RuleParams{
169		Command: `$preamble` +
170			`${config.Aapt2Cmd} link -o $out $flags --proguard $proguardOptions ` +
171			`--output-text-symbols ${rTxt} $inFlags` +
172			`$postamble`,
173
174		CommandDeps: []string{
175			"${config.Aapt2Cmd}",
176			"${config.SoongZipCmd}",
177		},
178		Restat: true,
179	},
180	"flags", "inFlags", "proguardOptions", "rTxt", "extraPackages", "preamble", "postamble")
181
182var aapt2ExtractExtraPackagesRule = pctx.AndroidStaticRule("aapt2ExtractExtraPackages",
183	blueprint.RuleParams{
184		Command:     `${config.ExtractJarPackagesCmd} -i $in -o $out --prefix '--extra-packages '`,
185		CommandDeps: []string{"${config.ExtractJarPackagesCmd}"},
186		Restat:      true,
187	})
188
189var fileListToFileRule = pctx.AndroidStaticRule("fileListToFile",
190	blueprint.RuleParams{
191		Command:        `cp $out.rsp $out`,
192		Rspfile:        "$out.rsp",
193		RspfileContent: "$in",
194	})
195
196var mergeAssetsRule = pctx.AndroidStaticRule("mergeAssets",
197	blueprint.RuleParams{
198		Command:     `${config.MergeZipsCmd} ${out} ${in}`,
199		CommandDeps: []string{"${config.MergeZipsCmd}"},
200	})
201
202func aapt2Link(ctx android.ModuleContext,
203	packageRes, genJar, proguardOptions, rTxt android.WritablePath,
204	flags []string, deps android.Paths,
205	compiledRes, compiledOverlay, assetPackages android.Paths, splitPackages android.WritablePaths,
206	featureFlagsPaths android.Paths) {
207
208	var inFlags []string
209
210	if len(compiledRes) > 0 {
211		// Create a file that contains the list of all compiled resource file paths.
212		resFileList := android.PathForModuleOut(ctx, "aapt2", "res.list")
213		// Write out file lists to files
214		ctx.Build(pctx, android.BuildParams{
215			Rule:        fileListToFileRule,
216			Description: "resource file list",
217			Inputs:      compiledRes,
218			Output:      resFileList,
219		})
220
221		deps = append(deps, compiledRes...)
222		deps = append(deps, resFileList)
223		// aapt2 filepath arguments that start with "@" mean file-list files.
224		inFlags = append(inFlags, "@"+resFileList.String())
225	}
226
227	if len(compiledOverlay) > 0 {
228		// Compiled overlay files are processed the same way as compiled resources.
229		overlayFileList := android.PathForModuleOut(ctx, "aapt2", "overlay.list")
230		ctx.Build(pctx, android.BuildParams{
231			Rule:        fileListToFileRule,
232			Description: "overlay resource file list",
233			Inputs:      compiledOverlay,
234			Output:      overlayFileList,
235		})
236
237		deps = append(deps, compiledOverlay...)
238		deps = append(deps, overlayFileList)
239		// Compiled overlay files are passed over to aapt2 using -R option.
240		inFlags = append(inFlags, "-R", "@"+overlayFileList.String())
241	}
242
243	// Set auxiliary outputs as implicit outputs to establish correct dependency chains.
244	implicitOutputs := append(splitPackages, proguardOptions, rTxt)
245	linkOutput := packageRes
246
247	// AAPT2 ignores assets in overlays. Merge them after linking.
248	if len(assetPackages) > 0 {
249		linkOutput = android.PathForModuleOut(ctx, "aapt2", "package-res.apk")
250		inputZips := append(android.Paths{linkOutput}, assetPackages...)
251		ctx.Build(pctx, android.BuildParams{
252			Rule:        mergeAssetsRule,
253			Inputs:      inputZips,
254			Output:      packageRes,
255			Description: "merge assets from dependencies",
256		})
257	}
258
259	for _, featureFlagsPath := range featureFlagsPaths {
260		deps = append(deps, featureFlagsPath)
261		inFlags = append(inFlags, "--feature-flags", "@"+featureFlagsPath.String())
262	}
263
264	// Note the absence of splitPackages. The caller is supposed to compose and provide --split flag
265	// values via the flags parameter when it wants to split outputs.
266	// TODO(b/174509108): Perhaps we can process it in this func while keeping the code reasonably
267	// tidy.
268	args := map[string]string{
269		"flags":           strings.Join(flags, " "),
270		"inFlags":         strings.Join(inFlags, " "),
271		"proguardOptions": proguardOptions.String(),
272		"rTxt":            rTxt.String(),
273	}
274
275	if genJar != nil {
276		// Generating java source files from aapt2 was requested, use aapt2LinkAndGenRule and pass it
277		// genJar and genDir args.
278		genDir := android.PathForModuleGen(ctx, "aapt2", "R")
279		ctx.Variable(pctx, "aapt2GenDir", genDir.String())
280		ctx.Variable(pctx, "aapt2GenJar", genJar.String())
281		implicitOutputs = append(implicitOutputs, genJar)
282		args["preamble"] = `rm -rf $aapt2GenDir && `
283		args["postamble"] = `&& ${config.SoongZipCmd} -write_if_changed -jar -o $aapt2GenJar -C $aapt2GenDir -D $aapt2GenDir && ` +
284			`rm -rf $aapt2GenDir`
285		args["flags"] += " --java $aapt2GenDir"
286	}
287
288	ctx.Build(pctx, android.BuildParams{
289		Rule:            aapt2LinkRule,
290		Description:     "aapt2 link",
291		Implicits:       deps,
292		Output:          linkOutput,
293		ImplicitOutputs: implicitOutputs,
294		Args:            args,
295	})
296}
297
298// aapt2ExtractExtraPackages takes a srcjar generated by aapt2 or a classes jar generated by ResourceProcessorBusyBox
299// and converts it to a text file containing a list of --extra_package arguments for passing to Make modules so they
300// correctly generate R.java entries for packages provided by transitive dependencies.
301func aapt2ExtractExtraPackages(ctx android.ModuleContext, out android.WritablePath, in android.Path) {
302	ctx.Build(pctx, android.BuildParams{
303		Rule:        aapt2ExtractExtraPackagesRule,
304		Description: "aapt2 extract extra packages",
305		Input:       in,
306		Output:      out,
307	})
308}
309
310var aapt2ConvertRule = pctx.AndroidStaticRule("aapt2Convert",
311	blueprint.RuleParams{
312		Command: `${config.Aapt2Cmd} convert --enable-compact-entries ` +
313			`--output-format $format $in -o $out`,
314		CommandDeps: []string{"${config.Aapt2Cmd}"},
315	}, "format",
316)
317
318// Converts xml files and resource tables (resources.arsc) in the given jar/apk file to a proto
319// format. The proto definition is available at frameworks/base/tools/aapt2/Resources.proto.
320func aapt2Convert(ctx android.ModuleContext, out android.WritablePath, in android.Path, format string) {
321	ctx.Build(pctx, android.BuildParams{
322		Rule:        aapt2ConvertRule,
323		Input:       in,
324		Output:      out,
325		Description: "convert to " + format,
326		Args: map[string]string{
327			"format": format,
328		},
329	})
330}
331