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