• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2021 Google Inc. All rights reserved.
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 java
16
17import (
18	"fmt"
19	"path/filepath"
20	"regexp"
21	"sort"
22	"strings"
23
24	"github.com/google/blueprint/proptools"
25
26	"android/soong/android"
27	"android/soong/bazel"
28	"android/soong/java/config"
29	"android/soong/remoteexec"
30)
31
32// The values allowed for Droidstubs' Api_levels_sdk_type
33var allowedApiLevelSdkTypes = []string{"public", "system", "module-lib", "system-server"}
34
35func init() {
36	RegisterStubsBuildComponents(android.InitRegistrationContext)
37}
38
39func RegisterStubsBuildComponents(ctx android.RegistrationContext) {
40	ctx.RegisterModuleType("stubs_defaults", StubsDefaultsFactory)
41
42	ctx.RegisterModuleType("droidstubs", DroidstubsFactory)
43	ctx.RegisterModuleType("droidstubs_host", DroidstubsHostFactory)
44
45	ctx.RegisterModuleType("prebuilt_stubs_sources", PrebuiltStubsSourcesFactory)
46}
47
48// Droidstubs
49type Droidstubs struct {
50	Javadoc
51
52	properties              DroidstubsProperties
53	apiFile                 android.Path
54	removedApiFile          android.Path
55	nullabilityWarningsFile android.WritablePath
56
57	checkCurrentApiTimestamp      android.WritablePath
58	updateCurrentApiTimestamp     android.WritablePath
59	checkLastReleasedApiTimestamp android.WritablePath
60	apiLintTimestamp              android.WritablePath
61	apiLintReport                 android.WritablePath
62
63	checkNullabilityWarningsTimestamp android.WritablePath
64
65	annotationsZip android.WritablePath
66	apiVersionsXml android.WritablePath
67
68	metadataZip android.WritablePath
69	metadataDir android.WritablePath
70}
71
72type DroidstubsProperties struct {
73	// The generated public API filename by Metalava, defaults to <module>_api.txt
74	Api_filename *string
75
76	// the generated removed API filename by Metalava, defaults to <module>_removed.txt
77	Removed_api_filename *string
78
79	Check_api struct {
80		Last_released ApiToCheck
81
82		Current ApiToCheck
83
84		Api_lint struct {
85			Enabled *bool
86
87			// If set, performs api_lint on any new APIs not found in the given signature file
88			New_since *string `android:"path"`
89
90			// If not blank, path to the baseline txt file for approved API lint violations.
91			Baseline_file *string `android:"path"`
92		}
93	}
94
95	// user can specify the version of previous released API file in order to do compatibility check.
96	Previous_api *string `android:"path"`
97
98	// is set to true, Metalava will allow framework SDK to contain annotations.
99	Annotations_enabled *bool
100
101	// a list of top-level directories containing files to merge qualifier annotations (i.e. those intended to be included in the stubs written) from.
102	Merge_annotations_dirs []string
103
104	// a list of top-level directories containing Java stub files to merge show/hide annotations from.
105	Merge_inclusion_annotations_dirs []string
106
107	// a file containing a list of classes to do nullability validation for.
108	Validate_nullability_from_list *string
109
110	// a file containing expected warnings produced by validation of nullability annotations.
111	Check_nullability_warnings *string
112
113	// if set to true, allow Metalava to generate doc_stubs source files. Defaults to false.
114	Create_doc_stubs *bool
115
116	// if set to true, cause Metalava to output Javadoc comments in the stubs source files. Defaults to false.
117	// Has no effect if create_doc_stubs: true.
118	Output_javadoc_comments *bool
119
120	// if set to false then do not write out stubs. Defaults to true.
121	//
122	// TODO(b/146727827): Remove capability when we do not need to generate stubs and API separately.
123	Generate_stubs *bool
124
125	// if set to true, provides a hint to the build system that this rule uses a lot of memory,
126	// whicih can be used for scheduling purposes
127	High_mem *bool
128
129	// if set to true, Metalava will allow framework SDK to contain API levels annotations.
130	Api_levels_annotations_enabled *bool
131
132	// Apply the api levels database created by this module rather than generating one in this droidstubs.
133	Api_levels_module *string
134
135	// the dirs which Metalava extracts API levels annotations from.
136	Api_levels_annotations_dirs []string
137
138	// the sdk kind which Metalava extracts API levels annotations from. Supports 'public', 'system', 'module-lib' and 'system-server'; defaults to public.
139	Api_levels_sdk_type *string
140
141	// the filename which Metalava extracts API levels annotations from. Defaults to android.jar.
142	Api_levels_jar_filename *string
143
144	// if set to true, collect the values used by the Dev tools and
145	// write them in files packaged with the SDK. Defaults to false.
146	Write_sdk_values *bool
147
148	// path or filegroup to file defining extension an SDK name <-> numerical ID mapping and
149	// what APIs exist in which SDKs; passed to metalava via --sdk-extensions-info
150	Extensions_info_file *string `android:"path"`
151
152	// API surface of this module. If set, the module contributes to an API surface.
153	// For the full list of available API surfaces, refer to soong/android/sdk_version.go
154	Api_surface *string
155}
156
157// Used by xsd_config
158type ApiFilePath interface {
159	ApiFilePath() android.Path
160}
161
162type ApiStubsSrcProvider interface {
163	StubsSrcJar() android.Path
164}
165
166// Provider of information about API stubs, used by java_sdk_library.
167type ApiStubsProvider interface {
168	AnnotationsZip() android.Path
169	ApiFilePath
170	RemovedApiFilePath() android.Path
171
172	ApiStubsSrcProvider
173}
174
175// droidstubs passes sources files through Metalava to generate stub .java files that only contain the API to be
176// documented, filtering out hidden classes and methods.  The resulting .java files are intended to be passed to
177// a droiddoc module to generate documentation.
178func DroidstubsFactory() android.Module {
179	module := &Droidstubs{}
180
181	module.AddProperties(&module.properties,
182		&module.Javadoc.properties)
183
184	InitDroiddocModule(module, android.HostAndDeviceSupported)
185
186	module.SetDefaultableHook(func(ctx android.DefaultableHookContext) {
187		module.createApiContribution(ctx)
188	})
189	return module
190}
191
192// droidstubs_host passes sources files through Metalava to generate stub .java files that only contain the API
193// to be documented, filtering out hidden classes and methods.  The resulting .java files are intended to be
194// passed to a droiddoc_host module to generate documentation.  Use a droidstubs_host instead of a droidstubs
195// module when symbols needed by the source files are provided by java_library_host modules.
196func DroidstubsHostFactory() android.Module {
197	module := &Droidstubs{}
198
199	module.AddProperties(&module.properties,
200		&module.Javadoc.properties)
201
202	InitDroiddocModule(module, android.HostSupported)
203	return module
204}
205
206func (d *Droidstubs) OutputFiles(tag string) (android.Paths, error) {
207	switch tag {
208	case "":
209		return android.Paths{d.stubsSrcJar}, nil
210	case ".docs.zip":
211		return android.Paths{d.docZip}, nil
212	case ".api.txt", android.DefaultDistTag:
213		// This is the default dist path for dist properties that have no tag property.
214		return android.Paths{d.apiFile}, nil
215	case ".removed-api.txt":
216		return android.Paths{d.removedApiFile}, nil
217	case ".annotations.zip":
218		return android.Paths{d.annotationsZip}, nil
219	case ".api_versions.xml":
220		return android.Paths{d.apiVersionsXml}, nil
221	default:
222		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
223	}
224}
225
226func (d *Droidstubs) AnnotationsZip() android.Path {
227	return d.annotationsZip
228}
229
230func (d *Droidstubs) ApiFilePath() android.Path {
231	return d.apiFile
232}
233
234func (d *Droidstubs) RemovedApiFilePath() android.Path {
235	return d.removedApiFile
236}
237
238func (d *Droidstubs) StubsSrcJar() android.Path {
239	return d.stubsSrcJar
240}
241
242var metalavaMergeAnnotationsDirTag = dependencyTag{name: "metalava-merge-annotations-dir"}
243var metalavaMergeInclusionAnnotationsDirTag = dependencyTag{name: "metalava-merge-inclusion-annotations-dir"}
244var metalavaAPILevelsAnnotationsDirTag = dependencyTag{name: "metalava-api-levels-annotations-dir"}
245var metalavaAPILevelsModuleTag = dependencyTag{name: "metalava-api-levels-module-tag"}
246
247func (d *Droidstubs) DepsMutator(ctx android.BottomUpMutatorContext) {
248	d.Javadoc.addDeps(ctx)
249
250	if len(d.properties.Merge_annotations_dirs) != 0 {
251		for _, mergeAnnotationsDir := range d.properties.Merge_annotations_dirs {
252			ctx.AddDependency(ctx.Module(), metalavaMergeAnnotationsDirTag, mergeAnnotationsDir)
253		}
254	}
255
256	if len(d.properties.Merge_inclusion_annotations_dirs) != 0 {
257		for _, mergeInclusionAnnotationsDir := range d.properties.Merge_inclusion_annotations_dirs {
258			ctx.AddDependency(ctx.Module(), metalavaMergeInclusionAnnotationsDirTag, mergeInclusionAnnotationsDir)
259		}
260	}
261
262	if len(d.properties.Api_levels_annotations_dirs) != 0 {
263		for _, apiLevelsAnnotationsDir := range d.properties.Api_levels_annotations_dirs {
264			ctx.AddDependency(ctx.Module(), metalavaAPILevelsAnnotationsDirTag, apiLevelsAnnotationsDir)
265		}
266	}
267
268	if d.properties.Api_levels_module != nil {
269		ctx.AddDependency(ctx.Module(), metalavaAPILevelsModuleTag, proptools.String(d.properties.Api_levels_module))
270	}
271}
272
273func (d *Droidstubs) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.OptionalPath) {
274	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") ||
275		apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") ||
276		String(d.properties.Api_filename) != "" {
277		filename := proptools.StringDefault(d.properties.Api_filename, ctx.ModuleName()+"_api.txt")
278		uncheckedApiFile := android.PathForModuleOut(ctx, "metalava", filename)
279		cmd.FlagWithOutput("--api ", uncheckedApiFile)
280		d.apiFile = uncheckedApiFile
281	} else if sourceApiFile := proptools.String(d.properties.Check_api.Current.Api_file); sourceApiFile != "" {
282		// If check api is disabled then make the source file available for export.
283		d.apiFile = android.PathForModuleSrc(ctx, sourceApiFile)
284	}
285
286	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") ||
287		apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") ||
288		String(d.properties.Removed_api_filename) != "" {
289		filename := proptools.StringDefault(d.properties.Removed_api_filename, ctx.ModuleName()+"_removed.txt")
290		uncheckedRemovedFile := android.PathForModuleOut(ctx, "metalava", filename)
291		cmd.FlagWithOutput("--removed-api ", uncheckedRemovedFile)
292		d.removedApiFile = uncheckedRemovedFile
293	} else if sourceRemovedApiFile := proptools.String(d.properties.Check_api.Current.Removed_api_file); sourceRemovedApiFile != "" {
294		// If check api is disabled then make the source removed api file available for export.
295		d.removedApiFile = android.PathForModuleSrc(ctx, sourceRemovedApiFile)
296	}
297
298	if Bool(d.properties.Write_sdk_values) {
299		d.metadataDir = android.PathForModuleOut(ctx, "metalava", "metadata")
300		cmd.FlagWithArg("--sdk-values ", d.metadataDir.String())
301	}
302
303	if stubsDir.Valid() {
304		if Bool(d.properties.Create_doc_stubs) {
305			cmd.FlagWithArg("--doc-stubs ", stubsDir.String())
306		} else {
307			cmd.FlagWithArg("--stubs ", stubsDir.String())
308			if !Bool(d.properties.Output_javadoc_comments) {
309				cmd.Flag("--exclude-documentation-from-stubs")
310			}
311		}
312	}
313}
314
315func (d *Droidstubs) annotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
316	if Bool(d.properties.Annotations_enabled) {
317		cmd.Flag("--include-annotations")
318
319		cmd.FlagWithArg("--exclude-annotation ", "androidx.annotation.RequiresApi")
320
321		validatingNullability :=
322			strings.Contains(String(d.Javadoc.properties.Args), "--validate-nullability-from-merged-stubs") ||
323				String(d.properties.Validate_nullability_from_list) != ""
324
325		migratingNullability := String(d.properties.Previous_api) != ""
326		if migratingNullability {
327			previousApi := android.PathForModuleSrc(ctx, String(d.properties.Previous_api))
328			cmd.FlagWithInput("--migrate-nullness ", previousApi)
329		}
330
331		if s := String(d.properties.Validate_nullability_from_list); s != "" {
332			cmd.FlagWithInput("--validate-nullability-from-list ", android.PathForModuleSrc(ctx, s))
333		}
334
335		if validatingNullability {
336			d.nullabilityWarningsFile = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"_nullability_warnings.txt")
337			cmd.FlagWithOutput("--nullability-warnings-txt ", d.nullabilityWarningsFile)
338		}
339
340		d.annotationsZip = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"_annotations.zip")
341		cmd.FlagWithOutput("--extract-annotations ", d.annotationsZip)
342
343		if len(d.properties.Merge_annotations_dirs) != 0 {
344			d.mergeAnnoDirFlags(ctx, cmd)
345		}
346
347		// TODO(tnorbye): find owners to fix these warnings when annotation was enabled.
348		cmd.FlagWithArg("--hide ", "HiddenTypedefConstant").
349			FlagWithArg("--hide ", "SuperfluousPrefix").
350			FlagWithArg("--hide ", "AnnotationExtraction").
351			// b/222738070
352			FlagWithArg("--hide ", "BannedThrow").
353			// b/223382732
354			FlagWithArg("--hide ", "ChangedDefault")
355	}
356}
357
358func (d *Droidstubs) mergeAnnoDirFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
359	ctx.VisitDirectDepsWithTag(metalavaMergeAnnotationsDirTag, func(m android.Module) {
360		if t, ok := m.(*ExportedDroiddocDir); ok {
361			cmd.FlagWithArg("--merge-qualifier-annotations ", t.dir.String()).Implicits(t.deps)
362		} else {
363			ctx.PropertyErrorf("merge_annotations_dirs",
364				"module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m))
365		}
366	})
367}
368
369func (d *Droidstubs) inclusionAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
370	ctx.VisitDirectDepsWithTag(metalavaMergeInclusionAnnotationsDirTag, func(m android.Module) {
371		if t, ok := m.(*ExportedDroiddocDir); ok {
372			cmd.FlagWithArg("--merge-inclusion-annotations ", t.dir.String()).Implicits(t.deps)
373		} else {
374			ctx.PropertyErrorf("merge_inclusion_annotations_dirs",
375				"module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m))
376		}
377	})
378}
379
380func (d *Droidstubs) apiLevelsAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
381	var apiVersions android.Path
382	if proptools.Bool(d.properties.Api_levels_annotations_enabled) {
383		d.apiLevelsGenerationFlags(ctx, cmd)
384		apiVersions = d.apiVersionsXml
385	} else {
386		ctx.VisitDirectDepsWithTag(metalavaAPILevelsModuleTag, func(m android.Module) {
387			if s, ok := m.(*Droidstubs); ok {
388				apiVersions = s.apiVersionsXml
389			} else {
390				ctx.PropertyErrorf("api_levels_module",
391					"module %q is not a droidstubs module", ctx.OtherModuleName(m))
392			}
393		})
394	}
395	if apiVersions != nil {
396		cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion().String())
397		cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename())
398		cmd.FlagWithInput("--apply-api-levels ", apiVersions)
399	}
400}
401
402func (d *Droidstubs) apiLevelsGenerationFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
403	if len(d.properties.Api_levels_annotations_dirs) == 0 {
404		ctx.PropertyErrorf("api_levels_annotations_dirs",
405			"has to be non-empty if api levels annotations was enabled!")
406	}
407
408	d.apiVersionsXml = android.PathForModuleOut(ctx, "metalava", "api-versions.xml")
409	cmd.FlagWithOutput("--generate-api-levels ", d.apiVersionsXml)
410
411	filename := proptools.StringDefault(d.properties.Api_levels_jar_filename, "android.jar")
412
413	var dirs []string
414	var extensions_dir string
415	ctx.VisitDirectDepsWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.Module) {
416		if t, ok := m.(*ExportedDroiddocDir); ok {
417			extRegex := regexp.MustCompile(t.dir.String() + `/extensions/[0-9]+/public/.*\.jar`)
418
419			// Grab the first extensions_dir and we find while scanning ExportedDroiddocDir.deps;
420			// ideally this should be read from prebuiltApis.properties.Extensions_*
421			for _, dep := range t.deps {
422				if extRegex.MatchString(dep.String()) && d.properties.Extensions_info_file != nil {
423					if extensions_dir == "" {
424						extensions_dir = t.dir.String() + "/extensions"
425					}
426					cmd.Implicit(dep)
427				}
428				if dep.Base() == filename {
429					cmd.Implicit(dep)
430				}
431				if filename != "android.jar" && dep.Base() == "android.jar" {
432					// Metalava implicitly searches these patterns:
433					//  prebuilts/tools/common/api-versions/android-%/android.jar
434					//  prebuilts/sdk/%/public/android.jar
435					// Add android.jar files from the api_levels_annotations_dirs directories to try
436					// to satisfy these patterns.  If Metalava can't find a match for an API level
437					// between 1 and 28 in at least one pattern it will fail.
438					cmd.Implicit(dep)
439				}
440			}
441
442			dirs = append(dirs, t.dir.String())
443		} else {
444			ctx.PropertyErrorf("api_levels_annotations_dirs",
445				"module %q is not a metalava api-levels-annotations dir", ctx.OtherModuleName(m))
446		}
447	})
448
449	// Add all relevant --android-jar-pattern patterns for Metalava.
450	// When parsing a stub jar for a specific version, Metalava picks the first pattern that defines
451	// an actual file present on disk (in the order the patterns were passed). For system APIs for
452	// privileged apps that are only defined since API level 21 (Lollipop), fallback to public stubs
453	// for older releases. Similarly, module-lib falls back to system API.
454	var sdkDirs []string
455	switch proptools.StringDefault(d.properties.Api_levels_sdk_type, "public") {
456	case "system-server":
457		sdkDirs = []string{"system-server", "module-lib", "system", "public"}
458	case "module-lib":
459		sdkDirs = []string{"module-lib", "system", "public"}
460	case "system":
461		sdkDirs = []string{"system", "public"}
462	case "public":
463		sdkDirs = []string{"public"}
464	default:
465		ctx.PropertyErrorf("api_levels_sdk_type", "needs to be one of %v", allowedApiLevelSdkTypes)
466		return
467	}
468
469	for _, sdkDir := range sdkDirs {
470		for _, dir := range dirs {
471			cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/%%/%s/%s", dir, sdkDir, filename))
472		}
473	}
474
475	if d.properties.Extensions_info_file != nil {
476		if extensions_dir == "" {
477			ctx.ModuleErrorf("extensions_info_file set, but no SDK extension dirs found")
478		}
479		info_file := android.PathForModuleSrc(ctx, *d.properties.Extensions_info_file)
480		cmd.Implicit(info_file)
481		cmd.FlagWithArg("--sdk-extensions-root ", extensions_dir)
482		cmd.FlagWithArg("--sdk-extensions-info ", info_file.String())
483	}
484}
485
486func metalavaUseRbe(ctx android.ModuleContext) bool {
487	return ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_METALAVA")
488}
489
490func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths,
491	srcJarList android.Path, bootclasspath, classpath classpath, homeDir android.WritablePath) *android.RuleBuilderCommand {
492	rule.Command().Text("rm -rf").Flag(homeDir.String())
493	rule.Command().Text("mkdir -p").Flag(homeDir.String())
494
495	cmd := rule.Command()
496	cmd.FlagWithArg("ANDROID_PREFS_ROOT=", homeDir.String())
497
498	if metalavaUseRbe(ctx) {
499		rule.Remoteable(android.RemoteRuleSupports{RBE: true})
500		execStrategy := ctx.Config().GetenvWithDefault("RBE_METALAVA_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
501		labels := map[string]string{"type": "tool", "name": "metalava"}
502		// TODO: metalava pool rejects these jobs
503		pool := ctx.Config().GetenvWithDefault("RBE_METALAVA_POOL", "java16")
504		rule.Rewrapper(&remoteexec.REParams{
505			Labels:          labels,
506			ExecStrategy:    execStrategy,
507			ToolchainInputs: []string{config.JavaCmd(ctx).String()},
508			Platform:        map[string]string{remoteexec.PoolKey: pool},
509		})
510	}
511
512	cmd.BuiltTool("metalava").ImplicitTool(ctx.Config().HostJavaToolPath(ctx, "metalava.jar")).
513		Flag(config.JavacVmFlags).
514		Flag("-J--add-opens=java.base/java.util=ALL-UNNAMED").
515		FlagWithArg("-encoding ", "UTF-8").
516		FlagWithArg("-source ", javaVersion.String()).
517		FlagWithRspFileInputList("@", android.PathForModuleOut(ctx, "metalava.rsp"), srcs).
518		FlagWithInput("@", srcJarList)
519
520	if len(bootclasspath) > 0 {
521		cmd.FlagWithInputList("-bootclasspath ", bootclasspath.Paths(), ":")
522	}
523
524	if len(classpath) > 0 {
525		cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":")
526	}
527
528	cmd.Flag("--no-banner").
529		Flag("--color").
530		Flag("--quiet").
531		Flag("--format=v2").
532		FlagWithArg("--repeat-errors-max ", "10").
533		FlagWithArg("--hide ", "UnresolvedImport").
534		FlagWithArg("--hide ", "InvalidNullabilityOverride").
535		// b/223382732
536		FlagWithArg("--hide ", "ChangedDefault")
537
538	return cmd
539}
540
541func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) {
542	deps := d.Javadoc.collectDeps(ctx)
543
544	javaVersion := getJavaVersion(ctx, String(d.Javadoc.properties.Java_version), android.SdkContext(d))
545
546	// Create rule for metalava
547
548	srcJarDir := android.PathForModuleOut(ctx, "metalava", "srcjars")
549
550	rule := android.NewRuleBuilder(pctx, ctx)
551
552	rule.Sbox(android.PathForModuleOut(ctx, "metalava"),
553		android.PathForModuleOut(ctx, "metalava.sbox.textproto")).
554		SandboxInputs()
555
556	if BoolDefault(d.properties.High_mem, false) {
557		// This metalava run uses lots of memory, restrict the number of metalava jobs that can run in parallel.
558		rule.HighMem()
559	}
560
561	generateStubs := BoolDefault(d.properties.Generate_stubs, true)
562	var stubsDir android.OptionalPath
563	if generateStubs {
564		d.Javadoc.stubsSrcJar = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"-"+"stubs.srcjar")
565		stubsDir = android.OptionalPathForPath(android.PathForModuleOut(ctx, "metalava", "stubsDir"))
566		rule.Command().Text("rm -rf").Text(stubsDir.String())
567		rule.Command().Text("mkdir -p").Text(stubsDir.String())
568	}
569
570	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars)
571
572	homeDir := android.PathForModuleOut(ctx, "metalava", "home")
573	cmd := metalavaCmd(ctx, rule, javaVersion, d.Javadoc.srcFiles, srcJarList,
574		deps.bootClasspath, deps.classpath, homeDir)
575	cmd.Implicits(d.Javadoc.implicits)
576
577	d.stubsFlags(ctx, cmd, stubsDir)
578
579	d.annotationsFlags(ctx, cmd)
580	d.inclusionAnnotationsFlags(ctx, cmd)
581	d.apiLevelsAnnotationsFlags(ctx, cmd)
582
583	d.expandArgs(ctx, cmd)
584
585	for _, o := range d.Javadoc.properties.Out {
586		cmd.ImplicitOutput(android.PathForModuleGen(ctx, o))
587	}
588
589	// Add options for the other optional tasks: API-lint and check-released.
590	// We generate separate timestamp files for them.
591
592	doApiLint := false
593	doCheckReleased := false
594
595	// Add API lint options.
596
597	if BoolDefault(d.properties.Check_api.Api_lint.Enabled, false) {
598		doApiLint = true
599
600		newSince := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Api_lint.New_since)
601		if newSince.Valid() {
602			cmd.FlagWithInput("--api-lint ", newSince.Path())
603		} else {
604			cmd.Flag("--api-lint")
605		}
606		d.apiLintReport = android.PathForModuleOut(ctx, "metalava", "api_lint_report.txt")
607		cmd.FlagWithOutput("--report-even-if-suppressed ", d.apiLintReport) // TODO:  Change to ":api-lint"
608
609		// TODO(b/154317059): Clean up this allowlist by baselining and/or checking in last-released.
610		if d.Name() != "android.car-system-stubs-docs" &&
611			d.Name() != "android.car-stubs-docs" {
612			cmd.Flag("--lints-as-errors")
613			cmd.Flag("--warnings-as-errors") // Most lints are actually warnings.
614		}
615
616		baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Api_lint.Baseline_file)
617		updatedBaselineOutput := android.PathForModuleOut(ctx, "metalava", "api_lint_baseline.txt")
618		d.apiLintTimestamp = android.PathForModuleOut(ctx, "metalava", "api_lint.timestamp")
619
620		// Note this string includes a special shell quote $' ... ', which decodes the "\n"s.
621		//
622		// TODO: metalava also has a slightly different message hardcoded. Should we unify this
623		// message and metalava's one?
624		msg := `$'` + // Enclose with $' ... '
625			`************************************************************\n` +
626			`Your API changes are triggering API Lint warnings or errors.\n` +
627			`To make these errors go away, fix the code according to the\n` +
628			`error and/or warning messages above.\n` +
629			`\n` +
630			`If it is not possible to do so, there are workarounds:\n` +
631			`\n` +
632			`1. You can suppress the errors with @SuppressLint("<id>")\n` +
633			`   where the <id> is given in brackets in the error message above.\n`
634
635		if baselineFile.Valid() {
636			cmd.FlagWithInput("--baseline:api-lint ", baselineFile.Path())
637			cmd.FlagWithOutput("--update-baseline:api-lint ", updatedBaselineOutput)
638
639			msg += fmt.Sprintf(``+
640				`2. You can update the baseline by executing the following\n`+
641				`   command:\n`+
642				`       (cd $ANDROID_BUILD_TOP && cp \\\n`+
643				`       "%s" \\\n`+
644				`       "%s")\n`+
645				`   To submit the revised baseline.txt to the main Android\n`+
646				`   repository, you will need approval.\n`, updatedBaselineOutput, baselineFile.Path())
647		} else {
648			msg += fmt.Sprintf(``+
649				`2. You can add a baseline file of existing lint failures\n`+
650				`   to the build rule of %s.\n`, d.Name())
651		}
652		// Note the message ends with a ' (single quote), to close the $' ... ' .
653		msg += `************************************************************\n'`
654
655		cmd.FlagWithArg("--error-message:api-lint ", msg)
656	}
657
658	// Add "check released" options. (Detect incompatible API changes from the last public release)
659
660	if apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") {
661		doCheckReleased = true
662
663		if len(d.Javadoc.properties.Out) > 0 {
664			ctx.PropertyErrorf("out", "out property may not be combined with check_api")
665		}
666
667		apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Api_file))
668		removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Removed_api_file))
669		baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Last_released.Baseline_file)
670		updatedBaselineOutput := android.PathForModuleOut(ctx, "metalava", "last_released_baseline.txt")
671
672		d.checkLastReleasedApiTimestamp = android.PathForModuleOut(ctx, "metalava", "check_last_released_api.timestamp")
673
674		cmd.FlagWithInput("--check-compatibility:api:released ", apiFile)
675		cmd.FlagWithInput("--check-compatibility:removed:released ", removedApiFile)
676
677		if baselineFile.Valid() {
678			cmd.FlagWithInput("--baseline:compatibility:released ", baselineFile.Path())
679			cmd.FlagWithOutput("--update-baseline:compatibility:released ", updatedBaselineOutput)
680		}
681
682		// Note this string includes quote ($' ... '), which decodes the "\n"s.
683		msg := `$'\n******************************\n` +
684			`You have tried to change the API from what has been previously released in\n` +
685			`an SDK.  Please fix the errors listed above.\n` +
686			`******************************\n'`
687
688		cmd.FlagWithArg("--error-message:compatibility:released ", msg)
689	}
690
691	if generateStubs {
692		rule.Command().
693			BuiltTool("soong_zip").
694			Flag("-write_if_changed").
695			Flag("-jar").
696			FlagWithOutput("-o ", d.Javadoc.stubsSrcJar).
697			FlagWithArg("-C ", stubsDir.String()).
698			FlagWithArg("-D ", stubsDir.String())
699	}
700
701	if Bool(d.properties.Write_sdk_values) {
702		d.metadataZip = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"-metadata.zip")
703		rule.Command().
704			BuiltTool("soong_zip").
705			Flag("-write_if_changed").
706			Flag("-d").
707			FlagWithOutput("-o ", d.metadataZip).
708			FlagWithArg("-C ", d.metadataDir.String()).
709			FlagWithArg("-D ", d.metadataDir.String())
710	}
711
712	// TODO: We don't really need two separate API files, but this is a reminiscence of how
713	// we used to run metalava separately for API lint and the "last_released" check. Unify them.
714	if doApiLint {
715		rule.Command().Text("touch").Output(d.apiLintTimestamp)
716	}
717	if doCheckReleased {
718		rule.Command().Text("touch").Output(d.checkLastReleasedApiTimestamp)
719	}
720
721	// TODO(b/183630617): rewrapper doesn't support restat rules
722	if !metalavaUseRbe(ctx) {
723		rule.Restat()
724	}
725
726	zipSyncCleanupCmd(rule, srcJarDir)
727
728	rule.Build("metalava", "metalava merged")
729
730	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") {
731
732		if len(d.Javadoc.properties.Out) > 0 {
733			ctx.PropertyErrorf("out", "out property may not be combined with check_api")
734		}
735
736		apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Api_file))
737		removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Removed_api_file))
738		baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Current.Baseline_file)
739
740		if baselineFile.Valid() {
741			ctx.PropertyErrorf("baseline_file", "current API check can't have a baseline file. (module %s)", ctx.ModuleName())
742		}
743
744		d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "check_current_api.timestamp")
745
746		rule := android.NewRuleBuilder(pctx, ctx)
747
748		// Diff command line.
749		// -F matches the closest "opening" line, such as "package android {"
750		// and "  public class Intent {".
751		diff := `diff -u -F '{ *$'`
752
753		rule.Command().Text("( true")
754		rule.Command().
755			Text(diff).
756			Input(apiFile).Input(d.apiFile)
757
758		rule.Command().
759			Text(diff).
760			Input(removedApiFile).Input(d.removedApiFile)
761
762		msg := fmt.Sprintf(`\n******************************\n`+
763			`You have tried to change the API from what has been previously approved.\n\n`+
764			`To make these errors go away, you have two choices:\n`+
765			`   1. You can add '@hide' javadoc comments (and remove @SystemApi/@TestApi/etc)\n`+
766			`      to the new methods, etc. shown in the above diff.\n\n`+
767			`   2. You can update current.txt and/or removed.txt by executing the following command:\n`+
768			`         m %s-update-current-api\n\n`+
769			`      To submit the revised current.txt to the main Android repository,\n`+
770			`      you will need approval.\n`+
771			`******************************\n`, ctx.ModuleName())
772
773		rule.Command().
774			Text("touch").Output(d.checkCurrentApiTimestamp).
775			Text(") || (").
776			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
777			Text("; exit 38").
778			Text(")")
779
780		rule.Build("metalavaCurrentApiCheck", "check current API")
781
782		d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "update_current_api.timestamp")
783
784		// update API rule
785		rule = android.NewRuleBuilder(pctx, ctx)
786
787		rule.Command().Text("( true")
788
789		rule.Command().
790			Text("cp").Flag("-f").
791			Input(d.apiFile).Flag(apiFile.String())
792
793		rule.Command().
794			Text("cp").Flag("-f").
795			Input(d.removedApiFile).Flag(removedApiFile.String())
796
797		msg = "failed to update public API"
798
799		rule.Command().
800			Text("touch").Output(d.updateCurrentApiTimestamp).
801			Text(") || (").
802			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
803			Text("; exit 38").
804			Text(")")
805
806		rule.Build("metalavaCurrentApiUpdate", "update current API")
807	}
808
809	if String(d.properties.Check_nullability_warnings) != "" {
810		if d.nullabilityWarningsFile == nil {
811			ctx.PropertyErrorf("check_nullability_warnings",
812				"Cannot specify check_nullability_warnings unless validating nullability")
813		}
814
815		checkNullabilityWarnings := android.PathForModuleSrc(ctx, String(d.properties.Check_nullability_warnings))
816
817		d.checkNullabilityWarningsTimestamp = android.PathForModuleOut(ctx, "metalava", "check_nullability_warnings.timestamp")
818
819		msg := fmt.Sprintf(`\n******************************\n`+
820			`The warnings encountered during nullability annotation validation did\n`+
821			`not match the checked in file of expected warnings. The diffs are shown\n`+
822			`above. You have two options:\n`+
823			`   1. Resolve the differences by editing the nullability annotations.\n`+
824			`   2. Update the file of expected warnings by running:\n`+
825			`         cp %s %s\n`+
826			`       and submitting the updated file as part of your change.`,
827			d.nullabilityWarningsFile, checkNullabilityWarnings)
828
829		rule := android.NewRuleBuilder(pctx, ctx)
830
831		rule.Command().
832			Text("(").
833			Text("diff").Input(checkNullabilityWarnings).Input(d.nullabilityWarningsFile).
834			Text("&&").
835			Text("touch").Output(d.checkNullabilityWarningsTimestamp).
836			Text(") || (").
837			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
838			Text("; exit 38").
839			Text(")")
840
841		rule.Build("nullabilityWarningsCheck", "nullability warnings check")
842	}
843}
844
845var _ android.ApiProvider = (*Droidstubs)(nil)
846
847type bazelJavaApiContributionAttributes struct {
848	Api         bazel.LabelAttribute
849	Api_surface *string
850}
851
852func (d *Droidstubs) ConvertWithApiBp2build(ctx android.TopDownMutatorContext) {
853	props := bazel.BazelTargetModuleProperties{
854		Rule_class:        "java_api_contribution",
855		Bzl_load_location: "//build/bazel/rules/apis:java_api_contribution.bzl",
856	}
857	apiFile := d.properties.Check_api.Current.Api_file
858	// Do not generate a target if check_api is not set
859	if apiFile == nil {
860		return
861	}
862	attrs := &bazelJavaApiContributionAttributes{
863		Api: *bazel.MakeLabelAttribute(
864			android.BazelLabelForModuleSrcSingle(ctx, proptools.String(apiFile)).Label,
865		),
866		Api_surface: proptools.StringPtr(bazelApiSurfaceName(d.Name())),
867	}
868	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
869		Name: android.ApiContributionTargetName(ctx.ModuleName()),
870	}, attrs)
871}
872
873func (d *Droidstubs) createApiContribution(ctx android.DefaultableHookContext) {
874	api_file := d.properties.Check_api.Current.Api_file
875	api_surface := d.properties.Api_surface
876
877	props := struct {
878		Name        *string
879		Api_surface *string
880		Api_file    *string
881		Visibility  []string
882	}{}
883
884	props.Name = proptools.StringPtr(d.Name() + ".api.contribution")
885	props.Api_surface = api_surface
886	props.Api_file = api_file
887	props.Visibility = []string{"//visibility:override", "//visibility:public"}
888
889	ctx.CreateModule(ApiContributionFactory, &props)
890}
891
892// TODO (b/262014796): Export the API contributions of CorePlatformApi
893// A map to populate the api surface of a droidstub from a substring appearing in its name
894// This map assumes that droidstubs (either checked-in or created by java_sdk_library)
895// use a strict naming convention
896var (
897	droidstubsModuleNamingToSdkKind = map[string]android.SdkKind{
898		//public is commented out since the core libraries use public in their java_sdk_library names
899		"intracore":     android.SdkIntraCore,
900		"intra.core":    android.SdkIntraCore,
901		"system_server": android.SdkSystemServer,
902		"system-server": android.SdkSystemServer,
903		"system":        android.SdkSystem,
904		"module_lib":    android.SdkModule,
905		"module-lib":    android.SdkModule,
906		"platform.api":  android.SdkCorePlatform,
907		"test":          android.SdkTest,
908		"toolchain":     android.SdkToolchain,
909	}
910)
911
912// A helper function that returns the api surface of the corresponding java_api_contribution Bazel target
913// The api_surface is populated using the naming convention of the droidstubs module.
914func bazelApiSurfaceName(name string) string {
915	// Sort the keys so that longer strings appear first
916	// Otherwise substrings like system will match both system and system_server
917	sortedKeys := make([]string, 0)
918	for key := range droidstubsModuleNamingToSdkKind {
919		sortedKeys = append(sortedKeys, key)
920	}
921	sort.Slice(sortedKeys, func(i, j int) bool {
922		return len(sortedKeys[i]) > len(sortedKeys[j])
923	})
924	for _, sortedKey := range sortedKeys {
925		if strings.Contains(name, sortedKey) {
926			sdkKind := droidstubsModuleNamingToSdkKind[sortedKey]
927			return sdkKind.String() + "api"
928		}
929	}
930	// Default is publicapi
931	return android.SdkPublic.String() + "api"
932}
933
934func StubsDefaultsFactory() android.Module {
935	module := &DocDefaults{}
936
937	module.AddProperties(
938		&JavadocProperties{},
939		&DroidstubsProperties{},
940	)
941
942	android.InitDefaultsModule(module)
943
944	return module
945}
946
947var _ android.PrebuiltInterface = (*PrebuiltStubsSources)(nil)
948
949type PrebuiltStubsSourcesProperties struct {
950	Srcs []string `android:"path"`
951}
952
953type PrebuiltStubsSources struct {
954	android.ModuleBase
955	android.DefaultableModuleBase
956	prebuilt android.Prebuilt
957
958	properties PrebuiltStubsSourcesProperties
959
960	stubsSrcJar android.Path
961}
962
963func (p *PrebuiltStubsSources) OutputFiles(tag string) (android.Paths, error) {
964	switch tag {
965	case "":
966		return android.Paths{p.stubsSrcJar}, nil
967	default:
968		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
969	}
970}
971
972func (d *PrebuiltStubsSources) StubsSrcJar() android.Path {
973	return d.stubsSrcJar
974}
975
976func (p *PrebuiltStubsSources) GenerateAndroidBuildActions(ctx android.ModuleContext) {
977	if len(p.properties.Srcs) != 1 {
978		ctx.PropertyErrorf("srcs", "must only specify one directory path or srcjar, contains %d paths", len(p.properties.Srcs))
979		return
980	}
981
982	src := p.properties.Srcs[0]
983	if filepath.Ext(src) == ".srcjar" {
984		// This is a srcjar. We can use it directly.
985		p.stubsSrcJar = android.PathForModuleSrc(ctx, src)
986	} else {
987		outPath := android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
988
989		// This is a directory. Glob the contents just in case the directory does not exist.
990		srcGlob := src + "/**/*"
991		srcPaths := android.PathsForModuleSrc(ctx, []string{srcGlob})
992
993		// Although PathForModuleSrc can return nil if either the path doesn't exist or
994		// the path components are invalid it won't in this case because no components
995		// are specified and the module directory must exist in order to get this far.
996		srcDir := android.PathForModuleSrc(ctx).(android.SourcePath).Join(ctx, src)
997
998		rule := android.NewRuleBuilder(pctx, ctx)
999		rule.Command().
1000			BuiltTool("soong_zip").
1001			Flag("-write_if_changed").
1002			Flag("-jar").
1003			FlagWithOutput("-o ", outPath).
1004			FlagWithArg("-C ", srcDir.String()).
1005			FlagWithRspFileInputList("-r ", outPath.ReplaceExtension(ctx, "rsp"), srcPaths)
1006		rule.Restat()
1007		rule.Build("zip src", "Create srcjar from prebuilt source")
1008		p.stubsSrcJar = outPath
1009	}
1010}
1011
1012func (p *PrebuiltStubsSources) Prebuilt() *android.Prebuilt {
1013	return &p.prebuilt
1014}
1015
1016func (p *PrebuiltStubsSources) Name() string {
1017	return p.prebuilt.Name(p.ModuleBase.Name())
1018}
1019
1020// prebuilt_stubs_sources imports a set of java source files as if they were
1021// generated by droidstubs.
1022//
1023// By default, a prebuilt_stubs_sources has a single variant that expects a
1024// set of `.java` files generated by droidstubs.
1025//
1026// Specifying `host_supported: true` will produce two variants, one for use as a dependency of device modules and one
1027// for host modules.
1028//
1029// Intended only for use by sdk snapshots.
1030func PrebuiltStubsSourcesFactory() android.Module {
1031	module := &PrebuiltStubsSources{}
1032
1033	module.AddProperties(&module.properties)
1034
1035	android.InitPrebuiltModule(module, &module.properties.Srcs)
1036	InitDroiddocModule(module, android.HostAndDeviceSupported)
1037	return module
1038}
1039