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