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