// Copyright (C) 2021 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package aidl import ( "android/soong/aidl_library" "android/soong/android" "reflect" "fmt" "io" "path/filepath" "strconv" "strings" "github.com/google/blueprint" "github.com/google/blueprint/proptools" ) var ( aidlDumpApiRule = pctx.StaticRule("aidlDumpApiRule", blueprint.RuleParams{ Command: `rm -rf "${outDir}" && mkdir -p "${outDir}" && ` + `${aidlCmd} --dumpapi ${imports} ${optionalFlags} --out ${outDir} ${in} && ` + `${aidlHashGen} ${outDir} ${latestVersion} ${hashFile}`, CommandDeps: []string{"${aidlCmd}", "${aidlHashGen}"}, }, "optionalFlags", "imports", "outDir", "hashFile", "latestVersion") aidlCheckApiRule = pctx.StaticRule("aidlCheckApiRule", blueprint.RuleParams{ Command: `(${aidlCmd} ${optionalFlags} --checkapi=${checkApiLevel} ${imports} ${old} ${new} && touch ${out}) || ` + `(cat ${messageFile} && exit 1)`, CommandDeps: []string{"${aidlCmd}"}, Description: "AIDL CHECK API: ${new} against ${old}", }, "optionalFlags", "imports", "old", "new", "messageFile", "checkApiLevel") aidlVerifyHashRule = pctx.StaticRule("aidlVerifyHashRule", blueprint.RuleParams{ 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 ` + `touch ${out}; else cat '${messageFile}' && exit 1; fi`, Description: "Verify ${apiDir} files have not been modified", }, "apiDir", "version", "messageFile", "hashFile") ) type aidlApiProperties struct { BaseName string Srcs []string `android:"path"` AidlRoot string // base directory for the input aidl file Stability *string Unstable *bool Imports []string Headers []string Versions []string Dumpapi DumpApiProperties Frozen *bool } type aidlApi struct { android.ModuleBase properties aidlApiProperties // for triggering api check for version X against version X-1 checkApiTimestamps android.WritablePaths // for triggering updating current API updateApiTimestamp android.WritablePath // for triggering check that files have not been modified checkHashTimestamps android.WritablePaths // for triggering freezing API as the new version freezeApiTimestamp android.WritablePath // for checking for active development on unfrozen version hasDevelopment android.WritablePath } func (m *aidlApi) apiDir() string { return filepath.Join(aidlApiDir, m.properties.BaseName) } // `m -freeze-api` will freeze ToT as this version func (m *aidlApi) nextVersion() string { return nextVersion(m.properties.Versions) } func (m *aidlApi) hasVersion() bool { return len(m.properties.Versions) > 0 } func (m *aidlApi) latestVersion() string { if !m.hasVersion() { return "0" } return m.properties.Versions[len(m.properties.Versions)-1] } func (m *aidlApi) isFrozen() bool { return proptools.Bool(m.properties.Frozen) } // in order to keep original behavior for certain operations, we may want to // check if frozen is set. func (m *aidlApi) isExplicitlyUnFrozen() bool { return m.properties.Frozen != nil && !proptools.Bool(m.properties.Frozen) } type apiDump struct { version string dir android.Path files android.Paths hashFile android.OptionalPath } func (m *aidlApi) getImports(ctx android.ModuleContext, version string) map[string]string { iface := ctx.GetDirectDepWithTag(m.properties.BaseName, interfaceDep).(*aidlInterface) return iface.getImports(version) } func (m *aidlApi) createApiDumpFromSource(ctx android.ModuleContext) apiDump { srcs, imports := getPaths(ctx, m.properties.Srcs, m.properties.AidlRoot) if ctx.Failed() { return apiDump{} } // dumpapi uses imports for ToT("") version deps := getDeps(ctx, m.getImports(ctx, m.nextVersion())) imports = append(imports, deps.imports...) var apiDir android.WritablePath var apiFiles android.WritablePaths var hashFile android.WritablePath apiDir = android.PathForModuleOut(ctx, "dump") for _, src := range srcs { outFile := android.PathForModuleOut(ctx, "dump", src.Rel()) apiFiles = append(apiFiles, outFile) } hashFile = android.PathForModuleOut(ctx, "dump", ".hash") var optionalFlags []string if !proptools.Bool(m.properties.Unstable) { optionalFlags = append(optionalFlags, "--structured") } if m.properties.Stability != nil { optionalFlags = append(optionalFlags, "--stability", *m.properties.Stability) } if proptools.Bool(m.properties.Dumpapi.No_license) { optionalFlags = append(optionalFlags, "--no_license") } optionalFlags = append(optionalFlags, wrap("-p", deps.preprocessed.Strings(), "")...) version := nextVersion(m.properties.Versions) ctx.Build(pctx, android.BuildParams{ Rule: aidlDumpApiRule, Outputs: append(apiFiles, hashFile), Inputs: srcs, Implicits: deps.preprocessed, Args: map[string]string{ "optionalFlags": strings.Join(optionalFlags, " "), "imports": strings.Join(wrap("-I", imports, ""), " "), "outDir": apiDir.String(), "hashFile": hashFile.String(), "latestVersion": versionForHashGen(version), }, }) return apiDump{version, apiDir, apiFiles.Paths(), android.OptionalPathForPath(hashFile)} } func wrapWithDiffCheckIfElse(m *aidlApi, rb *android.RuleBuilder, writer func(*android.RuleBuilderCommand), elseBlock func(*android.RuleBuilderCommand)) { rbc := rb.Command() rbc.Text("if [ \"$(cat ").Input(m.hasDevelopment).Text(")\" = \"1\" ]; then") writer(rbc) rbc.Text("; else") elseBlock(rbc) rbc.Text("; fi") } func wrapWithDiffCheckIf(m *aidlApi, rb *android.RuleBuilder, writer func(*android.RuleBuilderCommand), needToWrap bool) { rbc := rb.Command() if needToWrap { rbc.Text("if [ \"$(cat ").Input(m.hasDevelopment).Text(")\" = \"1\" ]; then") } writer(rbc) if needToWrap { rbc.Text("; fi") } } // Migrate `versions` into `version_with_info`, and then append a version if it isn't nil func (m *aidlApi) migrateAndAppendVersion(ctx android.ModuleContext, rb *android.RuleBuilder, version *string, transitive bool) { isFreezingApi := version != nil // Remove `versions` property which is deprecated. wrapWithDiffCheckIf(m, rb, func(rbc *android.RuleBuilderCommand) { rbc.BuiltTool("bpmodify"). Text("-w -m " + m.properties.BaseName). Text("-parameter versions -remove-property"). Text(android.PathForModuleSrc(ctx, "Android.bp").String()) }, isFreezingApi) var iface *aidlInterface ctx.VisitDirectDeps(func(dep android.Module) { switch ctx.OtherModuleDependencyTag(dep).(type) { case interfaceDepTag: iface = dep.(*aidlInterface) } }) if iface == nil { ctx.ModuleErrorf("aidl_interface %s doesn't exist", m.properties.BaseName) return } var versions []string if len(iface.properties.Versions_with_info) == 0 { versions = append(versions, iface.getVersions()...) } if isFreezingApi { versions = append(versions, *version) } for _, v := range versions { importIfaces := make(map[string]*aidlInterface) ctx.VisitDirectDeps(func(dep android.Module) { if _, ok := ctx.OtherModuleDependencyTag(dep).(importInterfaceDepTag); ok { other := dep.(*aidlInterface) importIfaces[other.BaseModuleName()] = other } }) imports := make([]string, 0, len(iface.getImportsForVersion(v))) needTransitiveFreeze := isFreezingApi && v == *version && transitive if needTransitiveFreeze { importApis := make(map[string]*aidlApi) ctx.WalkDeps(func(child android.Module, parent android.Module) bool { if api, ok := child.(*aidlApi); ok { moduleName := strings.TrimSuffix(api.Name(), aidlApiSuffix) if _, ok := importIfaces[moduleName]; ok { importApis[moduleName] = api return false } } return true }) wrapWithDiffCheckIf(m, rb, func(rbc *android.RuleBuilderCommand) { rbc.BuiltTool("bpmodify"). Text("-w -m " + m.properties.BaseName). Text("-parameter versions_with_info -add-literal '"). Text(fmt.Sprintf(`{version: "%s", imports: [`, v)) for _, im := range iface.getImportsForVersion(v) { moduleName, version := parseModuleWithVersion(im) // Invoke an imported interface's freeze-api only if it depends on ToT version explicitly or implicitly. if version == importIfaces[moduleName].nextVersion() || !hasVersionSuffix(im) { rb.Command().Text(fmt.Sprintf(`echo "Call %s-freeze-api because %s depends on %s."`, moduleName, m.properties.BaseName, moduleName)) rbc.Implicit(importApis[moduleName].freezeApiTimestamp) } if hasVersionSuffix(im) { rbc.Text(fmt.Sprintf(`"%s",`, im)) } else { rbc.Text("\"" + im + "-V'" + `$(if [ "$(cat `). Input(importApis[im].hasDevelopment). Text(`)" = "1" ]; then echo "` + importIfaces[im].nextVersion() + `"; else echo "` + importIfaces[im].latestVersion() + `"; fi)'", `) } } rbc.Text("]}' "). Text(android.PathForModuleSrc(ctx, "Android.bp").String()). Text("&&"). BuiltTool("bpmodify"). Text("-w -m " + m.properties.BaseName). Text("-parameter frozen -set-bool true"). Text(android.PathForModuleSrc(ctx, "Android.bp").String()) }, isFreezingApi) } else { for _, im := range iface.getImportsForVersion(v) { if hasVersionSuffix(im) { imports = append(imports, im) } else { versionSuffix := importIfaces[im].latestVersion() if !importIfaces[im].hasVersion() || importIfaces[im].isExplicitlyUnFrozen() { versionSuffix = importIfaces[im].nextVersion() } imports = append(imports, im+"-V"+versionSuffix) } } importsStr := strings.Join(wrap(`"`, imports, `"`), ", ") data := fmt.Sprintf(`{version: "%s", imports: [%s]}`, v, importsStr) // Also modify Android.bp file to add the new version to the 'versions_with_info' property. wrapWithDiffCheckIf(m, rb, func(rbc *android.RuleBuilderCommand) { rbc.BuiltTool("bpmodify"). Text("-w -m " + m.properties.BaseName). Text("-parameter versions_with_info -add-literal '" + data + "' "). Text(android.PathForModuleSrc(ctx, "Android.bp").String()). Text("&&"). BuiltTool("bpmodify"). Text("-w -m " + m.properties.BaseName). Text("-parameter frozen -set-bool true"). Text(android.PathForModuleSrc(ctx, "Android.bp").String()) }, isFreezingApi) } } } func (m *aidlApi) makeApiDumpAsVersion(ctx android.ModuleContext, dump apiDump, version string, latestVersionDump *apiDump) android.WritablePath { creatingNewVersion := version != currentVersion moduleDir := android.PathForModuleSrc(ctx).String() targetDir := filepath.Join(moduleDir, m.apiDir(), version) rb := android.NewRuleBuilder(pctx, ctx) transitive := ctx.Config().IsEnvTrue("AIDL_TRANSITIVE_FREEZE") var actionWord string if creatingNewVersion { actionWord = "Making" // We are asked to create a new version. But before doing that, check if the given // dump is the same as the latest version. If so, don't create a new version, // otherwise we will be unnecessarily creating many versions. // Copy the given dump to the target directory only when the equality check failed // (i.e. `has_development` file contains "1"). wrapWithDiffCheckIf(m, rb, func(rbc *android.RuleBuilderCommand) { rbc.Text("mkdir -p " + targetDir + " && "). Text("cp -rf " + dump.dir.String() + "/. " + targetDir).Implicits(dump.files) }, true /* needToWrap */) wrapWithDiffCheckIfElse(m, rb, func(rbc *android.RuleBuilderCommand) { rbc.Text(fmt.Sprintf(`echo "There is change between ToT version and the latest stable version. Freezing %s-V%s."`, m.properties.BaseName, version)) }, func(rbc *android.RuleBuilderCommand) { rbc.Text(fmt.Sprintf(`echo "There is no change from the latest stable version of %s. Nothing happened."`, m.properties.BaseName)) }) m.migrateAndAppendVersion(ctx, rb, &version, transitive) } else { actionWord = "Updating" if m.isFrozen() { rb.Command().BuiltTool("bpmodify"). Text("-w -m " + m.properties.BaseName). Text("-parameter frozen -set-bool false"). Text(android.PathForModuleSrc(ctx, "Android.bp").String()) } // We are updating the current version. Don't copy .hash to the current dump rb.Command().Text("mkdir -p " + targetDir) rb.Command().Text("rsync --recursive --update --delete-before " + dump.dir.String() + "/* " + targetDir).Implicits(dump.files) m.migrateAndAppendVersion(ctx, rb, nil, false) } timestampFile := android.PathForModuleOut(ctx, "update_or_freeze_api_"+version+".timestamp") // explicitly don't touch timestamp, so that the command can be run repeatedly rb.Command().Text("true").ImplicitOutput(timestampFile) rb.Build("dump_aidl_api_"+m.properties.BaseName+"_"+version, actionWord+" AIDL API dump version "+version+" for "+m.properties.BaseName+" (see "+targetDir+")") return timestampFile } type deps struct { preprocessed android.Paths implicits android.Paths imports []string } // calculates import flags(-I) from deps. // When the target is ToT, use ToT of imported interfaces. If not, we use "current" snapshot of // imported interfaces. func getDeps(ctx android.ModuleContext, versionedImports map[string]string) deps { var deps deps ctx.VisitDirectDeps(func(dep android.Module) { switch ctx.OtherModuleDependencyTag(dep).(type) { case importInterfaceDepTag: iface := dep.(*aidlInterface) if version, ok := versionedImports[iface.BaseModuleName()]; ok { if iface.preprocessed[version] == nil { ctx.ModuleErrorf("can't import %v's preprocessed(version=%v)", iface.BaseModuleName(), version) } deps.preprocessed = append(deps.preprocessed, iface.preprocessed[version]) } case interfaceDepTag: iface := dep.(*aidlInterface) deps.imports = append(deps.imports, iface.properties.Include_dirs...) case apiDepTag: api := dep.(*aidlApi) // add imported module's checkapiTimestamps as implicits to make sure that imported apiDump is up-to-date deps.implicits = append(deps.implicits, api.checkApiTimestamps.Paths()...) deps.implicits = append(deps.implicits, api.checkHashTimestamps.Paths()...) deps.implicits = append(deps.implicits, api.hasDevelopment) case interfaceHeadersDepTag: aidlLibraryInfo, ok := android.OtherModuleProvider(ctx, dep, aidl_library.AidlLibraryProvider) if !ok { ctx.PropertyErrorf("headers", "module %v does not provide AidlLibraryInfo", dep.Name()) return } deps.implicits = append(deps.implicits, aidlLibraryInfo.Hdrs.ToList()...) deps.imports = append(deps.imports, android.Paths(aidlLibraryInfo.IncludeDirs.ToList()).Strings()...) } }) return deps } func (m *aidlApi) checkApi(ctx android.ModuleContext, oldDump, newDump apiDump, checkApiLevel string, messageFile android.Path) android.WritablePath { newVersion := newDump.dir.Base() timestampFile := android.PathForModuleOut(ctx, "checkapi_"+newVersion+".timestamp") // --checkapi(old,new) should use imports for "new" deps := getDeps(ctx, m.getImports(ctx, newDump.version)) var implicits android.Paths implicits = append(implicits, deps.implicits...) implicits = append(implicits, deps.preprocessed...) implicits = append(implicits, oldDump.files...) implicits = append(implicits, newDump.files...) implicits = append(implicits, messageFile) var optionalFlags []string if m.properties.Stability != nil { optionalFlags = append(optionalFlags, "--stability", *m.properties.Stability) } optionalFlags = append(optionalFlags, wrap("-p", deps.preprocessed.Strings(), "")...) ctx.Build(pctx, android.BuildParams{ Rule: aidlCheckApiRule, Implicits: implicits, Output: timestampFile, Args: map[string]string{ "optionalFlags": strings.Join(optionalFlags, " "), "imports": strings.Join(wrap("-I", deps.imports, ""), " "), "old": oldDump.dir.String(), "new": newDump.dir.String(), "messageFile": messageFile.String(), "checkApiLevel": checkApiLevel, }, }) return timestampFile } func (m *aidlApi) checkCompatibility(ctx android.ModuleContext, oldDump, newDump apiDump) android.WritablePath { messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_compatibility.txt") return m.checkApi(ctx, oldDump, newDump, "compatible", messageFile) } func (m *aidlApi) checkEquality(ctx android.ModuleContext, oldDump apiDump, newDump apiDump) android.WritablePath { // Use different messages depending on whether platform SDK is finalized or not. // In case when it is finalized, we should never allow updating the already frozen API. // If it's not finalized, we let users to update the current version by invoking // `m -update-api`. var messageFile android.SourcePath if m.isFrozen() { messageFile = android.PathForSource(ctx, "system/tools/aidl/build/message_check_equality_frozen.txt") } else { messageFile = android.PathForSource(ctx, "system/tools/aidl/build/message_check_equality.txt") } formattedMessageFile := android.PathForModuleOut(ctx, "message_check_equality.txt") rb := android.NewRuleBuilder(pctx, ctx) rb.Command().Text("sed").Flag(" s/%s/" + m.properties.BaseName + "/g ").Input(messageFile).Text(" > ").Output(formattedMessageFile) rb.Build("format_message_"+m.properties.BaseName, "") return m.checkApi(ctx, oldDump, newDump, "equal", formattedMessageFile) } func (m *aidlApi) checkIntegrity(ctx android.ModuleContext, dump apiDump) android.WritablePath { version := dump.dir.Base() timestampFile := android.PathForModuleOut(ctx, "checkhash_"+version+".timestamp") messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_integrity.txt") var implicits android.Paths implicits = append(implicits, dump.files...) implicits = append(implicits, dump.hashFile.Path()) implicits = append(implicits, messageFile) ctx.Build(pctx, android.BuildParams{ Rule: aidlVerifyHashRule, Implicits: implicits, Output: timestampFile, Args: map[string]string{ "apiDir": dump.dir.String(), "version": versionForHashGen(version), "hashFile": dump.hashFile.Path().String(), "messageFile": messageFile.String(), }, }) return timestampFile } // Get the `latest` versions of the imported AIDL interfaces. If an interface is frozen, this is the // last frozen version, if it is `frozen: false` this is the last frozen version + 1, if `frozen` is // not set this is the last frozen version because we don't know if there are changes or not to the // interface. // map["foo":"3", "bar":1] func (m *aidlApi) getLatestImportVersions(ctx android.ModuleContext) map[string]string { var latest_versions = make(map[string]string) ctx.VisitDirectDeps(func(dep android.Module) { switch ctx.OtherModuleDependencyTag(dep).(type) { case apiDepTag: api := dep.(*aidlApi) if len(api.properties.Versions) > 0 { if api.properties.Frozen == nil || api.isFrozen() { latest_versions[api.properties.BaseName] = api.latestVersion() } else { latest_versions[api.properties.BaseName] = api.nextVersion() } } else { latest_versions[api.properties.BaseName] = "1" } } }) return latest_versions } func (m *aidlApi) checkForDevelopment(ctx android.ModuleContext, latestVersionDump *apiDump, totDump apiDump) android.WritablePath { hasDevPath := android.PathForModuleOut(ctx, "has_development") rb := android.NewRuleBuilder(pctx, ctx) rb.Command().Text("rm -f " + hasDevPath.String()) if latestVersionDump != nil { current_imports := m.getImports(ctx, currentVersion) last_frozen_imports := m.getImports(ctx, m.properties.Versions[len(m.properties.Versions)-1]) var latest_versions = m.getLatestImportVersions(ctx) // replace any "latest" version with the version number from latest_versions for import_name, latest_version := range current_imports { if latest_version == "latest" { current_imports[import_name] = latest_versions[import_name] } } for import_name, latest_version := range last_frozen_imports { if latest_version == "latest" { last_frozen_imports[import_name] = latest_versions[import_name] } } different_imports := false if !reflect.DeepEqual(current_imports, last_frozen_imports) { if m.isFrozen() { 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", currentVersion, fmt.Sprint(current_imports), m.properties.Versions[len(m.properties.Versions)-1], fmt.Sprint(last_frozen_imports)) return hasDevPath } different_imports = true } // checkapi(latest, tot) should use imports for nextVersion(=tot) hasDevCommand := rb.Command() if !different_imports { hasDevCommand.BuiltTool("aidl").FlagWithArg("--checkapi=", "equal") if m.properties.Stability != nil { hasDevCommand.FlagWithArg("--stability ", *m.properties.Stability) } deps := getDeps(ctx, m.getImports(ctx, m.nextVersion())) hasDevCommand. FlagForEachArg("-I", deps.imports).Implicits(deps.implicits). FlagForEachInput("-p", deps.preprocessed). Text(latestVersionDump.dir.String()).Implicits(latestVersionDump.files). Text(totDump.dir.String()).Implicits(totDump.files) if m.isExplicitlyUnFrozen() { // Throw an error if checkapi returns with no differences msg := fmt.Sprintf("echo \"Interface %s can not be marked \\`frozen: false\\` if there are no changes "+ "between the current version and the last frozen version.\"", m.properties.BaseName) hasDevCommand. Text(fmt.Sprintf("2> /dev/null && %s && exit -1 || echo $? >", msg)).Output(hasDevPath) } else { // if is explicitly frozen if m.isFrozen() { // Throw an error if checkapi returns WITH differences msg := fmt.Sprintf("echo \"Interface %s can not be marked \\`frozen: true\\` because there are changes "+ "between the current version and the last frozen version.\"", m.properties.BaseName) hasDevCommand. Text(fmt.Sprintf("2> /dev/null || ( %s && exit -1) && echo 0 >", msg)).Output(hasDevPath) } else { hasDevCommand. Text("2> /dev/null; echo $? >").Output(hasDevPath) } } } else { // We know there are different imports which means has_development must be true hasDevCommand.Text("echo 1 >").Output(hasDevPath) } } else { rb.Command().Text("echo 1 >").Output(hasDevPath) } rb.Build("check_for_development", "") return hasDevPath } func (m *aidlApi) GenerateAndroidBuildActions(ctx android.ModuleContext) { // An API dump is created from source and it is compared against the API dump of the // 'current' (yet-to-be-finalized) version. By checking this we enforce that any change in // the AIDL interface is gated by the AIDL API review even before the interface is frozen as // a new version. totApiDump := m.createApiDumpFromSource(ctx) currentApiDir := android.ExistentPathForSource(ctx, ctx.ModuleDir(), m.apiDir(), currentVersion) var currentApiDump apiDump if currentApiDir.Valid() { currentApiDump = apiDump{ version: nextVersion(m.properties.Versions), dir: currentApiDir.Path(), files: ctx.Glob(filepath.Join(currentApiDir.Path().String(), "**/*.aidl"), nil), hashFile: android.ExistentPathForSource(ctx, ctx.ModuleDir(), m.apiDir(), currentVersion, ".hash"), } checked := m.checkEquality(ctx, currentApiDump, totApiDump) m.checkApiTimestamps = append(m.checkApiTimestamps, checked) } else { // The "current" directory might not exist, in case when the interface is first created. // Instruct user to create one by executing `m -update-api`. rb := android.NewRuleBuilder(pctx, ctx) ifaceName := m.properties.BaseName rb.Command().Text(fmt.Sprintf(`echo "API dump for the current version of AIDL interface %s does not exist."`, ifaceName)) 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)) // This file will never be created. Otherwise, the build will pass simply by running 'm; m'. alwaysChecked := android.PathForModuleOut(ctx, "checkapi_current.timestamp") rb.Command().Text("false").ImplicitOutput(alwaysChecked) rb.Build("check_current_aidl_api", "") m.checkApiTimestamps = append(m.checkApiTimestamps, alwaysChecked) } // Also check that version X is backwards compatible with version X-1. // "current" is checked against the latest version. var dumps []apiDump for _, ver := range m.properties.Versions { apiDir := filepath.Join(ctx.ModuleDir(), m.apiDir(), ver) apiDirPath := android.ExistentPathForSource(ctx, apiDir) if apiDirPath.Valid() { hashFilePath := filepath.Join(apiDir, ".hash") dump := apiDump{ version: ver, dir: apiDirPath.Path(), files: ctx.Glob(filepath.Join(apiDirPath.String(), "**/*.aidl"), nil), hashFile: android.ExistentPathForSource(ctx, hashFilePath), } if !dump.hashFile.Valid() { // We should show the source path of hash_gen because aidl_hash_gen cannot be built due to build error. cmd := fmt.Sprintf(`(croot && system/tools/aidl/build/hash_gen.sh %s %s %s)`, apiDir, versionForHashGen(ver), hashFilePath) 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", m.properties.BaseName, ver, cmd) } dumps = append(dumps, dump) } else if ctx.Config().AllowMissingDependencies() { ctx.AddMissingDependencies([]string{apiDir}) } else { ctx.ModuleErrorf("API version %s path %s does not exist", ver, apiDir) } } var latestVersionDump *apiDump if len(dumps) >= 1 { latestVersionDump = &dumps[len(dumps)-1] } if currentApiDir.Valid() { dumps = append(dumps, currentApiDump) } for i, _ := range dumps { if dumps[i].hashFile.Valid() { checkHashTimestamp := m.checkIntegrity(ctx, dumps[i]) m.checkHashTimestamps = append(m.checkHashTimestamps, checkHashTimestamp) } if i == 0 { continue } checked := m.checkCompatibility(ctx, dumps[i-1], dumps[i]) m.checkApiTimestamps = append(m.checkApiTimestamps, checked) } // Check for active development on the unfrozen version m.hasDevelopment = m.checkForDevelopment(ctx, latestVersionDump, totApiDump) // API dump from source is updated to the 'current' version. Triggered by `m -update-api` m.updateApiTimestamp = m.makeApiDumpAsVersion(ctx, totApiDump, currentVersion, nil) // API dump from source is frozen as the next stable version. Triggered by `m -freeze-api` nextVersion := m.nextVersion() m.freezeApiTimestamp = m.makeApiDumpAsVersion(ctx, totApiDump, nextVersion, latestVersionDump) nextApiDir := filepath.Join(ctx.ModuleDir(), m.apiDir(), nextVersion) if android.ExistentPathForSource(ctx, nextApiDir).Valid() { ctx.ModuleErrorf("API Directory exists for version %s path %s exists, but it is not specified in versions field.", nextVersion, nextApiDir) } } func (m *aidlApi) AndroidMk() android.AndroidMkData { return android.AndroidMkData{ Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { android.WriteAndroidMkData(w, data) targetName := m.properties.BaseName + "-freeze-api" fmt.Fprintln(w, ".PHONY:", targetName) fmt.Fprintln(w, targetName+":", m.freezeApiTimestamp.String()) targetName = m.properties.BaseName + "-update-api" fmt.Fprintln(w, ".PHONY:", targetName) fmt.Fprintln(w, targetName+":", m.updateApiTimestamp.String()) }, } } func (m *aidlApi) DepsMutator(ctx android.BottomUpMutatorContext) { ctx.AddReverseDependency(ctx.Module(), nil, aidlMetadataSingletonName) } func aidlApiFactory() android.Module { m := &aidlApi{} m.AddProperties(&m.properties) android.InitAndroidModule(m) return m } func addApiModule(mctx android.DefaultableHookContext, i *aidlInterface) string { apiModule := i.ModuleBase.Name() + aidlApiSuffix srcs, aidlRoot := i.srcsForVersion(mctx, i.nextVersion()) mctx.CreateModule(aidlApiFactory, &nameProperties{ Name: proptools.StringPtr(apiModule), }, &aidlApiProperties{ BaseName: i.ModuleBase.Name(), Srcs: srcs, AidlRoot: aidlRoot, Stability: i.properties.Stability, Unstable: i.properties.Unstable, Imports: i.properties.Imports, Headers: i.properties.Headers, Versions: i.getVersions(), Dumpapi: i.properties.Dumpapi, Frozen: i.properties.Frozen, }) return apiModule } func versionForHashGen(ver string) string { // aidlHashGen uses the version before current version. If it has never been frozen, return 'latest-version'. verInt, _ := strconv.Atoi(ver) if verInt > 1 { return strconv.Itoa(verInt - 1) } return "latest-version" } func init() { android.RegisterParallelSingletonType("aidl-freeze-api", freezeApiSingletonFactory) } func freezeApiSingletonFactory() android.Singleton { return &freezeApiSingleton{} } type freezeApiSingleton struct{} func (f *freezeApiSingleton) GenerateBuildActions(ctx android.SingletonContext) { var files android.Paths ctx.VisitAllModules(func(module android.Module) { if !module.Enabled(ctx) { return } if m, ok := module.(*aidlApi); ok { ownersToFreeze := strings.Fields(ctx.Config().Getenv("AIDL_FREEZE_OWNERS")) var shouldBeFrozen bool if len(ownersToFreeze) > 0 { shouldBeFrozen = android.InList(m.Owner(), ownersToFreeze) } else { shouldBeFrozen = m.Owner() == "" } if shouldBeFrozen { files = append(files, m.freezeApiTimestamp) } } }) ctx.Phony("aidl-freeze-api", files...) }