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 "reflect" 20 21 "fmt" 22 "io" 23 "path/filepath" 24 "strconv" 25 "strings" 26 27 "github.com/google/blueprint" 28 "github.com/google/blueprint/proptools" 29) 30 31var ( 32 aidlDumpApiRule = pctx.StaticRule("aidlDumpApiRule", blueprint.RuleParams{ 33 Command: `rm -rf "${outDir}" && mkdir -p "${outDir}" && ` + 34 `${aidlCmd} --dumpapi --structured ${imports} ${optionalFlags} --out ${outDir} ${in} && ` + 35 `${aidlHashGen} ${outDir} ${latestVersion} ${hashFile}`, 36 CommandDeps: []string{"${aidlCmd}", "${aidlHashGen}"}, 37 }, "optionalFlags", "imports", "outDir", "hashFile", "latestVersion") 38 39 aidlCheckApiRule = pctx.StaticRule("aidlCheckApiRule", blueprint.RuleParams{ 40 Command: `(${aidlCmd} ${optionalFlags} --checkapi=${checkApiLevel} ${imports} ${old} ${new} && touch ${out}) || ` + 41 `(cat ${messageFile} && exit 1)`, 42 CommandDeps: []string{"${aidlCmd}"}, 43 Description: "AIDL CHECK API: ${new} against ${old}", 44 }, "optionalFlags", "imports", "old", "new", "messageFile", "checkApiLevel") 45 46 aidlVerifyHashRule = pctx.StaticRule("aidlVerifyHashRule", blueprint.RuleParams{ 47 Command: `if [ $$(cd '${apiDir}' && { find ./ -name "*.aidl" -print0 | LC_ALL=C sort -z | xargs -0 sha1sum && echo ${version}; } | sha1sum | cut -d " " -f 1) = $$(tail -1 '${hashFile}') ]; then ` + 48 `touch ${out}; else cat '${messageFile}' && exit 1; fi`, 49 Description: "Verify ${apiDir} files have not been modified", 50 }, "apiDir", "version", "messageFile", "hashFile") 51) 52 53type aidlApiProperties struct { 54 BaseName string 55 Srcs []string `android:"path"` 56 AidlRoot string // base directory for the input aidl file 57 Stability *string 58 Imports []string 59 Headers []string 60 Versions []string 61 Dumpapi DumpApiProperties 62 Frozen *bool 63} 64 65type aidlApi struct { 66 android.ModuleBase 67 68 properties aidlApiProperties 69 70 // for triggering api check for version X against version X-1 71 checkApiTimestamps android.WritablePaths 72 73 // for triggering updating current API 74 updateApiTimestamp android.WritablePath 75 76 // for triggering check that files have not been modified 77 checkHashTimestamps android.WritablePaths 78 79 // for triggering freezing API as the new version 80 freezeApiTimestamp android.WritablePath 81 82 // for checking for active development on unfrozen version 83 hasDevelopment android.WritablePath 84} 85 86func (m *aidlApi) apiDir() string { 87 return filepath.Join(aidlApiDir, m.properties.BaseName) 88} 89 90// `m <iface>-freeze-api` will freeze ToT as this version 91func (m *aidlApi) nextVersion() string { 92 return nextVersion(m.properties.Versions) 93} 94 95func (m *aidlApi) hasVersion() bool { 96 return len(m.properties.Versions) > 0 97} 98 99func (m *aidlApi) latestVersion() string { 100 if !m.hasVersion() { 101 return "0" 102 } 103 return m.properties.Versions[len(m.properties.Versions)-1] 104} 105 106func (m *aidlApi) isFrozen() bool { 107 return proptools.Bool(m.properties.Frozen) 108} 109 110// in order to keep original behavior for certain operations, we may want to 111// check if frozen is set. 112func (m *aidlApi) isExplicitlyUnFrozen() bool { 113 return m.properties.Frozen != nil && !proptools.Bool(m.properties.Frozen) 114} 115 116type apiDump struct { 117 version string 118 dir android.Path 119 files android.Paths 120 hashFile android.OptionalPath 121} 122 123func (m *aidlApi) getImports(ctx android.ModuleContext, version string) map[string]string { 124 iface := ctx.GetDirectDepWithTag(m.properties.BaseName, interfaceDep).(*aidlInterface) 125 return iface.getImports(version) 126} 127 128func (m *aidlApi) createApiDumpFromSource(ctx android.ModuleContext) apiDump { 129 srcs, imports := getPaths(ctx, m.properties.Srcs, m.properties.AidlRoot) 130 131 if ctx.Failed() { 132 return apiDump{} 133 } 134 135 // dumpapi uses imports for ToT("") version 136 deps := getDeps(ctx, m.getImports(ctx, m.nextVersion())) 137 imports = append(imports, deps.imports...) 138 139 var apiDir android.WritablePath 140 var apiFiles android.WritablePaths 141 var hashFile android.WritablePath 142 143 apiDir = android.PathForModuleOut(ctx, "dump") 144 for _, src := range srcs { 145 outFile := android.PathForModuleOut(ctx, "dump", src.Rel()) 146 apiFiles = append(apiFiles, outFile) 147 } 148 hashFile = android.PathForModuleOut(ctx, "dump", ".hash") 149 150 var optionalFlags []string 151 if m.properties.Stability != nil { 152 optionalFlags = append(optionalFlags, "--stability", *m.properties.Stability) 153 } 154 if proptools.Bool(m.properties.Dumpapi.No_license) { 155 optionalFlags = append(optionalFlags, "--no_license") 156 } 157 optionalFlags = append(optionalFlags, wrap("-p", deps.preprocessed.Strings(), "")...) 158 159 version := nextVersion(m.properties.Versions) 160 ctx.Build(pctx, android.BuildParams{ 161 Rule: aidlDumpApiRule, 162 Outputs: append(apiFiles, hashFile), 163 Inputs: srcs, 164 Implicits: deps.preprocessed, 165 Args: map[string]string{ 166 "optionalFlags": strings.Join(optionalFlags, " "), 167 "imports": strings.Join(wrap("-I", imports, ""), " "), 168 "outDir": apiDir.String(), 169 "hashFile": hashFile.String(), 170 "latestVersion": versionForHashGen(version), 171 }, 172 }) 173 return apiDump{version, apiDir, apiFiles.Paths(), android.OptionalPathForPath(hashFile)} 174} 175 176func wrapWithDiffCheckIfElse(m *aidlApi, rb *android.RuleBuilder, writer func(*android.RuleBuilderCommand), elseBlock func(*android.RuleBuilderCommand)) { 177 rbc := rb.Command() 178 rbc.Text("if [ \"$(cat ").Input(m.hasDevelopment).Text(")\" = \"1\" ]; then") 179 writer(rbc) 180 rbc.Text("; else") 181 elseBlock(rbc) 182 rbc.Text("; fi") 183} 184 185func wrapWithDiffCheckIf(m *aidlApi, rb *android.RuleBuilder, writer func(*android.RuleBuilderCommand), needToWrap bool) { 186 rbc := rb.Command() 187 if needToWrap { 188 rbc.Text("if [ \"$(cat ").Input(m.hasDevelopment).Text(")\" = \"1\" ]; then") 189 } 190 writer(rbc) 191 if needToWrap { 192 rbc.Text("; fi") 193 } 194} 195 196// Migrate `versions` into `version_with_info`, and then append a version if it isn't nil 197func (m *aidlApi) migrateAndAppendVersion(ctx android.ModuleContext, rb *android.RuleBuilder, version *string, transitive bool) { 198 isFreezingApi := version != nil 199 200 // Remove `versions` property which is deprecated. 201 wrapWithDiffCheckIf(m, rb, func(rbc *android.RuleBuilderCommand) { 202 rbc.BuiltTool("bpmodify"). 203 Text("-w -m " + m.properties.BaseName). 204 Text("-parameter versions -remove-property"). 205 Text(android.PathForModuleSrc(ctx, "Android.bp").String()) 206 }, isFreezingApi) 207 208 var iface *aidlInterface 209 ctx.VisitDirectDeps(func(dep android.Module) { 210 switch ctx.OtherModuleDependencyTag(dep).(type) { 211 case interfaceDepTag: 212 iface = dep.(*aidlInterface) 213 } 214 }) 215 if iface == nil { 216 ctx.ModuleErrorf("aidl_interface %s doesn't exist", m.properties.BaseName) 217 return 218 } 219 var versions []string 220 if len(iface.properties.Versions_with_info) == 0 { 221 versions = append(versions, iface.getVersions()...) 222 } 223 if isFreezingApi { 224 versions = append(versions, *version) 225 } 226 for _, v := range versions { 227 importIfaces := make(map[string]*aidlInterface) 228 ctx.VisitDirectDeps(func(dep android.Module) { 229 if _, ok := ctx.OtherModuleDependencyTag(dep).(importInterfaceDepTag); ok { 230 other := dep.(*aidlInterface) 231 importIfaces[other.BaseModuleName()] = other 232 } 233 }) 234 imports := make([]string, 0, len(iface.getImportsForVersion(v))) 235 needTransitiveFreeze := isFreezingApi && v == *version && transitive 236 237 if needTransitiveFreeze { 238 importApis := make(map[string]*aidlApi) 239 ctx.WalkDeps(func(child android.Module, parent android.Module) bool { 240 if api, ok := child.(*aidlApi); ok { 241 moduleName := strings.TrimSuffix(api.Name(), aidlApiSuffix) 242 if _, ok := importIfaces[moduleName]; ok { 243 importApis[moduleName] = api 244 return false 245 } 246 } 247 return true 248 }) 249 wrapWithDiffCheckIf(m, rb, func(rbc *android.RuleBuilderCommand) { 250 rbc.BuiltTool("bpmodify"). 251 Text("-w -m " + m.properties.BaseName). 252 Text("-parameter versions_with_info -add-literal '"). 253 Text(fmt.Sprintf(`{version: "%s", imports: [`, v)) 254 255 for _, im := range iface.getImportsForVersion(v) { 256 moduleName, version := parseModuleWithVersion(im) 257 258 // Invoke an imported interface's freeze-api only if it depends on ToT version explicitly or implicitly. 259 if version == importIfaces[moduleName].nextVersion() || !hasVersionSuffix(im) { 260 rb.Command().Text(fmt.Sprintf(`echo "Call %s-freeze-api because %s depends on %s."`, moduleName, m.properties.BaseName, moduleName)) 261 rbc.Implicit(importApis[moduleName].freezeApiTimestamp) 262 } 263 if hasVersionSuffix(im) { 264 rbc.Text(fmt.Sprintf(`"%s",`, im)) 265 } else { 266 rbc.Text("\"" + im + "-V'" + `$(if [ "$(cat `). 267 Input(importApis[im].hasDevelopment). 268 Text(`)" = "1" ]; then echo "` + importIfaces[im].nextVersion() + 269 `"; else echo "` + importIfaces[im].latestVersion() + `"; fi)'", `) 270 } 271 } 272 rbc.Text("]}' "). 273 Text(android.PathForModuleSrc(ctx, "Android.bp").String()). 274 Text("&&"). 275 BuiltTool("bpmodify"). 276 Text("-w -m " + m.properties.BaseName). 277 Text("-parameter frozen -set-bool true"). 278 Text(android.PathForModuleSrc(ctx, "Android.bp").String()) 279 }, isFreezingApi) 280 } else { 281 for _, im := range iface.getImportsForVersion(v) { 282 if hasVersionSuffix(im) { 283 imports = append(imports, im) 284 } else { 285 versionSuffix := importIfaces[im].latestVersion() 286 if !importIfaces[im].hasVersion() || 287 importIfaces[im].isExplicitlyUnFrozen() { 288 versionSuffix = importIfaces[im].nextVersion() 289 } 290 imports = append(imports, im+"-V"+versionSuffix) 291 } 292 } 293 importsStr := strings.Join(wrap(`"`, imports, `"`), ", ") 294 data := fmt.Sprintf(`{version: "%s", imports: [%s]}`, v, importsStr) 295 296 // Also modify Android.bp file to add the new version to the 'versions_with_info' property. 297 wrapWithDiffCheckIf(m, rb, func(rbc *android.RuleBuilderCommand) { 298 rbc.BuiltTool("bpmodify"). 299 Text("-w -m " + m.properties.BaseName). 300 Text("-parameter versions_with_info -add-literal '" + data + "' "). 301 Text(android.PathForModuleSrc(ctx, "Android.bp").String()). 302 Text("&&"). 303 BuiltTool("bpmodify"). 304 Text("-w -m " + m.properties.BaseName). 305 Text("-parameter frozen -set-bool true"). 306 Text(android.PathForModuleSrc(ctx, "Android.bp").String()) 307 }, isFreezingApi) 308 } 309 } 310} 311 312func (m *aidlApi) makeApiDumpAsVersion(ctx android.ModuleContext, dump apiDump, version string, latestVersionDump *apiDump) android.WritablePath { 313 creatingNewVersion := version != currentVersion 314 moduleDir := android.PathForModuleSrc(ctx).String() 315 targetDir := filepath.Join(moduleDir, m.apiDir(), version) 316 rb := android.NewRuleBuilder(pctx, ctx) 317 transitive := ctx.Config().IsEnvTrue("AIDL_TRANSITIVE_FREEZE") 318 var actionWord string 319 if creatingNewVersion { 320 actionWord = "Making" 321 // We are asked to create a new version. But before doing that, check if the given 322 // dump is the same as the latest version. If so, don't create a new version, 323 // otherwise we will be unnecessarily creating many versions. 324 // Copy the given dump to the target directory only when the equality check failed 325 // (i.e. `has_development` file contains "1"). 326 wrapWithDiffCheckIf(m, rb, func(rbc *android.RuleBuilderCommand) { 327 rbc.Text("mkdir -p " + targetDir + " && "). 328 Text("cp -rf " + dump.dir.String() + "/. " + targetDir).Implicits(dump.files) 329 }, true /* needToWrap */) 330 wrapWithDiffCheckIfElse(m, rb, func(rbc *android.RuleBuilderCommand) { 331 rbc.Text(fmt.Sprintf(`echo "There is change between ToT version and the latest stable version. Freezing %s-V%s."`, m.properties.BaseName, version)) 332 }, func(rbc *android.RuleBuilderCommand) { 333 rbc.Text(fmt.Sprintf(`echo "There is no change from the latest stable version of %s. Nothing happened."`, m.properties.BaseName)) 334 }) 335 m.migrateAndAppendVersion(ctx, rb, &version, transitive) 336 } else { 337 actionWord = "Updating" 338 if m.isFrozen() { 339 rb.Command().BuiltTool("bpmodify"). 340 Text("-w -m " + m.properties.BaseName). 341 Text("-parameter frozen -set-bool false"). 342 Text(android.PathForModuleSrc(ctx, "Android.bp").String()) 343 344 } 345 // We are updating the current version. Don't copy .hash to the current dump 346 rb.Command().Text("mkdir -p " + targetDir) 347 rb.Command().Text("rsync --recursive --update --delete-before " + dump.dir.String() + "/* " + targetDir).Implicits(dump.files) 348 m.migrateAndAppendVersion(ctx, rb, nil, false) 349 } 350 351 timestampFile := android.PathForModuleOut(ctx, "update_or_freeze_api_"+version+".timestamp") 352 // explicitly don't touch timestamp, so that the command can be run repeatedly 353 rb.Command().Text("true").ImplicitOutput(timestampFile) 354 355 rb.Build("dump_aidl_api_"+m.properties.BaseName+"_"+version, actionWord+" AIDL API dump version "+version+" for "+m.properties.BaseName+" (see "+targetDir+")") 356 return timestampFile 357} 358 359type deps struct { 360 preprocessed android.Paths 361 implicits android.Paths 362 imports []string 363} 364 365// calculates import flags(-I) from deps. 366// When the target is ToT, use ToT of imported interfaces. If not, we use "current" snapshot of 367// imported interfaces. 368func getDeps(ctx android.ModuleContext, versionedImports map[string]string) deps { 369 var deps deps 370 ctx.VisitDirectDeps(func(dep android.Module) { 371 switch ctx.OtherModuleDependencyTag(dep).(type) { 372 case importInterfaceDepTag: 373 iface := dep.(*aidlInterface) 374 if version, ok := versionedImports[iface.BaseModuleName()]; ok { 375 if iface.preprocessed[version] == nil { 376 ctx.ModuleErrorf("can't import %v's preprocessed(version=%v)", iface.BaseModuleName(), version) 377 } 378 deps.preprocessed = append(deps.preprocessed, iface.preprocessed[version]) 379 } 380 case interfaceDepTag: 381 iface := dep.(*aidlInterface) 382 deps.imports = append(deps.imports, iface.properties.Include_dirs...) 383 case apiDepTag: 384 api := dep.(*aidlApi) 385 // add imported module's checkapiTimestamps as implicits to make sure that imported apiDump is up-to-date 386 deps.implicits = append(deps.implicits, api.checkApiTimestamps.Paths()...) 387 deps.implicits = append(deps.implicits, api.checkHashTimestamps.Paths()...) 388 deps.implicits = append(deps.implicits, api.hasDevelopment) 389 case interfaceHeadersDepTag: 390 headerInfo, ok := ctx.OtherModuleProvider(dep, AidlInterfaceHeadersProvider).(AidlInterfaceHeadersInfo) 391 if !ok { 392 ctx.PropertyErrorf("headers", "module %v does not provide AidlInterfaceHeadersInfo", dep.Name()) 393 return 394 } 395 deps.implicits = append(deps.implicits, headerInfo.Srcs...) 396 deps.imports = append(deps.imports, headerInfo.IncludeDir) 397 } 398 }) 399 return deps 400} 401 402func (m *aidlApi) checkApi(ctx android.ModuleContext, oldDump, newDump apiDump, checkApiLevel string, messageFile android.Path) android.WritablePath { 403 newVersion := newDump.dir.Base() 404 timestampFile := android.PathForModuleOut(ctx, "checkapi_"+newVersion+".timestamp") 405 406 // --checkapi(old,new) should use imports for "new" 407 deps := getDeps(ctx, m.getImports(ctx, newDump.version)) 408 var implicits android.Paths 409 implicits = append(implicits, deps.implicits...) 410 implicits = append(implicits, deps.preprocessed...) 411 implicits = append(implicits, oldDump.files...) 412 implicits = append(implicits, newDump.files...) 413 implicits = append(implicits, messageFile) 414 415 var optionalFlags []string 416 if m.properties.Stability != nil { 417 optionalFlags = append(optionalFlags, "--stability", *m.properties.Stability) 418 } 419 optionalFlags = append(optionalFlags, wrap("-p", deps.preprocessed.Strings(), "")...) 420 421 ctx.Build(pctx, android.BuildParams{ 422 Rule: aidlCheckApiRule, 423 Implicits: implicits, 424 Output: timestampFile, 425 Args: map[string]string{ 426 "optionalFlags": strings.Join(optionalFlags, " "), 427 "imports": strings.Join(wrap("-I", deps.imports, ""), " "), 428 "old": oldDump.dir.String(), 429 "new": newDump.dir.String(), 430 "messageFile": messageFile.String(), 431 "checkApiLevel": checkApiLevel, 432 }, 433 }) 434 return timestampFile 435} 436 437func (m *aidlApi) checkCompatibility(ctx android.ModuleContext, oldDump, newDump apiDump) android.WritablePath { 438 messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_compatibility.txt") 439 return m.checkApi(ctx, oldDump, newDump, "compatible", messageFile) 440} 441 442func (m *aidlApi) checkEquality(ctx android.ModuleContext, oldDump apiDump, newDump apiDump) android.WritablePath { 443 // Use different messages depending on whether platform SDK is finalized or not. 444 // In case when it is finalized, we should never allow updating the already frozen API. 445 // If it's not finalized, we let users to update the current version by invoking 446 // `m <name>-update-api`. 447 var messageFile android.SourcePath 448 if m.isFrozen() { 449 messageFile = android.PathForSource(ctx, "system/tools/aidl/build/message_check_equality_frozen.txt") 450 } else { 451 messageFile = android.PathForSource(ctx, "system/tools/aidl/build/message_check_equality.txt") 452 } 453 sdkIsFinal := !ctx.Config().DefaultAppTargetSdk(ctx).IsPreview() 454 if sdkIsFinal { 455 messageFile = android.PathForSource(ctx, "system/tools/aidl/build/message_check_equality_release.txt") 456 } 457 formattedMessageFile := android.PathForModuleOut(ctx, "message_check_equality.txt") 458 rb := android.NewRuleBuilder(pctx, ctx) 459 rb.Command().Text("sed").Flag(" s/%s/" + m.properties.BaseName + "/g ").Input(messageFile).Text(" > ").Output(formattedMessageFile) 460 rb.Build("format_message_"+m.properties.BaseName, "") 461 462 return m.checkApi(ctx, oldDump, newDump, "equal", formattedMessageFile) 463} 464 465func (m *aidlApi) checkIntegrity(ctx android.ModuleContext, dump apiDump) android.WritablePath { 466 version := dump.dir.Base() 467 timestampFile := android.PathForModuleOut(ctx, "checkhash_"+version+".timestamp") 468 messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_integrity.txt") 469 470 var implicits android.Paths 471 implicits = append(implicits, dump.files...) 472 implicits = append(implicits, dump.hashFile.Path()) 473 implicits = append(implicits, messageFile) 474 ctx.Build(pctx, android.BuildParams{ 475 Rule: aidlVerifyHashRule, 476 Implicits: implicits, 477 Output: timestampFile, 478 Args: map[string]string{ 479 "apiDir": dump.dir.String(), 480 "version": versionForHashGen(version), 481 "hashFile": dump.hashFile.Path().String(), 482 "messageFile": messageFile.String(), 483 }, 484 }) 485 return timestampFile 486} 487 488// Get the `latest` versions of the imported AIDL interfaces. If an interface is frozen, this is the 489// last frozen version, if it is `frozen: false` this is the last frozen version + 1, if `frozen` is 490// not set this is the last frozen version because we don't know if there are changes or not to the 491// interface. 492// map["foo":"3", "bar":1] 493func (m *aidlApi) getLatestImportVersions(ctx android.ModuleContext) map[string]string { 494 var latest_versions = make(map[string]string) 495 ctx.VisitDirectDeps(func(dep android.Module) { 496 switch ctx.OtherModuleDependencyTag(dep).(type) { 497 case apiDepTag: 498 api := dep.(*aidlApi) 499 if len(api.properties.Versions) > 0 { 500 if api.properties.Frozen == nil || api.isFrozen() { 501 latest_versions[api.properties.BaseName] = api.latestVersion() 502 } else { 503 latest_versions[api.properties.BaseName] = api.nextVersion() 504 } 505 } else { 506 latest_versions[api.properties.BaseName] = "1" 507 } 508 } 509 }) 510 return latest_versions 511} 512 513func (m *aidlApi) checkForDevelopment(ctx android.ModuleContext, latestVersionDump *apiDump, totDump apiDump) android.WritablePath { 514 hasDevPath := android.PathForModuleOut(ctx, "has_development") 515 rb := android.NewRuleBuilder(pctx, ctx) 516 rb.Command().Text("rm -f " + hasDevPath.String()) 517 if latestVersionDump != nil { 518 current_imports := m.getImports(ctx, currentVersion) 519 last_frozen_imports := m.getImports(ctx, m.properties.Versions[len(m.properties.Versions)-1]) 520 var latest_versions = m.getLatestImportVersions(ctx) 521 // replace any "latest" version with the version number from latest_versions 522 for import_name, latest_version := range current_imports { 523 if latest_version == "latest" { 524 current_imports[import_name] = latest_versions[import_name] 525 } 526 } 527 for import_name, latest_version := range last_frozen_imports { 528 if latest_version == "latest" { 529 last_frozen_imports[import_name] = latest_versions[import_name] 530 } 531 } 532 different_imports := false 533 if !reflect.DeepEqual(current_imports, last_frozen_imports) { 534 if m.isFrozen() { 535 ctx.ModuleErrorf("This interface is 'frozen: true' but the imports have changed. Set 'frozen: false' to allow changes: \n Version %s imports: %s\n Version %s imports: %s\n", 536 currentVersion, 537 fmt.Sprint(current_imports), 538 m.properties.Versions[len(m.properties.Versions)-1], 539 fmt.Sprint(last_frozen_imports)) 540 return hasDevPath 541 } 542 different_imports = true 543 } 544 // checkapi(latest, tot) should use imports for nextVersion(=tot) 545 hasDevCommand := rb.Command() 546 if !different_imports { 547 hasDevCommand.BuiltTool("aidl").FlagWithArg("--checkapi=", "equal") 548 if m.properties.Stability != nil { 549 hasDevCommand.FlagWithArg("--stability ", *m.properties.Stability) 550 } 551 deps := getDeps(ctx, m.getImports(ctx, m.nextVersion())) 552 hasDevCommand. 553 FlagForEachArg("-I", deps.imports).Implicits(deps.implicits). 554 FlagForEachInput("-p", deps.preprocessed). 555 Text(latestVersionDump.dir.String()).Implicits(latestVersionDump.files). 556 Text(totDump.dir.String()).Implicits(totDump.files) 557 if m.isExplicitlyUnFrozen() { 558 // Throw an error if checkapi returns with no differences 559 msg := fmt.Sprintf("echo \"Interface %s can not be marked \\`frozen: false\\` if there are no changes "+ 560 "between the current version and the last frozen version.\"", m.properties.BaseName) 561 hasDevCommand. 562 Text(fmt.Sprintf("2> /dev/null && %s && exit -1 || echo $? >", msg)).Output(hasDevPath) 563 } else { 564 hasDevCommand. 565 Text("2> /dev/null; echo $? >").Output(hasDevPath) 566 } 567 } else { 568 // We know there are different imports which means has_development must be true 569 hasDevCommand.Text("echo 1 >").Output(hasDevPath) 570 } 571 } else { 572 rb.Command().Text("echo 1 >").Output(hasDevPath) 573 } 574 rb.Build("check_for_development", "") 575 return hasDevPath 576} 577 578func (m *aidlApi) GenerateAndroidBuildActions(ctx android.ModuleContext) { 579 // An API dump is created from source and it is compared against the API dump of the 580 // 'current' (yet-to-be-finalized) version. By checking this we enforce that any change in 581 // the AIDL interface is gated by the AIDL API review even before the interface is frozen as 582 // a new version. 583 totApiDump := m.createApiDumpFromSource(ctx) 584 currentApiDir := android.ExistentPathForSource(ctx, ctx.ModuleDir(), m.apiDir(), currentVersion) 585 var currentApiDump apiDump 586 if currentApiDir.Valid() { 587 currentApiDump = apiDump{ 588 version: nextVersion(m.properties.Versions), 589 dir: currentApiDir.Path(), 590 files: ctx.Glob(filepath.Join(currentApiDir.Path().String(), "**/*.aidl"), nil), 591 hashFile: android.ExistentPathForSource(ctx, ctx.ModuleDir(), m.apiDir(), currentVersion, ".hash"), 592 } 593 checked := m.checkEquality(ctx, currentApiDump, totApiDump) 594 m.checkApiTimestamps = append(m.checkApiTimestamps, checked) 595 } else { 596 // The "current" directory might not exist, in case when the interface is first created. 597 // Instruct user to create one by executing `m <name>-update-api`. 598 rb := android.NewRuleBuilder(pctx, ctx) 599 ifaceName := m.properties.BaseName 600 rb.Command().Text(fmt.Sprintf(`echo "API dump for the current version of AIDL interface %s does not exist."`, ifaceName)) 601 rb.Command().Text(fmt.Sprintf(`echo "Run the command \"m %s-update-api\" or add \"unstable: true\" to the build rule for the interface if it does not need to be versioned"`, ifaceName)) 602 // This file will never be created. Otherwise, the build will pass simply by running 'm; m'. 603 alwaysChecked := android.PathForModuleOut(ctx, "checkapi_current.timestamp") 604 rb.Command().Text("false").ImplicitOutput(alwaysChecked) 605 rb.Build("check_current_aidl_api", "") 606 m.checkApiTimestamps = append(m.checkApiTimestamps, alwaysChecked) 607 } 608 609 // Also check that version X is backwards compatible with version X-1. 610 // "current" is checked against the latest version. 611 var dumps []apiDump 612 for _, ver := range m.properties.Versions { 613 apiDir := filepath.Join(ctx.ModuleDir(), m.apiDir(), ver) 614 apiDirPath := android.ExistentPathForSource(ctx, apiDir) 615 if apiDirPath.Valid() { 616 hashFilePath := filepath.Join(apiDir, ".hash") 617 dump := apiDump{ 618 version: ver, 619 dir: apiDirPath.Path(), 620 files: ctx.Glob(filepath.Join(apiDirPath.String(), "**/*.aidl"), nil), 621 hashFile: android.ExistentPathForSource(ctx, hashFilePath), 622 } 623 if !dump.hashFile.Valid() { 624 // We should show the source path of hash_gen because aidl_hash_gen cannot be built due to build error. 625 cmd := fmt.Sprintf(`(croot && system/tools/aidl/build/hash_gen.sh %s %s %s)`, apiDir, versionForHashGen(ver), hashFilePath) 626 ctx.ModuleErrorf("A frozen aidl_interface must have '.hash' file, but %s-V%s doesn't have it. Use the command below to generate a hash (DANGER: this should not normally happen. If an interface is changed downstream, it may cause undefined behavior, test failures, unexplained weather conditions, or otherwise broad malfunction of society. DO NOT RUN THIS COMMAND TO BREAK APIS. DO NOT!).\n%s\n", 627 m.properties.BaseName, ver, cmd) 628 } 629 dumps = append(dumps, dump) 630 } else if ctx.Config().AllowMissingDependencies() { 631 ctx.AddMissingDependencies([]string{apiDir}) 632 } else { 633 ctx.ModuleErrorf("API version %s path %s does not exist", ver, apiDir) 634 } 635 } 636 var latestVersionDump *apiDump 637 if len(dumps) >= 1 { 638 latestVersionDump = &dumps[len(dumps)-1] 639 } 640 if currentApiDir.Valid() { 641 dumps = append(dumps, currentApiDump) 642 } 643 for i, _ := range dumps { 644 if dumps[i].hashFile.Valid() { 645 checkHashTimestamp := m.checkIntegrity(ctx, dumps[i]) 646 m.checkHashTimestamps = append(m.checkHashTimestamps, checkHashTimestamp) 647 } 648 649 if i == 0 { 650 continue 651 } 652 checked := m.checkCompatibility(ctx, dumps[i-1], dumps[i]) 653 m.checkApiTimestamps = append(m.checkApiTimestamps, checked) 654 } 655 656 // Check for active development on the unfrozen version 657 m.hasDevelopment = m.checkForDevelopment(ctx, latestVersionDump, totApiDump) 658 659 // API dump from source is updated to the 'current' version. Triggered by `m <name>-update-api` 660 m.updateApiTimestamp = m.makeApiDumpAsVersion(ctx, totApiDump, currentVersion, nil) 661 662 // API dump from source is frozen as the next stable version. Triggered by `m <name>-freeze-api` 663 nextVersion := m.nextVersion() 664 m.freezeApiTimestamp = m.makeApiDumpAsVersion(ctx, totApiDump, nextVersion, latestVersionDump) 665 666 nextApiDir := filepath.Join(ctx.ModuleDir(), m.apiDir(), nextVersion) 667 if android.ExistentPathForSource(ctx, nextApiDir).Valid() { 668 ctx.ModuleErrorf("API Directory exists for version %s path %s exists, but it is not specified in versions field.", nextVersion, nextApiDir) 669 } 670} 671 672func (m *aidlApi) AndroidMk() android.AndroidMkData { 673 return android.AndroidMkData{ 674 Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { 675 android.WriteAndroidMkData(w, data) 676 targetName := m.properties.BaseName + "-freeze-api" 677 fmt.Fprintln(w, ".PHONY:", targetName) 678 fmt.Fprintln(w, targetName+":", m.freezeApiTimestamp.String()) 679 680 targetName = m.properties.BaseName + "-update-api" 681 fmt.Fprintln(w, ".PHONY:", targetName) 682 fmt.Fprintln(w, targetName+":", m.updateApiTimestamp.String()) 683 }, 684 } 685} 686 687func (m *aidlApi) DepsMutator(ctx android.BottomUpMutatorContext) { 688 ctx.AddReverseDependency(ctx.Module(), nil, aidlMetadataSingletonName) 689} 690 691func aidlApiFactory() android.Module { 692 m := &aidlApi{} 693 m.AddProperties(&m.properties) 694 android.InitAndroidModule(m) 695 return m 696} 697 698func addApiModule(mctx android.DefaultableHookContext, i *aidlInterface) string { 699 apiModule := i.ModuleBase.Name() + aidlApiSuffix 700 srcs, aidlRoot := i.srcsForVersion(mctx, i.nextVersion()) 701 mctx.CreateModule(aidlApiFactory, &nameProperties{ 702 Name: proptools.StringPtr(apiModule), 703 }, &aidlApiProperties{ 704 BaseName: i.ModuleBase.Name(), 705 Srcs: srcs, 706 AidlRoot: aidlRoot, 707 Stability: i.properties.Stability, 708 Imports: i.properties.Imports, 709 Headers: i.properties.Headers, 710 Versions: i.getVersions(), 711 Dumpapi: i.properties.Dumpapi, 712 Frozen: i.properties.Frozen, 713 }) 714 return apiModule 715} 716 717func versionForHashGen(ver string) string { 718 // aidlHashGen uses the version before current version. If it has never been frozen, return 'latest-version'. 719 verInt, _ := strconv.Atoi(ver) 720 if verInt > 1 { 721 return strconv.Itoa(verInt - 1) 722 } 723 return "latest-version" 724} 725 726func init() { 727 android.RegisterSingletonType("aidl-freeze-api", freezeApiSingletonFactory) 728} 729 730func freezeApiSingletonFactory() android.Singleton { 731 return &freezeApiSingleton{} 732} 733 734type freezeApiSingleton struct{} 735 736func (f *freezeApiSingleton) GenerateBuildActions(ctx android.SingletonContext) { 737 var files android.Paths 738 ctx.VisitAllModules(func(module android.Module) { 739 if !module.Enabled() { 740 return 741 } 742 if m, ok := module.(*aidlApi); ok { 743 ownersToFreeze := strings.Fields(ctx.Config().Getenv("AIDL_FREEZE_OWNERS")) 744 var shouldBeFrozen bool 745 if len(ownersToFreeze) > 0 { 746 shouldBeFrozen = android.InList(m.Owner(), ownersToFreeze) 747 } else { 748 shouldBeFrozen = m.Owner() == "" 749 } 750 if shouldBeFrozen { 751 files = append(files, m.freezeApiTimestamp) 752 } 753 } 754 }) 755 ctx.Phony("aidl-freeze-api", files...) 756} 757