1// Copyright (C) 2021 The Android Open Source Project 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 aidl 16 17import ( 18 "android/soong/android" 19 "android/soong/genrule" 20 "strconv" 21 22 "path/filepath" 23 "strings" 24 25 "github.com/google/blueprint" 26 "github.com/google/blueprint/pathtools" 27 "github.com/google/blueprint/proptools" 28) 29 30var ( 31 aidlDirPrepareRule = pctx.StaticRule("aidlDirPrepareRule", blueprint.RuleParams{ 32 Command: `mkdir -p "${outDir}" && touch ${out} # ${in}`, 33 Description: "create ${out}", 34 }, "outDir") 35 36 aidlCppRule = pctx.StaticRule("aidlCppRule", blueprint.RuleParams{ 37 Command: `mkdir -p "${headerDir}" && ` + 38 `mkdir -p "${outDir}/staging" && ` + 39 `mkdir -p "${headerDir}/staging" && ` + 40 `${aidlCmd} --lang=${lang} ${optionalFlags} --ninja -d ${outStagingFile}.d ` + 41 `-h ${headerDir}/staging -o ${outDir}/staging ${imports} ${nextImports} ${in} && ` + 42 `rsync --checksum ${outStagingFile}.d ${out}.d && ` + 43 `rsync --checksum ${outStagingFile} ${out} && ` + 44 `( [ -z "${stagingHeaders}" ] || rsync --checksum ${stagingHeaders} ${fullHeaderDir} ) && ` + 45 `sed -i 's/\/gen\/staging\//\/gen\//g' ${out}.d && ` + 46 `rm ${outStagingFile} ${outStagingFile}.d ${stagingHeaders}`, 47 Depfile: "${out}.d", 48 Deps: blueprint.DepsGCC, 49 CommandDeps: []string{"${aidlCmd}"}, 50 Restat: true, 51 Description: "AIDL ${lang} ${in}", 52 }, "imports", "nextImports", "lang", "headerDir", "outDir", "optionalFlags", "stagingHeaders", "outStagingFile", 53 "fullHeaderDir") 54 55 aidlJavaRule = pctx.StaticRule("aidlJavaRule", blueprint.RuleParams{ 56 Command: `${aidlCmd} --lang=java ${optionalFlags} --ninja -d ${out}.d ` + 57 `-o ${outDir} ${imports} ${nextImports} ${in}`, 58 Depfile: "${out}.d", 59 Deps: blueprint.DepsGCC, 60 CommandDeps: []string{"${aidlCmd}"}, 61 Restat: true, 62 Description: "AIDL Java ${in}", 63 }, "imports", "nextImports", "outDir", "optionalFlags") 64 65 aidlRustRule = pctx.StaticRule("aidlRustRule", blueprint.RuleParams{ 66 Command: `${aidlCmd} --lang=rust ${optionalFlags} --ninja -d ${out}.d ` + 67 `-o ${outDir} ${imports} ${nextImports} ${in}`, 68 Depfile: "${out}.d", 69 Deps: blueprint.DepsGCC, 70 CommandDeps: []string{"${aidlCmd}"}, 71 Restat: true, 72 Description: "AIDL Rust ${in}", 73 }, "imports", "nextImports", "outDir", "optionalFlags") 74) 75 76type aidlGenProperties struct { 77 Srcs []string `android:"path"` 78 AidlRoot string // base directory for the input aidl file 79 Imports []string 80 Headers []string 81 Stability *string 82 Min_sdk_version *string 83 Platform_apis bool 84 Lang string // target language [java|cpp|ndk|rust] 85 BaseName string 86 GenLog bool 87 Version string 88 GenRpc bool 89 GenTrace bool 90 Unstable *bool 91 NotFrozen bool 92 RequireFrozenReason string 93 Visibility []string 94 Flags []string 95 UseUnfrozen bool 96} 97 98type aidlGenRule struct { 99 android.ModuleBase 100 101 properties aidlGenProperties 102 103 deps deps 104 implicitInputs android.Paths 105 importFlags string 106 nextImportFlags string 107 108 // A frozen aidl_interface always have a hash file 109 hashFile android.Path 110 111 genOutDir android.ModuleGenPath 112 genHeaderDir android.ModuleGenPath 113 genHeaderDeps android.Paths 114 genOutputs android.WritablePaths 115} 116 117var _ android.SourceFileProducer = (*aidlGenRule)(nil) 118var _ genrule.SourceFileGenerator = (*aidlGenRule)(nil) 119 120func (g *aidlGenRule) aidlInterface(ctx android.BaseModuleContext) *aidlInterface { 121 return ctx.GetDirectDepWithTag(g.properties.BaseName, interfaceDep).(*aidlInterface) 122} 123 124func (g *aidlGenRule) getImports(ctx android.ModuleContext) map[string]string { 125 iface := g.aidlInterface(ctx) 126 return iface.getImports(g.properties.Version) 127} 128 129func (g *aidlGenRule) GenerateAndroidBuildActions(ctx android.ModuleContext) { 130 srcs, nextImports := getPaths(ctx, g.properties.Srcs, g.properties.AidlRoot) 131 132 g.deps = getDeps(ctx, g.getImports(ctx)) 133 134 if ctx.Failed() { 135 return 136 } 137 138 genDirTimestamp := android.PathForModuleGen(ctx, "timestamp") // $out/gen/timestamp 139 g.implicitInputs = append(g.implicitInputs, genDirTimestamp) 140 g.implicitInputs = append(g.implicitInputs, g.deps.implicits...) 141 g.implicitInputs = append(g.implicitInputs, g.deps.preprocessed...) 142 143 g.nextImportFlags = strings.Join(wrap("-N", nextImports, ""), " ") 144 g.importFlags = strings.Join(wrap("-I", g.deps.imports, ""), " ") 145 146 g.genOutDir = android.PathForModuleGen(ctx) 147 g.genHeaderDir = android.PathForModuleGen(ctx, "include") 148 for _, src := range srcs { 149 outFile, headers := g.generateBuildActionsForSingleAidl(ctx, src) 150 g.genOutputs = append(g.genOutputs, outFile) 151 g.genHeaderDeps = append(g.genHeaderDeps, headers...) 152 } 153 154 // This is to clean genOutDir before generating any file 155 ctx.Build(pctx, android.BuildParams{ 156 Rule: aidlDirPrepareRule, 157 Inputs: srcs, 158 Output: genDirTimestamp, 159 Args: map[string]string{ 160 "outDir": g.genOutDir.String(), 161 }, 162 }) 163 164 // This is to trigger genrule alone 165 ctx.Build(pctx, android.BuildParams{ 166 Rule: android.Phony, 167 Output: android.PathForModuleOut(ctx, "timestamp"), // $out/timestamp 168 Inputs: g.genOutputs.Paths(), 169 }) 170} 171 172func (g *aidlGenRule) generateBuildActionsForSingleAidl(ctx android.ModuleContext, src android.Path) (android.WritablePath, android.Paths) { 173 relPath := src.Rel() 174 baseDir := strings.TrimSuffix(strings.TrimSuffix(src.String(), relPath), "/") 175 176 var ext string 177 if g.properties.Lang == langJava { 178 ext = "java" 179 } else if g.properties.Lang == langRust { 180 ext = "rs" 181 } else { 182 ext = "cpp" 183 } 184 outFile := android.PathForModuleGen(ctx, pathtools.ReplaceExtension(relPath, ext)) 185 outStagingFile := android.PathForModuleGen(ctx, pathtools.ReplaceExtension("staging/"+relPath, ext)) 186 implicits := g.implicitInputs 187 188 // default version is 1 for any stable interface 189 version := "1" 190 previousVersion := "" 191 previousApiDir := "" 192 if g.properties.Version != "" { 193 version = g.properties.Version 194 } 195 versionInt, err := strconv.Atoi(version) 196 if err != nil && g.properties.Version != "" { 197 ctx.PropertyErrorf(g.properties.Version, "Invalid Version string: %s", g.properties.Version) 198 } else if err == nil && versionInt > 1 { 199 previousVersion = strconv.Itoa(versionInt - 1) 200 previousApiDir = filepath.Join(ctx.ModuleDir(), aidlApiDir, g.properties.BaseName, previousVersion) 201 } 202 203 optionalFlags := append([]string{}, g.properties.Flags...) 204 if proptools.Bool(g.properties.Unstable) != true { 205 optionalFlags = append(optionalFlags, "--structured") 206 optionalFlags = append(optionalFlags, "--version "+version) 207 hash := "notfrozen" 208 if !strings.HasPrefix(baseDir, ctx.Config().SoongOutDir()) { 209 hashFile := android.ExistentPathForSource(ctx, baseDir, ".hash") 210 if hashFile.Valid() { 211 hash = "$$(tail -1 '" + hashFile.Path().String() + "')" 212 implicits = append(implicits, hashFile.Path()) 213 214 g.hashFile = hashFile.Path() 215 } 216 } 217 optionalFlags = append(optionalFlags, "--hash "+hash) 218 } 219 if g.properties.GenRpc { 220 optionalFlags = append(optionalFlags, "--rpc") 221 } 222 if g.properties.GenTrace { 223 optionalFlags = append(optionalFlags, "-t") 224 } 225 if g.properties.Stability != nil { 226 optionalFlags = append(optionalFlags, "--stability", *g.properties.Stability) 227 } 228 if g.properties.Platform_apis { 229 optionalFlags = append(optionalFlags, "--min_sdk_version platform_apis") 230 } else { 231 minSdkVer := proptools.StringDefault(g.properties.Min_sdk_version, "current") 232 optionalFlags = append(optionalFlags, "--min_sdk_version "+minSdkVer) 233 } 234 optionalFlags = append(optionalFlags, wrap("-p", g.deps.preprocessed.Strings(), "")...) 235 236 // If this is an unfrozen version of a previously frozen interface, we want (1) the location 237 // of the previously frozen source and (2) the previously frozen hash so the generated 238 // library can behave like both versions at run time. 239 if !g.properties.UseUnfrozen && previousVersion != "" && 240 !proptools.Bool(g.properties.Unstable) && g.hashFile == nil { 241 apiDirPath := android.ExistentPathForSource(ctx, previousApiDir) 242 if apiDirPath.Valid() { 243 optionalFlags = append(optionalFlags, "--previous_api_dir="+apiDirPath.Path().String()) 244 } else { 245 ctx.PropertyErrorf("--previous_api_dir is invalid: %s", apiDirPath.Path().String()) 246 } 247 hashFile := android.ExistentPathForSource(ctx, previousApiDir, ".hash") 248 if hashFile.Valid() { 249 previousHash := "$$(tail -1 '" + hashFile.Path().String() + "')" 250 implicits = append(implicits, hashFile.Path()) 251 optionalFlags = append(optionalFlags, "--previous_hash "+previousHash) 252 } else { 253 ctx.ModuleErrorf("Failed to find previous version's hash file in %s", previousApiDir) 254 } 255 } 256 257 var headers android.WritablePaths 258 if g.properties.Lang == langJava { 259 ctx.Build(pctx, android.BuildParams{ 260 Rule: aidlJavaRule, 261 Input: src, 262 Implicits: implicits, 263 Output: outFile, 264 Args: map[string]string{ 265 "imports": g.importFlags, 266 "nextImports": g.nextImportFlags, 267 "outDir": g.genOutDir.String(), 268 "optionalFlags": strings.Join(optionalFlags, " "), 269 }, 270 }) 271 } else if g.properties.Lang == langRust { 272 ctx.Build(pctx, android.BuildParams{ 273 Rule: aidlRustRule, 274 Input: src, 275 Implicits: implicits, 276 Output: outFile, 277 Args: map[string]string{ 278 "imports": g.importFlags, 279 "nextImports": g.nextImportFlags, 280 "outDir": g.genOutDir.String(), 281 "optionalFlags": strings.Join(optionalFlags, " "), 282 }, 283 }) 284 } else { 285 typeName := strings.TrimSuffix(filepath.Base(relPath), ".aidl") 286 packagePath := filepath.Dir(relPath) 287 baseName := typeName 288 // TODO(b/111362593): aidl_to_cpp_common.cpp uses heuristics to figure out if 289 // an interface name has a leading I. Those same heuristics have been 290 // moved here. 291 if len(baseName) >= 2 && baseName[0] == 'I' && 292 strings.ToUpper(baseName)[1] == baseName[1] { 293 baseName = strings.TrimPrefix(typeName, "I") 294 } 295 296 prefix := "" 297 if g.properties.Lang == langNdk || g.properties.Lang == langNdkPlatform { 298 prefix = "aidl" 299 } 300 301 var stagingHeaders []string 302 var fullHeaderDir = g.genHeaderDir.Join(ctx, prefix, packagePath) 303 if g.properties.Lang != langCppAnalyzer { 304 headers = append(headers, g.genHeaderDir.Join(ctx, prefix, packagePath, typeName+".h")) 305 stagingHeaders = append(stagingHeaders, g.genHeaderDir.Join(ctx, "staging/"+prefix, packagePath, typeName+".h").String()) 306 headers = append(headers, g.genHeaderDir.Join(ctx, prefix, packagePath, "Bp"+baseName+".h")) 307 stagingHeaders = append(stagingHeaders, g.genHeaderDir.Join(ctx, "staging/"+prefix, packagePath, "Bp"+baseName+".h").String()) 308 headers = append(headers, g.genHeaderDir.Join(ctx, prefix, packagePath, "Bn"+baseName+".h")) 309 stagingHeaders = append(stagingHeaders, g.genHeaderDir.Join(ctx, "staging/"+prefix, packagePath, "Bn"+baseName+".h").String()) 310 } 311 312 if g.properties.GenLog { 313 optionalFlags = append(optionalFlags, "--log") 314 } 315 316 aidlLang := g.properties.Lang 317 if aidlLang == langNdkPlatform { 318 aidlLang = "ndk" 319 } 320 321 ctx.Build(pctx, android.BuildParams{ 322 Rule: aidlCppRule, 323 Input: src, 324 Implicits: implicits, 325 Output: outFile, 326 ImplicitOutputs: headers, 327 Args: map[string]string{ 328 "imports": g.importFlags, 329 "nextImports": g.nextImportFlags, 330 "lang": aidlLang, 331 "headerDir": g.genHeaderDir.String(), 332 "fullHeaderDir": fullHeaderDir.String(), 333 "outDir": g.genOutDir.String(), 334 "outStagingFile": outStagingFile.String(), 335 "optionalFlags": strings.Join(optionalFlags, " "), 336 "stagingHeaders": strings.Join(stagingHeaders, " "), 337 }, 338 }) 339 } 340 341 return outFile, headers.Paths() 342} 343 344func (g *aidlGenRule) GeneratedSourceFiles() android.Paths { 345 return g.genOutputs.Paths() 346} 347 348func (g *aidlGenRule) Srcs() android.Paths { 349 return g.genOutputs.Paths() 350} 351 352func (g *aidlGenRule) GeneratedDeps() android.Paths { 353 return g.genHeaderDeps 354} 355 356func (g *aidlGenRule) GeneratedHeaderDirs() android.Paths { 357 return android.Paths{g.genHeaderDir} 358} 359 360func (g *aidlGenRule) DepsMutator(ctx android.BottomUpMutatorContext) { 361 ctx.AddReverseDependency(ctx.Module(), nil, aidlMetadataSingletonName) 362} 363func aidlGenFactory() android.Module { 364 g := &aidlGenRule{} 365 g.AddProperties(&g.properties) 366 android.InitAndroidModule(g) 367 return g 368} 369