1// Copyright 2018 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	"strings"
21
22	"github.com/google/blueprint/proptools"
23
24	"android/soong/android"
25	"android/soong/java/config"
26)
27
28func init() {
29	RegisterDocsBuildComponents(android.InitRegistrationContext)
30}
31
32func RegisterDocsBuildComponents(ctx android.RegistrationContext) {
33	ctx.RegisterModuleType("doc_defaults", DocDefaultsFactory)
34
35	ctx.RegisterModuleType("droiddoc", DroiddocFactory)
36	ctx.RegisterModuleType("droiddoc_host", DroiddocHostFactory)
37	ctx.RegisterModuleType("droiddoc_exported_dir", ExportedDroiddocDirFactory)
38	ctx.RegisterModuleType("javadoc", JavadocFactory)
39	ctx.RegisterModuleType("javadoc_host", JavadocHostFactory)
40}
41
42type JavadocProperties struct {
43	// list of source files used to compile the Java module.  May be .java, .logtags, .proto,
44	// or .aidl files.
45	Srcs []string `android:"path,arch_variant"`
46
47	// list of source files that should not be used to build the Java module.
48	// This is most useful in the arch/multilib variants to remove non-common files
49	// filegroup or genrule can be included within this property.
50	Exclude_srcs []string `android:"path,arch_variant"`
51
52	// list of package names that should actually be used. If this property is left unspecified,
53	// all the sources from the srcs property is used.
54	Filter_packages []string
55
56	// list of java libraries that will be in the classpath.
57	Libs []string `android:"arch_variant"`
58
59	// If set to false, don't allow this module(-docs.zip) to be exported. Defaults to true.
60	Installable *bool
61
62	// if not blank, set to the version of the sdk to compile against.
63	// Defaults to compiling against the current platform.
64	Sdk_version *string `android:"arch_variant"`
65
66	// When targeting 1.9 and above, override the modules to use with --system,
67	// otherwise provides defaults libraries to add to the bootclasspath.
68	// Defaults to "none"
69	System_modules *string
70
71	Aidl struct {
72		// Top level directories to pass to aidl tool
73		Include_dirs []string
74
75		// Directories rooted at the Android.bp file to pass to aidl tool
76		Local_include_dirs []string
77	}
78
79	// If not blank, set the java version passed to javadoc as -source
80	Java_version *string
81
82	// local files that are used within user customized droiddoc options.
83	Arg_files []string `android:"path"`
84
85	// user customized droiddoc args. Deprecated, use flags instead.
86	// Available variables for substitution:
87	//
88	//  $(location <label>): the path to the arg_files with name <label>
89	//  $$: a literal $
90	Args *string
91
92	// user customized droiddoc args. Not compatible with property args.
93	// Available variables for substitution:
94	//
95	//  $(location <label>): the path to the arg_files with name <label>
96	//  $$: a literal $
97	Flags []string
98
99	// names of the output files used in args that will be generated
100	Out []string
101}
102
103type ApiToCheck struct {
104	// path to the API txt file that the new API extracted from source code is checked
105	// against. The path can be local to the module or from other module (via :module syntax).
106	Api_file *string `android:"path"`
107
108	// path to the API txt file that the new @removed API extractd from source code is
109	// checked against. The path can be local to the module or from other module (via
110	// :module syntax).
111	Removed_api_file *string `android:"path"`
112
113	// If not blank, path to the baseline txt file for approved API check violations.
114	Baseline_file *string `android:"path"`
115
116	// Arguments to the apicheck tool.
117	Args *string
118}
119
120type DroiddocProperties struct {
121	// directory relative to top of the source tree that contains doc templates files.
122	Custom_template *string
123
124	// directories under current module source which contains html/jd files.
125	Html_dirs []string
126
127	// set a value in the Clearsilver hdf namespace.
128	Hdf []string
129
130	// proofread file contains all of the text content of the javadocs concatenated into one file,
131	// suitable for spell-checking and other goodness.
132	Proofread_file *string
133
134	// a todo file lists the program elements that are missing documentation.
135	// At some point, this might be improved to show more warnings.
136	Todo_file *string `android:"path"`
137
138	// directory under current module source that provide additional resources (images).
139	Resourcesdir *string
140
141	// resources output directory under out/soong/.intermediates.
142	Resourcesoutdir *string
143
144	// index.html under current module will be copied to docs out dir, if not null.
145	Static_doc_index_redirect *string `android:"path"`
146
147	// source.properties under current module will be copied to docs out dir, if not null.
148	Static_doc_properties *string `android:"path"`
149
150	// a list of files under current module source dir which contains known tags in Java sources.
151	// filegroup or genrule can be included within this property.
152	Knowntags []string `android:"path"`
153
154	// if set to true, generate docs through Dokka instead of Doclava.
155	Dokka_enabled *bool
156
157	// Compat config XML. Generates compat change documentation if set.
158	Compat_config *string `android:"path"`
159}
160
161//
162// Common flags passed down to build rule
163//
164type droiddocBuilderFlags struct {
165	bootClasspathArgs  string
166	classpathArgs      string
167	sourcepathArgs     string
168	dokkaClasspathArgs string
169	aidlFlags          string
170	aidlDeps           android.Paths
171
172	doclavaStubsFlags string
173	doclavaDocsFlags  string
174	postDoclavaCmds   string
175}
176
177func InitDroiddocModule(module android.DefaultableModule, hod android.HostOrDeviceSupported) {
178	android.InitAndroidArchModule(module, hod, android.MultilibCommon)
179	android.InitDefaultableModule(module)
180}
181
182func apiCheckEnabled(ctx android.ModuleContext, apiToCheck ApiToCheck, apiVersionTag string) bool {
183	if ctx.Config().IsEnvTrue("WITHOUT_CHECK_API") {
184		return false
185	} else if String(apiToCheck.Api_file) != "" && String(apiToCheck.Removed_api_file) != "" {
186		return true
187	} else if String(apiToCheck.Api_file) != "" {
188		panic("for " + apiVersionTag + " removed_api_file has to be non-empty!")
189	} else if String(apiToCheck.Removed_api_file) != "" {
190		panic("for " + apiVersionTag + " api_file has to be non-empty!")
191	}
192
193	return false
194}
195
196//
197// Javadoc
198//
199type Javadoc struct {
200	android.ModuleBase
201	android.DefaultableModuleBase
202
203	properties JavadocProperties
204
205	srcJars     android.Paths
206	srcFiles    android.Paths
207	sourcepaths android.Paths
208	implicits   android.Paths
209
210	docZip      android.WritablePath
211	stubsSrcJar android.WritablePath
212}
213
214func (j *Javadoc) OutputFiles(tag string) (android.Paths, error) {
215	switch tag {
216	case "":
217		return android.Paths{j.stubsSrcJar}, nil
218	case ".docs.zip":
219		return android.Paths{j.docZip}, nil
220	default:
221		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
222	}
223}
224
225// javadoc converts .java source files to documentation using javadoc.
226func JavadocFactory() android.Module {
227	module := &Javadoc{}
228
229	module.AddProperties(&module.properties)
230
231	InitDroiddocModule(module, android.HostAndDeviceSupported)
232	return module
233}
234
235// javadoc_host converts .java source files to documentation using javadoc.
236func JavadocHostFactory() android.Module {
237	module := &Javadoc{}
238
239	module.AddProperties(&module.properties)
240
241	InitDroiddocModule(module, android.HostSupported)
242	return module
243}
244
245var _ android.OutputFileProducer = (*Javadoc)(nil)
246
247func (j *Javadoc) SdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
248	return android.SdkSpecFrom(ctx, String(j.properties.Sdk_version))
249}
250
251func (j *Javadoc) SystemModules() string {
252	return proptools.String(j.properties.System_modules)
253}
254
255func (j *Javadoc) MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
256	return j.SdkVersion(ctx)
257}
258
259func (j *Javadoc) TargetSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
260	return j.SdkVersion(ctx)
261}
262
263func (j *Javadoc) addDeps(ctx android.BottomUpMutatorContext) {
264	if ctx.Device() {
265		sdkDep := decodeSdkDep(ctx, android.SdkContext(j))
266		if sdkDep.useModule {
267			ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...)
268			ctx.AddVariationDependencies(nil, systemModulesTag, sdkDep.systemModules)
269			ctx.AddVariationDependencies(nil, java9LibTag, sdkDep.java9Classpath...)
270			ctx.AddVariationDependencies(nil, libTag, sdkDep.classpath...)
271		}
272	}
273
274	ctx.AddVariationDependencies(nil, libTag, j.properties.Libs...)
275}
276
277func (j *Javadoc) collectAidlFlags(ctx android.ModuleContext, deps deps) droiddocBuilderFlags {
278	var flags droiddocBuilderFlags
279
280	flags.aidlFlags, flags.aidlDeps = j.aidlFlags(ctx, deps.aidlPreprocess, deps.aidlIncludeDirs)
281
282	return flags
283}
284
285func (j *Javadoc) aidlFlags(ctx android.ModuleContext, aidlPreprocess android.OptionalPath,
286	aidlIncludeDirs android.Paths) (string, android.Paths) {
287
288	aidlIncludes := android.PathsForModuleSrc(ctx, j.properties.Aidl.Local_include_dirs)
289	aidlIncludes = append(aidlIncludes, android.PathsForSource(ctx, j.properties.Aidl.Include_dirs)...)
290
291	var flags []string
292	var deps android.Paths
293
294	if aidlPreprocess.Valid() {
295		flags = append(flags, "-p"+aidlPreprocess.String())
296		deps = append(deps, aidlPreprocess.Path())
297	} else {
298		flags = append(flags, android.JoinWithPrefix(aidlIncludeDirs.Strings(), "-I"))
299	}
300
301	flags = append(flags, android.JoinWithPrefix(aidlIncludes.Strings(), "-I"))
302	flags = append(flags, "-I"+android.PathForModuleSrc(ctx).String())
303	if src := android.ExistentPathForSource(ctx, ctx.ModuleDir(), "src"); src.Valid() {
304		flags = append(flags, "-I"+src.String())
305	}
306
307	return strings.Join(flags, " "), deps
308}
309
310// TODO: remove the duplication between this and the one in gen.go
311func (j *Javadoc) genSources(ctx android.ModuleContext, srcFiles android.Paths,
312	flags droiddocBuilderFlags) android.Paths {
313
314	outSrcFiles := make(android.Paths, 0, len(srcFiles))
315	var aidlSrcs android.Paths
316
317	aidlIncludeFlags := genAidlIncludeFlags(srcFiles)
318
319	for _, srcFile := range srcFiles {
320		switch srcFile.Ext() {
321		case ".aidl":
322			aidlSrcs = append(aidlSrcs, srcFile)
323		case ".logtags":
324			javaFile := genLogtags(ctx, srcFile)
325			outSrcFiles = append(outSrcFiles, javaFile)
326		default:
327			outSrcFiles = append(outSrcFiles, srcFile)
328		}
329	}
330
331	// Process all aidl files together to support sharding them into one or more rules that produce srcjars.
332	if len(aidlSrcs) > 0 {
333		srcJarFiles := genAidl(ctx, aidlSrcs, flags.aidlFlags+aidlIncludeFlags, flags.aidlDeps)
334		outSrcFiles = append(outSrcFiles, srcJarFiles...)
335	}
336
337	return outSrcFiles
338}
339
340func (j *Javadoc) collectDeps(ctx android.ModuleContext) deps {
341	var deps deps
342
343	sdkDep := decodeSdkDep(ctx, android.SdkContext(j))
344	if sdkDep.invalidVersion {
345		ctx.AddMissingDependencies(sdkDep.bootclasspath)
346		ctx.AddMissingDependencies(sdkDep.java9Classpath)
347	} else if sdkDep.useFiles {
348		deps.bootClasspath = append(deps.bootClasspath, sdkDep.jars...)
349		deps.aidlPreprocess = sdkDep.aidl
350	} else {
351		deps.aidlPreprocess = sdkDep.aidl
352	}
353
354	ctx.VisitDirectDeps(func(module android.Module) {
355		otherName := ctx.OtherModuleName(module)
356		tag := ctx.OtherModuleDependencyTag(module)
357
358		switch tag {
359		case bootClasspathTag:
360			if ctx.OtherModuleHasProvider(module, JavaInfoProvider) {
361				dep := ctx.OtherModuleProvider(module, JavaInfoProvider).(JavaInfo)
362				deps.bootClasspath = append(deps.bootClasspath, dep.ImplementationJars...)
363			} else if sm, ok := module.(SystemModulesProvider); ok {
364				// A system modules dependency has been added to the bootclasspath
365				// so add its libs to the bootclasspath.
366				deps.bootClasspath = append(deps.bootClasspath, sm.HeaderJars()...)
367			} else {
368				panic(fmt.Errorf("unknown dependency %q for %q", otherName, ctx.ModuleName()))
369			}
370		case libTag:
371			if dep, ok := module.(SdkLibraryDependency); ok {
372				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.SdkVersion(ctx))...)
373			} else if ctx.OtherModuleHasProvider(module, JavaInfoProvider) {
374				dep := ctx.OtherModuleProvider(module, JavaInfoProvider).(JavaInfo)
375				deps.classpath = append(deps.classpath, dep.HeaderJars...)
376				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs...)
377			} else if dep, ok := module.(android.SourceFileProducer); ok {
378				checkProducesJars(ctx, dep)
379				deps.classpath = append(deps.classpath, dep.Srcs()...)
380			} else {
381				ctx.ModuleErrorf("depends on non-java module %q", otherName)
382			}
383		case java9LibTag:
384			if ctx.OtherModuleHasProvider(module, JavaInfoProvider) {
385				dep := ctx.OtherModuleProvider(module, JavaInfoProvider).(JavaInfo)
386				deps.java9Classpath = append(deps.java9Classpath, dep.HeaderJars...)
387			} else {
388				ctx.ModuleErrorf("depends on non-java module %q", otherName)
389			}
390		case systemModulesTag:
391			if deps.systemModules != nil {
392				panic("Found two system module dependencies")
393			}
394			sm := module.(SystemModulesProvider)
395			outputDir, outputDeps := sm.OutputDirAndDeps()
396			deps.systemModules = &systemModules{outputDir, outputDeps}
397		}
398	})
399	// do not pass exclude_srcs directly when expanding srcFiles since exclude_srcs
400	// may contain filegroup or genrule.
401	srcFiles := android.PathsForModuleSrcExcludes(ctx, j.properties.Srcs, j.properties.Exclude_srcs)
402	j.implicits = append(j.implicits, srcFiles...)
403
404	filterByPackage := func(srcs []android.Path, filterPackages []string) []android.Path {
405		if filterPackages == nil {
406			return srcs
407		}
408		filtered := []android.Path{}
409		for _, src := range srcs {
410			if src.Ext() != ".java" {
411				// Don't filter-out non-Java (=generated sources) by package names. This is not ideal,
412				// but otherwise metalava emits stub sources having references to the generated AIDL classes
413				// in filtered-out pacages (e.g. com.android.internal.*).
414				// TODO(b/141149570) We need to fix this by introducing default private constructors or
415				// fixing metalava to not emit constructors having references to unknown classes.
416				filtered = append(filtered, src)
417				continue
418			}
419			packageName := strings.ReplaceAll(filepath.Dir(src.Rel()), "/", ".")
420			if android.HasAnyPrefix(packageName, filterPackages) {
421				filtered = append(filtered, src)
422			}
423		}
424		return filtered
425	}
426	srcFiles = filterByPackage(srcFiles, j.properties.Filter_packages)
427
428	aidlFlags := j.collectAidlFlags(ctx, deps)
429	srcFiles = j.genSources(ctx, srcFiles, aidlFlags)
430
431	// srcs may depend on some genrule output.
432	j.srcJars = srcFiles.FilterByExt(".srcjar")
433	j.srcJars = append(j.srcJars, deps.srcJars...)
434
435	j.srcFiles = srcFiles.FilterOutByExt(".srcjar")
436	j.srcFiles = append(j.srcFiles, deps.srcs...)
437
438	if len(j.srcFiles) > 0 {
439		j.sourcepaths = android.PathsForModuleSrc(ctx, []string{"."})
440	}
441
442	return deps
443}
444
445func (j *Javadoc) expandArgs(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
446	var argFiles android.Paths
447	argFilesMap := map[string]string{}
448	argFileLabels := []string{}
449
450	for _, label := range j.properties.Arg_files {
451		var paths = android.PathsForModuleSrc(ctx, []string{label})
452		if _, exists := argFilesMap[label]; !exists {
453			argFilesMap[label] = strings.Join(cmd.PathsForInputs(paths), " ")
454			argFileLabels = append(argFileLabels, label)
455			argFiles = append(argFiles, paths...)
456		} else {
457			ctx.ModuleErrorf("multiple arg_files for %q, %q and %q",
458				label, argFilesMap[label], paths)
459		}
460	}
461
462	var argsPropertyName string
463	flags := make([]string, 0)
464	if j.properties.Args != nil && j.properties.Flags != nil {
465		ctx.PropertyErrorf("args", "flags is set. Cannot set args")
466	} else if args := proptools.String(j.properties.Args); args != "" {
467		flags = append(flags, args)
468		argsPropertyName = "args"
469	} else {
470		flags = append(flags, j.properties.Flags...)
471		argsPropertyName = "flags"
472	}
473
474	for _, flag := range flags {
475		expanded, err := android.Expand(flag, func(name string) (string, error) {
476			if strings.HasPrefix(name, "location ") {
477				label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
478				if paths, ok := argFilesMap[label]; ok {
479					return paths, nil
480				} else {
481					return "", fmt.Errorf("unknown location label %q, expecting one of %q",
482						label, strings.Join(argFileLabels, ", "))
483				}
484			} else if name == "genDir" {
485				return android.PathForModuleGen(ctx).String(), nil
486			}
487			return "", fmt.Errorf("unknown variable '$(%s)'", name)
488		})
489
490		if err != nil {
491			ctx.PropertyErrorf(argsPropertyName, "%s", err.Error())
492		}
493		cmd.Flag(expanded)
494	}
495
496	cmd.Implicits(argFiles)
497}
498
499func (j *Javadoc) DepsMutator(ctx android.BottomUpMutatorContext) {
500	j.addDeps(ctx)
501}
502
503func (j *Javadoc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
504	deps := j.collectDeps(ctx)
505
506	j.docZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"docs.zip")
507
508	outDir := android.PathForModuleOut(ctx, "out")
509	srcJarDir := android.PathForModuleOut(ctx, "srcjars")
510
511	j.stubsSrcJar = nil
512
513	rule := android.NewRuleBuilder(pctx, ctx)
514
515	rule.Command().Text("rm -rf").Text(outDir.String())
516	rule.Command().Text("mkdir -p").Text(outDir.String())
517
518	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, j.srcJars)
519
520	javaVersion := getJavaVersion(ctx, String(j.properties.Java_version), android.SdkContext(j))
521
522	cmd := javadocSystemModulesCmd(ctx, rule, j.srcFiles, outDir, srcJarDir, srcJarList,
523		deps.systemModules, deps.classpath, j.sourcepaths)
524
525	cmd.FlagWithArg("-source ", javaVersion.String()).
526		Flag("-J-Xmx1024m").
527		Flag("-XDignore.symbol.file").
528		Flag("-Xdoclint:none")
529
530	j.expandArgs(ctx, cmd)
531
532	rule.Command().
533		BuiltTool("soong_zip").
534		Flag("-write_if_changed").
535		Flag("-d").
536		FlagWithOutput("-o ", j.docZip).
537		FlagWithArg("-C ", outDir.String()).
538		FlagWithArg("-D ", outDir.String())
539
540	rule.Restat()
541
542	zipSyncCleanupCmd(rule, srcJarDir)
543
544	rule.Build("javadoc", "javadoc")
545}
546
547//
548// Droiddoc
549//
550type Droiddoc struct {
551	Javadoc
552
553	properties DroiddocProperties
554}
555
556// droiddoc converts .java source files to documentation using doclava or dokka.
557func DroiddocFactory() android.Module {
558	module := &Droiddoc{}
559
560	module.AddProperties(&module.properties,
561		&module.Javadoc.properties)
562
563	InitDroiddocModule(module, android.HostAndDeviceSupported)
564	return module
565}
566
567// droiddoc_host converts .java source files to documentation using doclava or dokka.
568func DroiddocHostFactory() android.Module {
569	module := &Droiddoc{}
570
571	module.AddProperties(&module.properties,
572		&module.Javadoc.properties)
573
574	InitDroiddocModule(module, android.HostSupported)
575	return module
576}
577
578func (d *Droiddoc) OutputFiles(tag string) (android.Paths, error) {
579	switch tag {
580	case "", ".docs.zip":
581		return android.Paths{d.Javadoc.docZip}, nil
582	default:
583		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
584	}
585}
586
587func (d *Droiddoc) DepsMutator(ctx android.BottomUpMutatorContext) {
588	d.Javadoc.addDeps(ctx)
589
590	if String(d.properties.Custom_template) != "" {
591		ctx.AddDependency(ctx.Module(), droiddocTemplateTag, String(d.properties.Custom_template))
592	}
593}
594
595func (d *Droiddoc) doclavaDocsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, docletPath classpath) {
596	buildNumberFile := ctx.Config().BuildNumberFile(ctx)
597	// Droiddoc always gets "-source 1.8" because it doesn't support 1.9 sources.  For modules with 1.9
598	// sources, droiddoc will get sources produced by metalava which will have already stripped out the
599	// 1.9 language features.
600	cmd.FlagWithArg("-source ", "1.8").
601		Flag("-J-Xmx1600m").
602		Flag("-J-XX:-OmitStackTraceInFastThrow").
603		Flag("-XDignore.symbol.file").
604		FlagWithArg("-doclet ", "com.google.doclava.Doclava").
605		FlagWithInputList("-docletpath ", docletPath.Paths(), ":").
606		FlagWithArg("-hdf page.build ", ctx.Config().BuildId()+"-$(cat "+buildNumberFile.String()+")").OrderOnly(buildNumberFile).
607		FlagWithArg("-hdf page.now ", `"$(date -d @$(cat `+ctx.Config().Getenv("BUILD_DATETIME_FILE")+`) "+%d %b %Y %k:%M")" `)
608
609	if String(d.properties.Custom_template) == "" {
610		// TODO: This is almost always droiddoc-templates-sdk
611		ctx.PropertyErrorf("custom_template", "must specify a template")
612	}
613
614	ctx.VisitDirectDepsWithTag(droiddocTemplateTag, func(m android.Module) {
615		if t, ok := m.(*ExportedDroiddocDir); ok {
616			cmd.FlagWithArg("-templatedir ", t.dir.String()).Implicits(t.deps)
617		} else {
618			ctx.PropertyErrorf("custom_template", "module %q is not a droiddoc_exported_dir", ctx.OtherModuleName(m))
619		}
620	})
621
622	if len(d.properties.Html_dirs) > 0 {
623		htmlDir := android.PathForModuleSrc(ctx, d.properties.Html_dirs[0])
624		cmd.FlagWithArg("-htmldir ", htmlDir.String()).
625			Implicits(android.PathsForModuleSrc(ctx, []string{filepath.Join(d.properties.Html_dirs[0], "**/*")}))
626	}
627
628	if len(d.properties.Html_dirs) > 1 {
629		htmlDir2 := android.PathForModuleSrc(ctx, d.properties.Html_dirs[1])
630		cmd.FlagWithArg("-htmldir2 ", htmlDir2.String()).
631			Implicits(android.PathsForModuleSrc(ctx, []string{filepath.Join(d.properties.Html_dirs[1], "**/*")}))
632	}
633
634	if len(d.properties.Html_dirs) > 2 {
635		ctx.PropertyErrorf("html_dirs", "Droiddoc only supports up to 2 html dirs")
636	}
637
638	knownTags := android.PathsForModuleSrc(ctx, d.properties.Knowntags)
639	cmd.FlagForEachInput("-knowntags ", knownTags)
640
641	cmd.FlagForEachArg("-hdf ", d.properties.Hdf)
642
643	if String(d.properties.Proofread_file) != "" {
644		proofreadFile := android.PathForModuleOut(ctx, String(d.properties.Proofread_file))
645		cmd.FlagWithOutput("-proofread ", proofreadFile)
646	}
647
648	if String(d.properties.Todo_file) != "" {
649		// tricky part:
650		// we should not compute full path for todo_file through PathForModuleOut().
651		// the non-standard doclet will get the full path relative to "-o".
652		cmd.FlagWithArg("-todo ", String(d.properties.Todo_file)).
653			ImplicitOutput(android.PathForModuleOut(ctx, String(d.properties.Todo_file)))
654	}
655
656	if String(d.properties.Resourcesdir) != "" {
657		// TODO: should we add files under resourcesDir to the implicits? It seems that
658		// resourcesDir is one sub dir of htmlDir
659		resourcesDir := android.PathForModuleSrc(ctx, String(d.properties.Resourcesdir))
660		cmd.FlagWithArg("-resourcesdir ", resourcesDir.String())
661	}
662
663	if String(d.properties.Resourcesoutdir) != "" {
664		// TODO: it seems -resourceoutdir reference/android/images/ didn't get generated anywhere.
665		cmd.FlagWithArg("-resourcesoutdir ", String(d.properties.Resourcesoutdir))
666	}
667}
668
669func (d *Droiddoc) postDoclavaCmds(ctx android.ModuleContext, rule *android.RuleBuilder) {
670	if String(d.properties.Static_doc_index_redirect) != "" {
671		staticDocIndexRedirect := android.PathForModuleSrc(ctx, String(d.properties.Static_doc_index_redirect))
672		rule.Command().Text("cp").
673			Input(staticDocIndexRedirect).
674			Output(android.PathForModuleOut(ctx, "out", "index.html"))
675	}
676
677	if String(d.properties.Static_doc_properties) != "" {
678		staticDocProperties := android.PathForModuleSrc(ctx, String(d.properties.Static_doc_properties))
679		rule.Command().Text("cp").
680			Input(staticDocProperties).
681			Output(android.PathForModuleOut(ctx, "out", "source.properties"))
682	}
683}
684
685func javadocCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths,
686	outDir, srcJarDir, srcJarList android.Path, sourcepaths android.Paths) *android.RuleBuilderCommand {
687
688	cmd := rule.Command().
689		BuiltTool("soong_javac_wrapper").Tool(config.JavadocCmd(ctx)).
690		Flag(config.JavacVmFlags).
691		FlagWithArg("-encoding ", "UTF-8").
692		FlagWithRspFileInputList("@", android.PathForModuleOut(ctx, "javadoc.rsp"), srcs).
693		FlagWithInput("@", srcJarList)
694
695	// TODO(ccross): Remove this if- statement once we finish migration for all Doclava
696	// based stubs generation.
697	// In the future, all the docs generation depends on Metalava stubs (droidstubs) srcjar
698	// dir. We need add the srcjar dir to -sourcepath arg, so that Javadoc can figure out
699	// the correct package name base path.
700	if len(sourcepaths) > 0 {
701		cmd.FlagWithList("-sourcepath ", sourcepaths.Strings(), ":")
702	} else {
703		cmd.FlagWithArg("-sourcepath ", srcJarDir.String())
704	}
705
706	cmd.FlagWithArg("-d ", outDir.String()).
707		Flag("-quiet")
708
709	return cmd
710}
711
712func javadocSystemModulesCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths,
713	outDir, srcJarDir, srcJarList android.Path, systemModules *systemModules,
714	classpath classpath, sourcepaths android.Paths) *android.RuleBuilderCommand {
715
716	cmd := javadocCmd(ctx, rule, srcs, outDir, srcJarDir, srcJarList, sourcepaths)
717
718	flag, deps := systemModules.FormJavaSystemModulesPath(ctx.Device())
719	cmd.Flag(flag).Implicits(deps)
720
721	cmd.FlagWithArg("--patch-module ", "java.base=.")
722
723	if len(classpath) > 0 {
724		cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":")
725	}
726
727	return cmd
728}
729
730func javadocBootclasspathCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths,
731	outDir, srcJarDir, srcJarList android.Path, bootclasspath, classpath classpath,
732	sourcepaths android.Paths) *android.RuleBuilderCommand {
733
734	cmd := javadocCmd(ctx, rule, srcs, outDir, srcJarDir, srcJarList, sourcepaths)
735
736	if len(bootclasspath) == 0 && ctx.Device() {
737		// explicitly specify -bootclasspath "" if the bootclasspath is empty to
738		// ensure java does not fall back to the default bootclasspath.
739		cmd.FlagWithArg("-bootclasspath ", `""`)
740	} else if len(bootclasspath) > 0 {
741		cmd.FlagWithInputList("-bootclasspath ", bootclasspath.Paths(), ":")
742	}
743
744	if len(classpath) > 0 {
745		cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":")
746	}
747
748	return cmd
749}
750
751func dokkaCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
752	outDir, srcJarDir android.Path, bootclasspath, classpath classpath) *android.RuleBuilderCommand {
753
754	// Dokka doesn't support bootClasspath, so combine these two classpath vars for Dokka.
755	dokkaClasspath := append(bootclasspath.Paths(), classpath.Paths()...)
756
757	return rule.Command().
758		BuiltTool("dokka").
759		Flag(config.JavacVmFlags).
760		Flag(srcJarDir.String()).
761		FlagWithInputList("-classpath ", dokkaClasspath, ":").
762		FlagWithArg("-format ", "dac").
763		FlagWithArg("-dacRoot ", "/reference/kotlin").
764		FlagWithArg("-output ", outDir.String())
765}
766
767func (d *Droiddoc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
768	deps := d.Javadoc.collectDeps(ctx)
769
770	d.Javadoc.docZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"docs.zip")
771
772	jsilver := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "jsilver.jar")
773	doclava := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "doclava.jar")
774
775	outDir := android.PathForModuleOut(ctx, "out")
776	srcJarDir := android.PathForModuleOut(ctx, "srcjars")
777
778	rule := android.NewRuleBuilder(pctx, ctx)
779
780	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars)
781
782	var cmd *android.RuleBuilderCommand
783	if Bool(d.properties.Dokka_enabled) {
784		cmd = dokkaCmd(ctx, rule, outDir, srcJarDir, deps.bootClasspath, deps.classpath)
785	} else {
786		cmd = javadocBootclasspathCmd(ctx, rule, d.Javadoc.srcFiles, outDir, srcJarDir, srcJarList,
787			deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths)
788	}
789
790	d.expandArgs(ctx, cmd)
791
792	if d.properties.Compat_config != nil {
793		compatConfig := android.PathForModuleSrc(ctx, String(d.properties.Compat_config))
794		cmd.FlagWithInput("-compatconfig ", compatConfig)
795	}
796
797	var desc string
798	if Bool(d.properties.Dokka_enabled) {
799		desc = "dokka"
800	} else {
801		d.doclavaDocsFlags(ctx, cmd, classpath{jsilver, doclava})
802
803		for _, o := range d.Javadoc.properties.Out {
804			cmd.ImplicitOutput(android.PathForModuleGen(ctx, o))
805		}
806
807		d.postDoclavaCmds(ctx, rule)
808		desc = "doclava"
809	}
810
811	rule.Command().
812		BuiltTool("soong_zip").
813		Flag("-write_if_changed").
814		Flag("-d").
815		FlagWithOutput("-o ", d.docZip).
816		FlagWithArg("-C ", outDir.String()).
817		FlagWithArg("-D ", outDir.String())
818
819	rule.Restat()
820
821	zipSyncCleanupCmd(rule, srcJarDir)
822
823	rule.Build("javadoc", desc)
824}
825
826//
827// Exported Droiddoc Directory
828//
829var droiddocTemplateTag = dependencyTag{name: "droiddoc-template"}
830
831type ExportedDroiddocDirProperties struct {
832	// path to the directory containing Droiddoc related files.
833	Path *string
834}
835
836type ExportedDroiddocDir struct {
837	android.ModuleBase
838
839	properties ExportedDroiddocDirProperties
840
841	deps android.Paths
842	dir  android.Path
843}
844
845// droiddoc_exported_dir exports a directory of html templates or nullability annotations for use by doclava.
846func ExportedDroiddocDirFactory() android.Module {
847	module := &ExportedDroiddocDir{}
848	module.AddProperties(&module.properties)
849	android.InitAndroidModule(module)
850	return module
851}
852
853func (d *ExportedDroiddocDir) DepsMutator(android.BottomUpMutatorContext) {}
854
855func (d *ExportedDroiddocDir) GenerateAndroidBuildActions(ctx android.ModuleContext) {
856	path := String(d.properties.Path)
857	d.dir = android.PathForModuleSrc(ctx, path)
858	d.deps = android.PathsForModuleSrc(ctx, []string{filepath.Join(path, "**/*")})
859}
860
861//
862// Defaults
863//
864type DocDefaults struct {
865	android.ModuleBase
866	android.DefaultsModuleBase
867}
868
869func DocDefaultsFactory() android.Module {
870	module := &DocDefaults{}
871
872	module.AddProperties(
873		&JavadocProperties{},
874		&DroiddocProperties{},
875	)
876
877	android.InitDefaultsModule(module)
878
879	return module
880}
881
882func zipSyncCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
883	srcJarDir android.ModuleOutPath, srcJars android.Paths) android.OutputPath {
884
885	cmd := rule.Command()
886	cmd.Text("rm -rf").Text(cmd.PathForOutput(srcJarDir))
887	cmd = rule.Command()
888	cmd.Text("mkdir -p").Text(cmd.PathForOutput(srcJarDir))
889	srcJarList := srcJarDir.Join(ctx, "list")
890
891	rule.Temporary(srcJarList)
892
893	cmd = rule.Command()
894	cmd.BuiltTool("zipsync").
895		FlagWithArg("-d ", cmd.PathForOutput(srcJarDir)).
896		FlagWithOutput("-l ", srcJarList).
897		FlagWithArg("-f ", `"*.java"`).
898		Inputs(srcJars)
899
900	return srcJarList
901}
902
903func zipSyncCleanupCmd(rule *android.RuleBuilder, srcJarDir android.ModuleOutPath) {
904	rule.Command().Text("rm -rf").Text(srcJarDir.String())
905}
906