blob: 5346ddf89c4789bdc78a48cfd04cf950986dbe8f [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 (
18 "path/filepath"
Colin Crossb69301e2017-12-01 10:48:26 -080019 "sort"
Colin Cross3bc7ffa2017-11-22 16:19:37 -080020 "strconv"
21 "strings"
22
23 "github.com/google/blueprint"
24
25 "android/soong/android"
26)
27
Colin Cross3bc7ffa2017-11-22 16:19:37 -080028// Convert input resource file path to output file path.
29// values-[config]/<file>.xml -> values-[config]_<file>.arsc.flat;
Jaewoong Jung60d6d572020-11-20 17:58:27 -080030// For other resource file, just replace the last "/" with "_" and add .flat extension.
Colin Cross3bc7ffa2017-11-22 16:19:37 -080031func 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
Jaewoong Jung60d6d572020-11-20 17:58:27 -080043// pathsToAapt2Paths Calls pathToAapt2Path on each entry of the given Paths, i.e. []Path.
Colin Cross3bc7ffa2017-11-22 16:19:37 -080044func 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
Jaewoong Jung60d6d572020-11-20 17:58:27 -080054// Shard resource files for efficiency. See aapt2Compile for details.
55const AAPT2_SHARD_SIZE = 100
56
Colin Cross3bc7ffa2017-11-22 16:19:37 -080057var aapt2CompileRule = pctx.AndroidStaticRule("aapt2Compile",
58 blueprint.RuleParams{
Colin Cross4215cfd2019-06-20 16:53:30 -070059 Command: `${config.Aapt2Cmd} compile -o $outDir $cFlags $in`,
Colin Cross3bc7ffa2017-11-22 16:19:37 -080060 CommandDeps: []string{"${config.Aapt2Cmd}"},
61 },
62 "outDir", "cFlags")
63
Jaewoong Jung60d6d572020-11-20 17:58:27 -080064// aapt2Compile compiles resources and puts the results in the requested directory.
Colin Crossa0ba2f52019-06-22 12:59:27 -070065func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Paths,
66 flags []string) android.WritablePaths {
67
Jaewoong Jung60d6d572020-11-20 17:58:27 -080068 // 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.
Colin Cross0a2f7192019-09-23 14:33:09 -070076 shards := android.ShardPaths(paths, AAPT2_SHARD_SIZE)
Colin Cross3bc7ffa2017-11-22 16:19:37 -080077
78 ret := make(android.WritablePaths, 0, len(paths))
79
80 for i, shard := range shards {
Jaewoong Jung60d6d572020-11-20 17:58:27 -080081 // 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.
Colin Cross3bc7ffa2017-11-22 16:19:37 -080084 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{
Jaewoong Jung60d6d572020-11-20 17:58:27 -080098 // 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.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800104 "outDir": android.PathForModuleOut(ctx, "aapt2", dir.String()).String(),
Colin Crossa0ba2f52019-06-22 12:59:27 -0700105 "cFlags": strings.Join(flags, " "),
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800106 },
107 })
108 }
109
Colin Crossb69301e2017-12-01 10:48:26 -0800110 sort.Slice(ret, func(i, j int) bool {
111 return ret[i].String() < ret[j].String()
112 })
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800113 return ret
114}
115
Colin Crossa592e3e2019-02-19 16:59:53 -0800116var aapt2CompileZipRule = pctx.AndroidStaticRule("aapt2CompileZip",
117 blueprint.RuleParams{
Dan Willemsen304cfec2019-05-28 14:49:06 -0700118 Command: `${config.ZipSyncCmd} -d $resZipDir $zipSyncFlags $in && ` +
Colin Cross4215cfd2019-06-20 16:53:30 -0700119 `${config.Aapt2Cmd} compile -o $out $cFlags --dir $resZipDir`,
Colin Crossa592e3e2019-02-19 16:59:53 -0800120 CommandDeps: []string{
121 "${config.Aapt2Cmd}",
122 "${config.ZipSyncCmd}",
123 },
Dan Willemsen304cfec2019-05-28 14:49:06 -0700124 }, "cFlags", "resZipDir", "zipSyncFlags")
Colin Crossa592e3e2019-02-19 16:59:53 -0800125
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800126// 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.
Colin Crossa0ba2f52019-06-22 12:59:27 -0700128func aapt2CompileZip(ctx android.ModuleContext, flata android.WritablePath, zip android.Path, zipPrefix string,
129 flags []string) {
130
Dan Willemsen304cfec2019-05-28 14:49:06 -0700131 if zipPrefix != "" {
132 zipPrefix = "--zip-prefix " + zipPrefix
133 }
Colin Crossa592e3e2019-02-19 16:59:53 -0800134 ctx.Build(pctx, android.BuildParams{
135 Rule: aapt2CompileZipRule,
136 Description: "aapt2 compile zip",
137 Input: zip,
138 Output: flata,
139 Args: map[string]string{
Colin Crossa0ba2f52019-06-22 12:59:27 -0700140 "cFlags": strings.Join(flags, " "),
Dan Willemsen304cfec2019-05-28 14:49:06 -0700141 "resZipDir": android.PathForModuleOut(ctx, "aapt2", "reszip", flata.Base()).String(),
142 "zipSyncFlags": zipPrefix,
Colin Crossa592e3e2019-02-19 16:59:53 -0800143 },
144 })
145}
146
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800147var aapt2LinkRule = pctx.AndroidStaticRule("aapt2Link",
148 blueprint.RuleParams{
Colin Cross78e3cb02018-10-17 15:42:59 -0700149 Command: `rm -rf $genDir && ` +
150 `${config.Aapt2Cmd} link -o $out $flags --java $genDir --proguard $proguardOptions ` +
Colin Crossa97c5d32018-03-28 14:58:31 -0700151 `--output-text-symbols ${rTxt} $inFlags && ` +
Colin Cross66f78822018-05-02 12:58:28 -0700152 `${config.SoongZipCmd} -write_if_changed -jar -o $genJar -C $genDir -D $genDir &&` +
153 `${config.ExtractJarPackagesCmd} -i $genJar -o $extraPackages --prefix '--extra-packages '`,
154
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800155 CommandDeps: []string{
Colin Cross44f06682017-11-29 00:17:36 -0800156 "${config.Aapt2Cmd}",
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800157 "${config.SoongZipCmd}",
Colin Cross66f78822018-05-02 12:58:28 -0700158 "${config.ExtractJarPackagesCmd}",
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800159 },
160 Restat: true,
161 },
Colin Cross66f78822018-05-02 12:58:28 -0700162 "flags", "inFlags", "proguardOptions", "genDir", "genJar", "rTxt", "extraPackages")
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800163
164var fileListToFileRule = pctx.AndroidStaticRule("fileListToFile",
165 blueprint.RuleParams{
166 Command: `cp $out.rsp $out`,
167 Rspfile: "$out.rsp",
168 RspfileContent: "$in",
169 })
170
Jaewoong Jung6431ca72020-01-15 14:15:10 -0800171var mergeAssetsRule = pctx.AndroidStaticRule("mergeAssets",
172 blueprint.RuleParams{
173 Command: `${config.MergeZipsCmd} ${out} ${in}`,
174 CommandDeps: []string{"${config.MergeZipsCmd}"},
175 })
176
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800177func aapt2Link(ctx android.ModuleContext,
Colin Cross66f78822018-05-02 12:58:28 -0700178 packageRes, genJar, proguardOptions, rTxt, extraPackages android.WritablePath,
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800179 flags []string, deps android.Paths,
Jaewoong Jung6431ca72020-01-15 14:15:10 -0800180 compiledRes, compiledOverlay, assetPackages android.Paths, splitPackages android.WritablePaths) {
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800181
182 genDir := android.PathForModuleGen(ctx, "aapt2", "R")
183
184 var inFlags []string
185
186 if len(compiledRes) > 0 {
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800187 // Create a file that contains the list of all compiled resource file paths.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800188 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)
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800199 // aapt2 filepath arguments that start with "@" mean file-list files.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800200 inFlags = append(inFlags, "@"+resFileList.String())
201 }
202
203 if len(compiledOverlay) > 0 {
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800204 // Compiled overlay files are processed the same way as compiled resources.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800205 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)
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800215 // Compiled overlay files are passed over to aapt2 using -R option.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800216 inFlags = append(inFlags, "-R", "@"+overlayFileList.String())
217 }
218
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800219 // Set auxiliary outputs as implicit outputs to establish correct dependency chains.
Colin Crosse560c4a2019-03-19 16:03:11 -0700220 implicitOutputs := append(splitPackages, proguardOptions, genJar, rTxt, extraPackages)
Jaewoong Jung6431ca72020-01-15 14:15:10 -0800221 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 }
Colin Crosse560c4a2019-03-19 16:03:11 -0700234
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800235 ctx.Build(pctx, android.BuildParams{
236 Rule: aapt2LinkRule,
237 Description: "aapt2 link",
238 Implicits: deps,
Jaewoong Jung6431ca72020-01-15 14:15:10 -0800239 Output: linkOutput,
Colin Crosse560c4a2019-03-19 16:03:11 -0700240 ImplicitOutputs: implicitOutputs,
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800241 // 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.
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800245 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(),
Colin Crossa97c5d32018-03-28 14:58:31 -0700251 "rTxt": rTxt.String(),
Colin Cross66f78822018-05-02 12:58:28 -0700252 "extraPackages": extraPackages.String(),
Colin Cross3bc7ffa2017-11-22 16:19:37 -0800253 },
254 })
255}
Colin Crossf6237212018-10-29 23:14:58 -0700256
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
Jaewoong Jung60d6d572020-11-20 17:58:27 -0800263// 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.
Colin Crossf6237212018-10-29 23:14:58 -0700265func 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}