| // 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/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 --structured ${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 |
| 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 <iface>-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 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: |
| headerInfo, ok := ctx.OtherModuleProvider(dep, AidlInterfaceHeadersProvider).(AidlInterfaceHeadersInfo) |
| if !ok { |
| ctx.PropertyErrorf("headers", "module %v does not provide AidlInterfaceHeadersInfo", dep.Name()) |
| return |
| } |
| deps.implicits = append(deps.implicits, headerInfo.Srcs...) |
| deps.imports = append(deps.imports, headerInfo.IncludeDir) |
| } |
| }) |
| 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 <name>-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") |
| } |
| sdkIsFinal := !ctx.Config().DefaultAppTargetSdk(ctx).IsPreview() |
| if sdkIsFinal { |
| messageFile = android.PathForSource(ctx, "system/tools/aidl/build/message_check_equality_release.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 { |
| 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 <name>-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 <name>-update-api` |
| m.updateApiTimestamp = m.makeApiDumpAsVersion(ctx, totApiDump, currentVersion, nil) |
| |
| // API dump from source is frozen as the next stable version. Triggered by `m <name>-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, |
| 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() { |
| 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...) |
| } |