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