• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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