blob: bae4d1ee3ea33c46ceb82dd4156e5325704583c1 [file] [log] [blame]
Colin Cross3bc7ffa2017-11-22 16:19:37 -08001// 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 (
Jihoon Kang8c67d582024-10-22 19:10:25 +000018 "fmt"
Colin Cross3bc7ffa2017-11-22 16:19:37 -080019 "path/filepath"
Jihoon Kang8c67d582024-10-22 19:10:25 +000020 "regexp"
Colin Crossb69301e2017-12-01 10:48:26 -080021 "sort"
Colin Cross3bc7ffa2017-11-22 16:19:37 -080022 "strconv"
23 "strings"
24
25 "github.com/google/blueprint"
26
27 "android/soong/android"
28)
29
Inseob Kim34dc4cd2023-11-07 13:37:14 +090030func isPathValueResource(res android.Path) bool {
31 subDir := filepath.Dir(res.String())
32 subDir, lastDir := filepath.Split(subDir)
33 return strings.HasPrefix(lastDir, "values")
34}
35
Jihoon Kang8c67d582024-10-22 19:10:25 +000036func isFlagsPath(subDir string) bool {
37 re := regexp.MustCompile(`flag\(!?([a-zA-Z_-]+\.)*[a-zA-Z0-9_-]+\)`)
38 lastDir := filepath.Base(subDir)
39 return re.MatchString(lastDir)
40}
41
Colin Cross3bc7ffa2017-11-22 16:19:37 -080042// Convert input resource file path to output file path.
43// values-[config]/<file>.xml -> values-[config]_<file>.arsc.flat;
Jihoon Kang8c67d582024-10-22 19:10:25 +000044// flag(fully.qualified.flag_name)/values-[config]/<file>.xml -> /values-[config]_<file>.(fully.qualified.flag_name).arsc.flat;
Jaewoong Jung60d6d572020-11-20 17:58:27 -080045// For other resource file, just replace the last "/" with "_" and add .flat extension.
Colin Cross3bc7ffa2017-11-22 16:19:37 -080046func pathToAapt2Path(ctx android.ModuleContext, res android.Path) android.WritablePath {
47
Jihoon Kang8c67d582024-10-22 19:10:25 +000048 extension := filepath.Ext(res.Base())
49 name := strings.TrimSuffix(res.Base(), extension)
Inseob Kim34dc4cd2023-11-07 13:37:14 +090050 if isPathValueResource(res) {
Jihoon Kang8c67d582024-10-22 19:10:25 +000051 extension = ".arsc"
Colin Cross3bc7ffa2017-11-22 16:19:37 -080052 }
Inseob Kim34dc4cd2023-11-07 13:37:14 +090053 subDir := filepath.Dir(res.String())
54 subDir, lastDir := filepath.Split(subDir)
Jihoon Kang8c67d582024-10-22 19:10:25 +000055 if isFlagsPath(subDir) {
56 var flag string
57 subDir, flag = filepath.Split(filepath.Dir(subDir))
58 flag = strings.TrimPrefix(flag, "flag")
59 name = fmt.Sprintf("%s_%s.%s%s.flat", lastDir, name, flag, extension)
60 } else {
61 name = fmt.Sprintf("%s_%s%s.flat", lastDir, name, extension)
62 }
63 out := android.PathForModuleOut(ctx, "aapt2", subDir, name)
64 return out
Colin Cross3bc7ffa2017-11-22 16:19:37 -080065}
66
Jaewoong Jung60d6d572020-11-20 17:58:27 -080067// pathsToAapt2Paths Calls pathToAapt2Path on each entry of the given Paths, i.e. []Path.
Colin Cross3bc7ffa2017-11-22 16:19:37 -080068func pathsToAapt2Paths(ctx android.ModuleContext, resPaths android.Paths) android.WritablePaths {
69 outPaths := make(android.WritablePaths, len(resPaths))
70
71 for i, res := range resPaths {
72 outPaths[i] = pathToAapt2Path(ctx, res)
73 }
74
75 return outPaths
76}
77
Jaewoong Jung60d6d572020-11-20 17:58:27 -080078// Shard resource files for efficiency. See aapt2Compile for details.
79const AAPT2_SHARD_SIZE = 100
80
Colin Cross3bc7ffa2017-11-22 16:19:37 -080081var aapt2CompileRule = pctx.AndroidStaticRule("aapt2Compile",
82 blueprint.RuleParams{
Colin Cross4215cfd2019-06-20 16:53:30 -070083 Command: `${config.Aapt2Cmd} compile -o $outDir $cFlags $in`,
Colin Cross3bc7ffa2017-11-22 16:19:37 -080084 CommandDeps: []string{"${config.Aapt2Cmd}"},
85 },
86 "outDir", "cFlags")
87
Jaewoong Jung60d6d572020-11-20 17:58:27 -080088// aapt2Compile compiles resources and puts the results in the requested directory.
Colin Crossa0ba2f52019-06-22 12:59:27 -070089func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Paths,
Jihoon Kang98ea8362024-07-16 18:20:03 +000090 flags []string, productToFilter string, featureFlagsPaths android.Paths) android.WritablePaths {
Inseob Kim34dc4cd2023-11-07 13:37:14 +090091 if productToFilter != "" && productToFilter != "default" {
92 // --filter-product leaves only product-specific resources. Product-specific resources only exist
93 // in value resources (values/*.xml), so filter value resource files only. Ignore other types of
94 // resources as they don't need to be in product characteristics RRO (and they will cause aapt2
95 // compile errors)
96 filteredPaths := android.Paths{}
97 for _, path := range paths {
98 if isPathValueResource(path) {
99 filteredPaths = append(filteredPaths, path)
100 }
101 }
102 paths = filteredPaths
103 flags = append([]string{"--filter-product " + productToFilter}, flags...)
104 }
Colin Crossa0ba2f52019-06-22 12:59:27 -0700105
Jihoon Kang98ea8362024-07-16 18:20:03 +0000106 for _, featureFlagsPath := range android.SortedUniquePaths(featureFlagsPaths) {
107 flags = append(flags, "--feature-flags", "@"+featureFlagsPath.String())
108 }
109
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800110 // Shard the input paths so that they can be processed in parallel. If we shard them into too
111 // small chunks, the additional cost of spinning up aapt2 outweighs the performance gain. The
112 // current shard size, 100, seems to be a good balance between the added cost and the gain.
113 // The aapt2 compile actions are trivially short, but each action in ninja takes on the order of
114 // ~10 ms to run. frameworks/base/core/res/res has >10k resource files, so compiling each one
115 // with an individual action could take 100 CPU seconds. Sharding them reduces the overhead of
116 // starting actions by a factor of 100, at the expense of recompiling more files when one
117 // changes. Since the individual compiles are trivial it's a good tradeoff.
Colin Cross0a2f7192019-09-23 14:33:09 -0700118 shards := android.ShardPaths(paths, AAPT2_SHARD_SIZE)
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800119
120 ret := make(android.WritablePaths, 0, len(paths))
121
122 for i, shard := range shards {
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800123 // This should be kept in sync with pathToAapt2Path. The aapt2 compile command takes an
124 // output directory path, but not output file paths. So, outPaths is just where we expect
125 // the output files will be located.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800126 outPaths := pathsToAapt2Paths(ctx, shard)
127 ret = append(ret, outPaths...)
128
129 shardDesc := ""
130 if i != 0 {
131 shardDesc = " " + strconv.Itoa(i+1)
132 }
133
134 ctx.Build(pctx, android.BuildParams{
135 Rule: aapt2CompileRule,
136 Description: "aapt2 compile " + dir.String() + shardDesc,
Jihoon Kang98ea8362024-07-16 18:20:03 +0000137 Implicits: featureFlagsPaths,
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800138 Inputs: shard,
139 Outputs: outPaths,
140 Args: map[string]string{
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800141 // The aapt2 compile command takes an output directory path, but not output file paths.
142 // outPaths specified above is only used for dependency management purposes. In order for
143 // the outPaths values to match the actual outputs from aapt2, the dir parameter value
144 // must be a common prefix path of the paths values, and the top-level path segment used
145 // below, "aapt2", must always be kept in sync with the one in pathToAapt2Path.
146 // TODO(b/174505750): Make this easier and robust to use.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800147 "outDir": android.PathForModuleOut(ctx, "aapt2", dir.String()).String(),
Colin Crossa0ba2f52019-06-22 12:59:27 -0700148 "cFlags": strings.Join(flags, " "),
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800149 },
150 })
151 }
152
Colin Crossb69301e2017-12-01 10:48:26 -0800153 sort.Slice(ret, func(i, j int) bool {
154 return ret[i].String() < ret[j].String()
155 })
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800156 return ret
157}
158
Colin Crossa592e3e2019-02-19 16:59:53 -0800159var aapt2CompileZipRule = pctx.AndroidStaticRule("aapt2CompileZip",
160 blueprint.RuleParams{
Dan Willemsen304cfec2019-05-28 14:49:06 -0700161 Command: `${config.ZipSyncCmd} -d $resZipDir $zipSyncFlags $in && ` +
Colin Cross4215cfd2019-06-20 16:53:30 -0700162 `${config.Aapt2Cmd} compile -o $out $cFlags --dir $resZipDir`,
Colin Crossa592e3e2019-02-19 16:59:53 -0800163 CommandDeps: []string{
164 "${config.Aapt2Cmd}",
165 "${config.ZipSyncCmd}",
166 },
Dan Willemsen304cfec2019-05-28 14:49:06 -0700167 }, "cFlags", "resZipDir", "zipSyncFlags")
Colin Crossa592e3e2019-02-19 16:59:53 -0800168
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800169// Unzips the given compressed file and compiles the resource source files in it. The zipPrefix
170// parameter points to the subdirectory in the zip file where the resource files are located.
Colin Crossa0ba2f52019-06-22 12:59:27 -0700171func aapt2CompileZip(ctx android.ModuleContext, flata android.WritablePath, zip android.Path, zipPrefix string,
172 flags []string) {
173
Dan Willemsen304cfec2019-05-28 14:49:06 -0700174 if zipPrefix != "" {
175 zipPrefix = "--zip-prefix " + zipPrefix
176 }
Colin Crossa592e3e2019-02-19 16:59:53 -0800177 ctx.Build(pctx, android.BuildParams{
178 Rule: aapt2CompileZipRule,
179 Description: "aapt2 compile zip",
180 Input: zip,
181 Output: flata,
182 Args: map[string]string{
Colin Crossa0ba2f52019-06-22 12:59:27 -0700183 "cFlags": strings.Join(flags, " "),
Dan Willemsen304cfec2019-05-28 14:49:06 -0700184 "resZipDir": android.PathForModuleOut(ctx, "aapt2", "reszip", flata.Base()).String(),
185 "zipSyncFlags": zipPrefix,
Colin Crossa592e3e2019-02-19 16:59:53 -0800186 },
187 })
188}
189
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800190var aapt2LinkRule = pctx.AndroidStaticRule("aapt2Link",
191 blueprint.RuleParams{
Colin Crossf3b7bad2023-08-02 15:49:00 -0700192 Command: `$preamble` +
193 `${config.Aapt2Cmd} link -o $out $flags --proguard $proguardOptions ` +
194 `--output-text-symbols ${rTxt} $inFlags` +
195 `$postamble`,
Colin Cross66f78822018-05-02 12:58:28 -0700196
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800197 CommandDeps: []string{
Colin Cross44f06682017-11-29 00:17:36 -0800198 "${config.Aapt2Cmd}",
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800199 "${config.SoongZipCmd}",
200 },
201 Restat: true,
202 },
Colin Crossf3b7bad2023-08-02 15:49:00 -0700203 "flags", "inFlags", "proguardOptions", "rTxt", "extraPackages", "preamble", "postamble")
204
205var aapt2ExtractExtraPackagesRule = pctx.AndroidStaticRule("aapt2ExtractExtraPackages",
206 blueprint.RuleParams{
207 Command: `${config.ExtractJarPackagesCmd} -i $in -o $out --prefix '--extra-packages '`,
208 CommandDeps: []string{"${config.ExtractJarPackagesCmd}"},
209 Restat: true,
210 })
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800211
212var fileListToFileRule = pctx.AndroidStaticRule("fileListToFile",
213 blueprint.RuleParams{
214 Command: `cp $out.rsp $out`,
215 Rspfile: "$out.rsp",
216 RspfileContent: "$in",
217 })
218
Jaewoong Jung6431ca72020-01-15 14:15:10 -0800219var mergeAssetsRule = pctx.AndroidStaticRule("mergeAssets",
220 blueprint.RuleParams{
221 Command: `${config.MergeZipsCmd} ${out} ${in}`,
222 CommandDeps: []string{"${config.MergeZipsCmd}"},
223 })
224
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800225func aapt2Link(ctx android.ModuleContext,
Colin Crossf3b7bad2023-08-02 15:49:00 -0700226 packageRes, genJar, proguardOptions, rTxt android.WritablePath,
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800227 flags []string, deps android.Paths,
Jihoon Kang84b25892023-12-01 22:01:06 +0000228 compiledRes, compiledOverlay, assetPackages android.Paths, splitPackages android.WritablePaths,
229 featureFlagsPaths android.Paths) {
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800230
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800231 var inFlags []string
232
233 if len(compiledRes) > 0 {
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800234 // Create a file that contains the list of all compiled resource file paths.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800235 resFileList := android.PathForModuleOut(ctx, "aapt2", "res.list")
236 // Write out file lists to files
237 ctx.Build(pctx, android.BuildParams{
238 Rule: fileListToFileRule,
239 Description: "resource file list",
240 Inputs: compiledRes,
241 Output: resFileList,
242 })
243
244 deps = append(deps, compiledRes...)
245 deps = append(deps, resFileList)
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800246 // aapt2 filepath arguments that start with "@" mean file-list files.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800247 inFlags = append(inFlags, "@"+resFileList.String())
248 }
249
250 if len(compiledOverlay) > 0 {
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800251 // Compiled overlay files are processed the same way as compiled resources.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800252 overlayFileList := android.PathForModuleOut(ctx, "aapt2", "overlay.list")
253 ctx.Build(pctx, android.BuildParams{
254 Rule: fileListToFileRule,
255 Description: "overlay resource file list",
256 Inputs: compiledOverlay,
257 Output: overlayFileList,
258 })
259
260 deps = append(deps, compiledOverlay...)
261 deps = append(deps, overlayFileList)
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800262 // Compiled overlay files are passed over to aapt2 using -R option.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800263 inFlags = append(inFlags, "-R", "@"+overlayFileList.String())
264 }
265
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800266 // Set auxiliary outputs as implicit outputs to establish correct dependency chains.
Colin Crossf3b7bad2023-08-02 15:49:00 -0700267 implicitOutputs := append(splitPackages, proguardOptions, rTxt)
Jaewoong Jung6431ca72020-01-15 14:15:10 -0800268 linkOutput := packageRes
269
270 // AAPT2 ignores assets in overlays. Merge them after linking.
271 if len(assetPackages) > 0 {
272 linkOutput = android.PathForModuleOut(ctx, "aapt2", "package-res.apk")
273 inputZips := append(android.Paths{linkOutput}, assetPackages...)
274 ctx.Build(pctx, android.BuildParams{
275 Rule: mergeAssetsRule,
276 Inputs: inputZips,
277 Output: packageRes,
278 Description: "merge assets from dependencies",
279 })
280 }
Colin Crosse560c4a2019-03-19 16:03:11 -0700281
Jihoon Kang84b25892023-12-01 22:01:06 +0000282 for _, featureFlagsPath := range featureFlagsPaths {
283 deps = append(deps, featureFlagsPath)
284 inFlags = append(inFlags, "--feature-flags", "@"+featureFlagsPath.String())
285 }
286
Colin Crossf3b7bad2023-08-02 15:49:00 -0700287 // Note the absence of splitPackages. The caller is supposed to compose and provide --split flag
288 // values via the flags parameter when it wants to split outputs.
289 // TODO(b/174509108): Perhaps we can process it in this func while keeping the code reasonably
290 // tidy.
291 args := map[string]string{
292 "flags": strings.Join(flags, " "),
293 "inFlags": strings.Join(inFlags, " "),
294 "proguardOptions": proguardOptions.String(),
295 "rTxt": rTxt.String(),
296 }
297
298 if genJar != nil {
299 // Generating java source files from aapt2 was requested, use aapt2LinkAndGenRule and pass it
300 // genJar and genDir args.
301 genDir := android.PathForModuleGen(ctx, "aapt2", "R")
302 ctx.Variable(pctx, "aapt2GenDir", genDir.String())
303 ctx.Variable(pctx, "aapt2GenJar", genJar.String())
304 implicitOutputs = append(implicitOutputs, genJar)
305 args["preamble"] = `rm -rf $aapt2GenDir && `
306 args["postamble"] = `&& ${config.SoongZipCmd} -write_if_changed -jar -o $aapt2GenJar -C $aapt2GenDir -D $aapt2GenDir && ` +
307 `rm -rf $aapt2GenDir`
308 args["flags"] += " --java $aapt2GenDir"
309 }
310
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800311 ctx.Build(pctx, android.BuildParams{
312 Rule: aapt2LinkRule,
313 Description: "aapt2 link",
314 Implicits: deps,
Jaewoong Jung6431ca72020-01-15 14:15:10 -0800315 Output: linkOutput,
Colin Crosse560c4a2019-03-19 16:03:11 -0700316 ImplicitOutputs: implicitOutputs,
Colin Crossf3b7bad2023-08-02 15:49:00 -0700317 Args: args,
318 })
319}
320
321// aapt2ExtractExtraPackages takes a srcjar generated by aapt2 or a classes jar generated by ResourceProcessorBusyBox
322// and converts it to a text file containing a list of --extra_package arguments for passing to Make modules so they
323// correctly generate R.java entries for packages provided by transitive dependencies.
324func aapt2ExtractExtraPackages(ctx android.ModuleContext, out android.WritablePath, in android.Path) {
325 ctx.Build(pctx, android.BuildParams{
326 Rule: aapt2ExtractExtraPackagesRule,
327 Description: "aapt2 extract extra packages",
328 Input: in,
329 Output: out,
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800330 })
331}
Colin Crossf6237212018-10-29 23:14:58 -0700332
333var aapt2ConvertRule = pctx.AndroidStaticRule("aapt2Convert",
334 blueprint.RuleParams{
Rico Wind21862282023-08-01 14:38:36 +0200335 Command: `${config.Aapt2Cmd} convert --enable-compact-entries ` +
336 `--output-format $format $in -o $out`,
Colin Crossf6237212018-10-29 23:14:58 -0700337 CommandDeps: []string{"${config.Aapt2Cmd}"},
Rico Wind351bac92022-09-22 10:41:42 +0200338 }, "format",
339)
Colin Crossf6237212018-10-29 23:14:58 -0700340
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800341// Converts xml files and resource tables (resources.arsc) in the given jar/apk file to a proto
342// format. The proto definition is available at frameworks/base/tools/aapt2/Resources.proto.
Rico Wind351bac92022-09-22 10:41:42 +0200343func aapt2Convert(ctx android.ModuleContext, out android.WritablePath, in android.Path, format string) {
Colin Crossf6237212018-10-29 23:14:58 -0700344 ctx.Build(pctx, android.BuildParams{
345 Rule: aapt2ConvertRule,
346 Input: in,
347 Output: out,
Rico Wind351bac92022-09-22 10:41:42 +0200348 Description: "convert to " + format,
349 Args: map[string]string{
350 "format": format,
351 },
Colin Crossf6237212018-10-29 23:14:58 -0700352 })
353}