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