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