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