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 `(cd ${outDir} && find ./ -name "*.aidl" -print0 | LC_ALL=C sort -z | xargs -0 sha1sum && echo ${latestVersion}) | sha1sum | cut -d " " -f 1 > ${hashFile} `, 35 CommandDeps: []string{"${aidlCmd}"}, 36 }, "optionalFlags", "imports", "outDir", "hashFile", "latestVersion") 37 38 aidlCheckApiRule = pctx.StaticRule("aidlCheckApiRule", blueprint.RuleParams{ 39 Command: `(${aidlCmd} ${optionalFlags} --checkapi=${checkApiLevel} ${old} ${new} && touch ${out}) || ` + 40 `(cat ${messageFile} && exit 1)`, 41 CommandDeps: []string{"${aidlCmd}"}, 42 Description: "AIDL CHECK API: ${new} against ${old}", 43 }, "optionalFlags", "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) = $$(read -r <'${hashFile}' hash extra; printf %s $$hash) ]; 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 ImportsWithoutVersion []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 80func (m *aidlApi) apiDir() string { 81 return filepath.Join(aidlApiDir, m.properties.BaseName) 82} 83 84// `m <iface>-freeze-api` will freeze ToT as this version 85func (m *aidlApi) nextVersion() string { 86 return nextVersion(m.properties.Versions) 87} 88 89type apiDump struct { 90 dir android.Path 91 files android.Paths 92 hashFile android.OptionalPath 93} 94 95func (m *aidlApi) createApiDumpFromSource(ctx android.ModuleContext) apiDump { 96 srcs, imports := getPaths(ctx, m.properties.Srcs, m.properties.AidlRoot) 97 98 if ctx.Failed() { 99 return apiDump{} 100 } 101 102 var importPaths []string 103 importPaths = append(importPaths, imports...) 104 ctx.VisitDirectDeps(func(dep android.Module) { 105 if importedAidl, ok := dep.(*aidlInterface); ok { 106 importPaths = append(importPaths, importedAidl.properties.Full_import_paths...) 107 } 108 }) 109 110 var apiDir android.WritablePath 111 var apiFiles android.WritablePaths 112 var hashFile android.WritablePath 113 114 apiDir = android.PathForModuleOut(ctx, "dump") 115 aidlRoot := android.PathForModuleSrc(ctx, m.properties.AidlRoot) 116 for _, src := range srcs { 117 baseDir := getBaseDir(ctx, src, aidlRoot) 118 relPath, _ := filepath.Rel(baseDir, src.String()) 119 outFile := android.PathForModuleOut(ctx, "dump", relPath) 120 apiFiles = append(apiFiles, outFile) 121 } 122 hashFile = android.PathForModuleOut(ctx, "dump", ".hash") 123 latestVersion := "latest-version" 124 if len(m.properties.Versions) >= 1 { 125 latestVersion = m.properties.Versions[len(m.properties.Versions)-1] 126 } 127 128 var optionalFlags []string 129 if m.properties.Stability != nil { 130 optionalFlags = append(optionalFlags, "--stability", *m.properties.Stability) 131 } 132 if proptools.Bool(m.properties.Dumpapi.No_license) { 133 optionalFlags = append(optionalFlags, "--no_license") 134 } 135 136 ctx.Build(pctx, android.BuildParams{ 137 Rule: aidlDumpApiRule, 138 Outputs: append(apiFiles, hashFile), 139 Inputs: srcs, 140 Args: map[string]string{ 141 "optionalFlags": strings.Join(optionalFlags, " "), 142 "imports": strings.Join(wrap("-I", importPaths, ""), " "), 143 "outDir": apiDir.String(), 144 "hashFile": hashFile.String(), 145 "latestVersion": latestVersion, 146 }, 147 }) 148 return apiDump{apiDir, apiFiles.Paths(), android.OptionalPathForPath(hashFile)} 149} 150 151func (m *aidlApi) makeApiDumpAsVersion(ctx android.ModuleContext, dump apiDump, version string, latestVersionDump *apiDump) android.WritablePath { 152 creatingNewVersion := version != currentVersion 153 moduleDir := android.PathForModuleSrc(ctx).String() 154 targetDir := filepath.Join(moduleDir, m.apiDir(), version) 155 rb := android.NewRuleBuilder(pctx, ctx) 156 157 if creatingNewVersion { 158 // We are asked to create a new version. But before doing that, check if the given 159 // dump is the same as the latest version. If so, don't create a new version, 160 // otherwise we will be unnecessarily creating many versions. `newVersionNeededFile` 161 // is created when the equality check fails. 162 newVersionNeededFile := android.PathForModuleOut(ctx, "updateapi_"+version+".needed") 163 rb.Command().Text("rm -f " + newVersionNeededFile.String()) 164 165 if latestVersionDump != nil { 166 equalityCheckCommand := rb.Command() 167 equalityCheckCommand.BuiltTool("aidl"). 168 FlagWithArg("--checkapi=", "equal") 169 if m.properties.Stability != nil { 170 equalityCheckCommand.FlagWithArg("--stability ", *m.properties.Stability) 171 } 172 equalityCheckCommand. 173 Text(latestVersionDump.dir.String()).Implicits(latestVersionDump.files). 174 Text(dump.dir.String()).Implicits(dump.files). 175 Text("&> /dev/null") 176 equalityCheckCommand. 177 Text("|| touch"). 178 Text(newVersionNeededFile.String()) 179 } else { 180 // If there is no latest version (i.e. we are creating the initial version) 181 // create the new version unconditionally 182 rb.Command().Text("touch").Text(newVersionNeededFile.String()) 183 } 184 185 // Copy the given dump to the target directory only when the equality check failed 186 // (i.e. `newVersionNeededFile` exists). 187 rb.Command(). 188 Text("if [ -f " + newVersionNeededFile.String() + " ]; then"). 189 Text("cp -rf " + dump.dir.String() + "/. " + targetDir).Implicits(dump.files). 190 Text("; fi") 191 192 // Also modify Android.bp file to add the new version to the 'versions' property. 193 rb.Command(). 194 Text("if [ -f " + newVersionNeededFile.String() + " ]; then"). 195 BuiltTool("bpmodify"). 196 Text("-w -m " + m.properties.BaseName). 197 Text("-parameter versions -a " + version). 198 Text(android.PathForModuleSrc(ctx, "Android.bp").String()). 199 Text("; fi") 200 201 } else { 202 // We are updating the current version. Don't copy .hash to the current dump 203 rb.Command().Text("mkdir -p " + targetDir) 204 rb.Command().Text("rm -rf " + targetDir + "/*") 205 rb.Command().Text("cp -rf " + dump.dir.String() + "/* " + targetDir).Implicits(dump.files) 206 } 207 208 timestampFile := android.PathForModuleOut(ctx, "updateapi_"+version+".timestamp") 209 rb.Command().Text("touch").Output(timestampFile) 210 211 rb.Build("dump_aidl_api"+m.properties.BaseName+"_"+version, 212 "Making AIDL API of "+m.properties.BaseName+" as version "+version) 213 return timestampFile 214} 215 216type depTag struct { 217 blueprint.BaseDependencyTag 218 name string 219} 220 221var ( 222 apiDep = depTag{name: "api"} 223 interfaceDep = depTag{name: "interface"} 224 225 importApiDep = depTag{name: "imported-api"} 226 importInterfaceDep = depTag{name: "imported-interface"} 227) 228 229// calculates import flags(-I) from deps. 230// When the target is ToT, use ToT of imported interfaces. If not, we use "current" snapshot of 231// imported interfaces. 232func getImportsFromDeps(ctx android.ModuleContext, targetIsToT bool) (importPaths []string, implicits android.Paths) { 233 ctx.VisitDirectDeps(func(dep android.Module) { 234 switch ctx.OtherModuleDependencyTag(dep) { 235 case importInterfaceDep: 236 iface := dep.(*aidlInterface) 237 if proptools.Bool(iface.properties.Unstable) || targetIsToT { 238 importPaths = append(importPaths, iface.properties.Full_import_paths...) 239 } else { 240 // use "current" snapshot from stable "imported" modules 241 currentDir := filepath.Join(ctx.OtherModuleDir(dep), aidlApiDir, iface.BaseModuleName(), currentVersion) 242 importPaths = append(importPaths, currentDir) 243 // TODO(b/189288369) this should be transitive 244 importPaths = append(importPaths, iface.properties.Include_dirs...) 245 } 246 case interfaceDep: 247 iface := dep.(*aidlInterface) 248 importPaths = append(importPaths, iface.properties.Include_dirs...) 249 case importApiDep, apiDep: 250 api := dep.(*aidlApi) 251 // add imported module's checkapiTimestamps as implicits to make sure that imported apiDump is up-to-date 252 implicits = append(implicits, api.checkApiTimestamps.Paths()...) 253 implicits = append(implicits, api.checkHashTimestamps.Paths()...) 254 } 255 }) 256 return 257} 258 259func (m *aidlApi) checkApi(ctx android.ModuleContext, oldDump, newDump apiDump, checkApiLevel string, messageFile android.Path) android.WritablePath { 260 newVersion := newDump.dir.Base() 261 timestampFile := android.PathForModuleOut(ctx, "checkapi_"+newVersion+".timestamp") 262 263 var optionalFlags []string 264 if m.properties.Stability != nil { 265 optionalFlags = append(optionalFlags, "--stability", *m.properties.Stability) 266 } 267 268 var implicits android.Paths 269 implicits = append(implicits, oldDump.files...) 270 implicits = append(implicits, newDump.files...) 271 implicits = append(implicits, messageFile) 272 ctx.Build(pctx, android.BuildParams{ 273 Rule: aidlCheckApiRule, 274 Implicits: implicits, 275 Output: timestampFile, 276 Args: map[string]string{ 277 "optionalFlags": strings.Join(optionalFlags, " "), 278 "old": oldDump.dir.String(), 279 "new": newDump.dir.String(), 280 "messageFile": messageFile.String(), 281 "checkApiLevel": checkApiLevel, 282 }, 283 }) 284 return timestampFile 285} 286 287func (m *aidlApi) checkCompatibility(ctx android.ModuleContext, oldDump, newDump apiDump) android.WritablePath { 288 messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_compatibility.txt") 289 return m.checkApi(ctx, oldDump, newDump, "compatible", messageFile) 290} 291 292func (m *aidlApi) checkEquality(ctx android.ModuleContext, oldDump apiDump, newDump apiDump) android.WritablePath { 293 // Use different messages depending on whether platform SDK is finalized or not. 294 // In case when it is finalized, we should never allow updating the already frozen API. 295 // If it's not finalized, we let users to update the current version by invoking 296 // `m <name>-update-api`. 297 messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_equality.txt") 298 sdkIsFinal := !ctx.Config().DefaultAppTargetSdk(ctx).IsPreview() 299 if sdkIsFinal { 300 messageFile = android.PathForSource(ctx, "system/tools/aidl/build/message_check_equality_release.txt") 301 } 302 formattedMessageFile := android.PathForModuleOut(ctx, "message_check_equality.txt") 303 rb := android.NewRuleBuilder(pctx, ctx) 304 rb.Command().Text("sed").Flag(" s/%s/" + m.properties.BaseName + "/g ").Input(messageFile).Text(" > ").Output(formattedMessageFile) 305 rb.Build("format_message_"+m.properties.BaseName, "") 306 307 var implicits android.Paths 308 implicits = append(implicits, oldDump.files...) 309 implicits = append(implicits, newDump.files...) 310 implicits = append(implicits, formattedMessageFile) 311 return m.checkApi(ctx, oldDump, newDump, "equal", formattedMessageFile) 312} 313 314func (m *aidlApi) checkIntegrity(ctx android.ModuleContext, dump apiDump) android.WritablePath { 315 version := dump.dir.Base() 316 timestampFile := android.PathForModuleOut(ctx, "checkhash_"+version+".timestamp") 317 messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_integrity.txt") 318 319 i, _ := strconv.Atoi(version) 320 if i == 1 { 321 version = "latest-version" 322 } else { 323 version = strconv.Itoa(i - 1) 324 } 325 326 var implicits android.Paths 327 implicits = append(implicits, dump.files...) 328 implicits = append(implicits, dump.hashFile.Path()) 329 implicits = append(implicits, messageFile) 330 ctx.Build(pctx, android.BuildParams{ 331 Rule: aidlVerifyHashRule, 332 Implicits: implicits, 333 Output: timestampFile, 334 Args: map[string]string{ 335 "apiDir": dump.dir.String(), 336 "version": version, 337 "hashFile": dump.hashFile.Path().String(), 338 "messageFile": messageFile.String(), 339 }, 340 }) 341 return timestampFile 342} 343 344func (m *aidlApi) GenerateAndroidBuildActions(ctx android.ModuleContext) { 345 // An API dump is created from source and it is compared against the API dump of the 346 // 'current' (yet-to-be-finalized) version. By checking this we enforce that any change in 347 // the AIDL interface is gated by the AIDL API review even before the interface is frozen as 348 // a new version. 349 totApiDump := m.createApiDumpFromSource(ctx) 350 currentApiDir := android.ExistentPathForSource(ctx, ctx.ModuleDir(), m.apiDir(), currentVersion) 351 var currentApiDump apiDump 352 if currentApiDir.Valid() { 353 currentApiDump = apiDump{ 354 dir: currentApiDir.Path(), 355 files: ctx.Glob(filepath.Join(currentApiDir.Path().String(), "**/*.aidl"), nil), 356 hashFile: android.ExistentPathForSource(ctx, ctx.ModuleDir(), m.apiDir(), currentVersion, ".hash"), 357 } 358 checked := m.checkEquality(ctx, currentApiDump, totApiDump) 359 m.checkApiTimestamps = append(m.checkApiTimestamps, checked) 360 } else { 361 // The "current" directory might not exist, in case when the interface is first created. 362 // Instruct user to create one by executing `m <name>-update-api`. 363 rb := android.NewRuleBuilder(pctx, ctx) 364 ifaceName := m.properties.BaseName 365 rb.Command().Text(fmt.Sprintf(`echo "API dump for the current version of AIDL interface %s does not exist."`, ifaceName)) 366 rb.Command().Text(fmt.Sprintf(`echo Run "m %s-update-api", or add "unstable: true" to the build rule `+ 367 `for the interface if it does not need to be versioned`, ifaceName)) 368 // This file will never be created. Otherwise, the build will pass simply by running 'm; m'. 369 alwaysChecked := android.PathForModuleOut(ctx, "checkapi_current.timestamp") 370 rb.Command().Text("false").ImplicitOutput(alwaysChecked) 371 rb.Build("check_current_aidl_api", "") 372 m.checkApiTimestamps = append(m.checkApiTimestamps, alwaysChecked) 373 } 374 375 // Also check that version X is backwards compatible with version X-1. 376 // "current" is checked against the latest version. 377 var dumps []apiDump 378 for _, ver := range m.properties.Versions { 379 apiDir := filepath.Join(ctx.ModuleDir(), m.apiDir(), ver) 380 apiDirPath := android.ExistentPathForSource(ctx, apiDir) 381 if apiDirPath.Valid() { 382 dumps = append(dumps, apiDump{ 383 dir: apiDirPath.Path(), 384 files: ctx.Glob(filepath.Join(apiDirPath.String(), "**/*.aidl"), nil), 385 hashFile: android.ExistentPathForSource(ctx, ctx.ModuleDir(), m.apiDir(), ver, ".hash"), 386 }) 387 } else if ctx.Config().AllowMissingDependencies() { 388 ctx.AddMissingDependencies([]string{apiDir}) 389 } else { 390 ctx.ModuleErrorf("API version %s path %s does not exist", ver, apiDir) 391 } 392 } 393 var latestVersionDump *apiDump 394 if len(dumps) >= 1 { 395 latestVersionDump = &dumps[len(dumps)-1] 396 } 397 if currentApiDir.Valid() { 398 dumps = append(dumps, currentApiDump) 399 } 400 for i, _ := range dumps { 401 if dumps[i].hashFile.Valid() { 402 checkHashTimestamp := m.checkIntegrity(ctx, dumps[i]) 403 m.checkHashTimestamps = append(m.checkHashTimestamps, checkHashTimestamp) 404 } 405 406 if i == 0 { 407 continue 408 } 409 checked := m.checkCompatibility(ctx, dumps[i-1], dumps[i]) 410 m.checkApiTimestamps = append(m.checkApiTimestamps, checked) 411 } 412 413 // API dump from source is updated to the 'current' version. Triggered by `m <name>-update-api` 414 m.updateApiTimestamp = m.makeApiDumpAsVersion(ctx, totApiDump, currentVersion, nil) 415 416 // API dump from source is frozen as the next stable version. Triggered by `m <name>-freeze-api` 417 nextVersion := m.nextVersion() 418 m.freezeApiTimestamp = m.makeApiDumpAsVersion(ctx, totApiDump, nextVersion, latestVersionDump) 419} 420 421func (m *aidlApi) AndroidMk() android.AndroidMkData { 422 return android.AndroidMkData{ 423 Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { 424 android.WriteAndroidMkData(w, data) 425 targetName := m.properties.BaseName + "-freeze-api" 426 fmt.Fprintln(w, ".PHONY:", targetName) 427 fmt.Fprintln(w, targetName+":", m.freezeApiTimestamp.String()) 428 429 targetName = m.properties.BaseName + "-update-api" 430 fmt.Fprintln(w, ".PHONY:", targetName) 431 fmt.Fprintln(w, targetName+":", m.updateApiTimestamp.String()) 432 }, 433 } 434} 435 436func (m *aidlApi) DepsMutator(ctx android.BottomUpMutatorContext) { 437 ctx.AddDependency(ctx.Module(), nil, wrap("", m.properties.ImportsWithoutVersion, aidlInterfaceSuffix)...) 438} 439 440func aidlApiFactory() android.Module { 441 m := &aidlApi{} 442 m.AddProperties(&m.properties) 443 android.InitAndroidModule(m) 444 return m 445} 446 447func addApiModule(mctx android.LoadHookContext, i *aidlInterface) string { 448 apiModule := i.ModuleBase.Name() + aidlApiSuffix 449 srcs, aidlRoot := i.srcsForVersion(mctx, i.nextVersion()) 450 mctx.CreateModule(aidlApiFactory, &nameProperties{ 451 Name: proptools.StringPtr(apiModule), 452 }, &aidlApiProperties{ 453 BaseName: i.ModuleBase.Name(), 454 Srcs: srcs, 455 AidlRoot: aidlRoot, 456 Stability: i.properties.Stability, 457 ImportsWithoutVersion: concat(i.properties.ImportsWithoutVersion, []string{i.ModuleBase.Name()}), 458 Versions: i.properties.Versions, 459 Dumpapi: i.properties.Dumpapi, 460 }) 461 return apiModule 462} 463 464func init() { 465 android.RegisterSingletonType("aidl-freeze-api", freezeApiSingletonFactory) 466} 467 468func freezeApiSingletonFactory() android.Singleton { 469 return &freezeApiSingleton{} 470} 471 472type freezeApiSingleton struct{} 473 474func (f *freezeApiSingleton) GenerateBuildActions(ctx android.SingletonContext) { 475 var files android.Paths 476 ctx.VisitAllModules(func(module android.Module) { 477 if !module.Enabled() { 478 return 479 } 480 if m, ok := module.(*aidlApi); ok { 481 files = append(files, m.freezeApiTimestamp) 482 } 483 }) 484 ctx.Phony("aidl-freeze-api", files...) 485} 486