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