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