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