1// Copyright 2015 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
17// This file generates the final rules for compiling all Java.  All properties related to
18// compiling should have been translated into javaBuilderFlags or another argument to the Transform*
19// functions.
20
21import (
22	"path/filepath"
23	"strconv"
24	"strings"
25
26	"github.com/google/blueprint"
27	"github.com/google/blueprint/proptools"
28
29	"android/soong/android"
30	"android/soong/remoteexec"
31)
32
33var (
34	pctx = android.NewPackageContext("android/soong/java")
35
36	// Compiling java is not conducive to proper dependency tracking.  The path-matches-class-name
37	// requirement leads to unpredictable generated source file names, and a single .java file
38	// will get compiled into multiple .class files if it contains inner classes.  To work around
39	// this, all java rules write into separate directories and then are combined into a .jar file
40	// (if the rule produces .class files) or a .srcjar file (if the rule produces .java files).
41	// .srcjar files are unzipped into a temporary directory when compiled with javac.
42	// TODO(b/143658984): goma can't handle the --system argument to javac.
43	javac, javacRE = remoteexec.MultiCommandStaticRules(pctx, "javac",
44		blueprint.RuleParams{
45			Command: `rm -rf "$outDir" "$annoDir" "$srcJarDir" && mkdir -p "$outDir" "$annoDir" "$srcJarDir" && ` +
46				`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
47				`(if [ -s $srcJarDir/list ] || [ -s $out.rsp ] ; then ` +
48				`${config.SoongJavacWrapper} $javaTemplate${config.JavacCmd} ` +
49				`${config.JavacHeapFlags} ${config.JavacVmFlags} ${config.CommonJdkFlags} ` +
50				`$processorpath $processor $javacFlags $bootClasspath $classpath ` +
51				`-source $javaVersion -target $javaVersion ` +
52				`-d $outDir -s $annoDir @$out.rsp @$srcJarDir/list ; fi ) && ` +
53				`$zipTemplate${config.SoongZipCmd} -jar -o $out -C $outDir -D $outDir && ` +
54				`rm -rf "$srcJarDir"`,
55			CommandDeps: []string{
56				"${config.JavacCmd}",
57				"${config.SoongZipCmd}",
58				"${config.ZipSyncCmd}",
59			},
60			CommandOrderOnly: []string{"${config.SoongJavacWrapper}"},
61			Rspfile:          "$out.rsp",
62			RspfileContent:   "$in",
63		}, map[string]*remoteexec.REParams{
64			"$javaTemplate": &remoteexec.REParams{
65				Labels:       map[string]string{"type": "compile", "lang": "java", "compiler": "javac"},
66				ExecStrategy: "${config.REJavacExecStrategy}",
67				Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
68			},
69			"$zipTemplate": &remoteexec.REParams{
70				Labels:       map[string]string{"type": "tool", "name": "soong_zip"},
71				Inputs:       []string{"${config.SoongZipCmd}", "$outDir"},
72				OutputFiles:  []string{"$out"},
73				ExecStrategy: "${config.REJavacExecStrategy}",
74				Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
75			},
76		}, []string{"javacFlags", "bootClasspath", "classpath", "processorpath", "processor", "srcJars", "srcJarDir",
77			"outDir", "annoDir", "javaVersion"}, nil)
78
79	_ = pctx.VariableFunc("kytheCorpus",
80		func(ctx android.PackageVarContext) string { return ctx.Config().XrefCorpusName() })
81	_ = pctx.VariableFunc("kytheCuEncoding",
82		func(ctx android.PackageVarContext) string { return ctx.Config().XrefCuEncoding() })
83	_ = pctx.SourcePathVariable("kytheVnames", "build/soong/vnames.json")
84	// Run it with -add-opens=java.base/java.nio=ALL-UNNAMED to avoid JDK9's warning about
85	// "Illegal reflective access by com.google.protobuf.Utf8$UnsafeProcessor ...
86	// to field java.nio.Buffer.address"
87	kytheExtract = pctx.AndroidStaticRule("kythe",
88		blueprint.RuleParams{
89			Command: `${config.ZipSyncCmd} -d $srcJarDir ` +
90				`-l $srcJarDir/list -f "*.java" $srcJars && ` +
91				`( [ ! -s $srcJarDir/list -a ! -s $out.rsp ] || ` +
92				`KYTHE_ROOT_DIRECTORY=. KYTHE_OUTPUT_FILE=$out ` +
93				`KYTHE_CORPUS=${kytheCorpus} ` +
94				`KYTHE_VNAMES=${kytheVnames} ` +
95				`KYTHE_KZIP_ENCODING=${kytheCuEncoding} ` +
96				`${config.SoongJavacWrapper} ${config.JavaCmd} ` +
97				`--add-opens=java.base/java.nio=ALL-UNNAMED ` +
98				`-jar ${config.JavaKytheExtractorJar} ` +
99				`${config.JavacHeapFlags} ${config.CommonJdkFlags} ` +
100				`$processorpath $processor $javacFlags $bootClasspath $classpath ` +
101				`-source $javaVersion -target $javaVersion ` +
102				`-d $outDir -s $annoDir @$out.rsp @$srcJarDir/list)`,
103			CommandDeps: []string{
104				"${config.JavaCmd}",
105				"${config.JavaKytheExtractorJar}",
106				"${kytheVnames}",
107				"${config.ZipSyncCmd}",
108			},
109			CommandOrderOnly: []string{"${config.SoongJavacWrapper}"},
110			Rspfile:          "$out.rsp",
111			RspfileContent:   "$in",
112		},
113		"javacFlags", "bootClasspath", "classpath", "processorpath", "processor", "srcJars", "srcJarDir",
114		"outDir", "annoDir", "javaVersion")
115
116	extractMatchingApks = pctx.StaticRule(
117		"extractMatchingApks",
118		blueprint.RuleParams{
119			Command: `rm -rf "$out" && ` +
120				`${config.ExtractApksCmd} -o "${out}" -allow-prereleased=${allow-prereleased} ` +
121				`-sdk-version=${sdk-version} -abis=${abis} ` +
122				`--screen-densities=${screen-densities} --stem=${stem} ` +
123				`-apkcerts=${apkcerts} -partition=${partition} ` +
124				`${in}`,
125			CommandDeps: []string{"${config.ExtractApksCmd}"},
126		},
127		"abis", "allow-prereleased", "screen-densities", "sdk-version", "stem", "apkcerts", "partition")
128
129	turbine, turbineRE = remoteexec.StaticRules(pctx, "turbine",
130		blueprint.RuleParams{
131			Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
132				`$reTemplate${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.TurbineJar} --output $out.tmp ` +
133				`--temp_dir "$outDir" --sources @$out.rsp  --source_jars $srcJars ` +
134				`--javacopts ${config.CommonJdkFlags} ` +
135				`$javacFlags -source $javaVersion -target $javaVersion -- $bootClasspath $classpath && ` +
136				`${config.Ziptime} $out.tmp && ` +
137				`(if cmp -s $out.tmp $out ; then rm $out.tmp ; else mv $out.tmp $out ; fi )`,
138			CommandDeps: []string{
139				"${config.TurbineJar}",
140				"${config.JavaCmd}",
141				"${config.Ziptime}",
142			},
143			Rspfile:        "$out.rsp",
144			RspfileContent: "$in",
145			Restat:         true,
146		},
147		&remoteexec.REParams{Labels: map[string]string{"type": "tool", "name": "turbine"},
148			ExecStrategy:      "${config.RETurbineExecStrategy}",
149			Inputs:            []string{"${config.TurbineJar}", "${out}.rsp", "$implicits"},
150			RSPFile:           "${out}.rsp",
151			OutputFiles:       []string{"$out.tmp"},
152			OutputDirectories: []string{"$outDir"},
153			ToolchainInputs:   []string{"${config.JavaCmd}"},
154			Platform:          map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
155		}, []string{"javacFlags", "bootClasspath", "classpath", "srcJars", "outDir", "javaVersion"}, []string{"implicits"})
156
157	jar, jarRE = remoteexec.StaticRules(pctx, "jar",
158		blueprint.RuleParams{
159			Command:        `$reTemplate${config.SoongZipCmd} -jar -o $out @$out.rsp`,
160			CommandDeps:    []string{"${config.SoongZipCmd}"},
161			Rspfile:        "$out.rsp",
162			RspfileContent: "$jarArgs",
163		},
164		&remoteexec.REParams{
165			ExecStrategy: "${config.REJarExecStrategy}",
166			Inputs:       []string{"${config.SoongZipCmd}", "${out}.rsp"},
167			RSPFile:      "${out}.rsp",
168			OutputFiles:  []string{"$out"},
169			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
170		}, []string{"jarArgs"}, nil)
171
172	zip, zipRE = remoteexec.StaticRules(pctx, "zip",
173		blueprint.RuleParams{
174			Command:        `${config.SoongZipCmd} -o $out @$out.rsp`,
175			CommandDeps:    []string{"${config.SoongZipCmd}"},
176			Rspfile:        "$out.rsp",
177			RspfileContent: "$jarArgs",
178		},
179		&remoteexec.REParams{
180			ExecStrategy: "${config.REZipExecStrategy}",
181			Inputs:       []string{"${config.SoongZipCmd}", "${out}.rsp", "$implicits"},
182			RSPFile:      "${out}.rsp",
183			OutputFiles:  []string{"$out"},
184			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
185		}, []string{"jarArgs"}, []string{"implicits"})
186
187	combineJar = pctx.AndroidStaticRule("combineJar",
188		blueprint.RuleParams{
189			Command:     `${config.MergeZipsCmd} --ignore-duplicates -j $jarArgs $out $in`,
190			CommandDeps: []string{"${config.MergeZipsCmd}"},
191		},
192		"jarArgs")
193
194	jarjar = pctx.AndroidStaticRule("jarjar",
195		blueprint.RuleParams{
196			Command: "${config.JavaCmd} ${config.JavaVmFlags}" +
197				// b/146418363 Enable Android specific jarjar transformer to drop compat annotations
198				// for newly repackaged classes. Dropping @UnsupportedAppUsage on repackaged classes
199				// avoids adding new hiddenapis after jarjar'ing.
200				" -DremoveAndroidCompatAnnotations=true" +
201				" -jar ${config.JarjarCmd} process $rulesFile $in $out",
202			CommandDeps: []string{"${config.JavaCmd}", "${config.JarjarCmd}", "$rulesFile"},
203		},
204		"rulesFile")
205
206	packageCheck = pctx.AndroidStaticRule("packageCheck",
207		blueprint.RuleParams{
208			Command: "rm -f $out && " +
209				"${config.PackageCheckCmd} $in $packages && " +
210				"touch $out",
211			CommandDeps: []string{"${config.PackageCheckCmd}"},
212		},
213		"packages")
214
215	jetifier = pctx.AndroidStaticRule("jetifier",
216		blueprint.RuleParams{
217			Command:     "${config.JavaCmd}  ${config.JavaVmFlags} -jar ${config.JetifierJar} -l error -o $out -i $in",
218			CommandDeps: []string{"${config.JavaCmd}", "${config.JetifierJar}"},
219		},
220	)
221
222	zipalign = pctx.AndroidStaticRule("zipalign",
223		blueprint.RuleParams{
224			Command: "if ! ${config.ZipAlign} -c -p 4 $in > /dev/null; then " +
225				"${config.ZipAlign} -f -p 4 $in $out; " +
226				"else " +
227				"cp -f $in $out; " +
228				"fi",
229			CommandDeps: []string{"${config.ZipAlign}"},
230		},
231	)
232)
233
234func init() {
235	pctx.Import("android/soong/android")
236	pctx.Import("android/soong/java/config")
237	pctx.Import("android/soong/remoteexec")
238}
239
240type javaBuilderFlags struct {
241	javacFlags     string
242	bootClasspath  classpath
243	classpath      classpath
244	java9Classpath classpath
245	processorPath  classpath
246	processors     []string
247	systemModules  *systemModules
248	aidlFlags      string
249	aidlDeps       android.Paths
250	javaVersion    javaVersion
251
252	errorProneExtraJavacFlags string
253	errorProneProcessorPath   classpath
254
255	kotlincFlags     string
256	kotlincClasspath classpath
257
258	proto android.ProtoFlags
259}
260
261func TransformJavaToClasses(ctx android.ModuleContext, outputFile android.WritablePath, shardIdx int,
262	srcFiles, srcJars android.Paths, flags javaBuilderFlags, deps android.Paths) {
263
264	// Compile java sources into .class files
265	desc := "javac"
266	if shardIdx >= 0 {
267		desc += strconv.Itoa(shardIdx)
268	}
269
270	transformJavaToClasses(ctx, outputFile, shardIdx, srcFiles, srcJars, flags, deps, "javac", desc)
271}
272
273func RunErrorProne(ctx android.ModuleContext, outputFile android.WritablePath,
274	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
275
276	flags.processorPath = append(flags.errorProneProcessorPath, flags.processorPath...)
277
278	if len(flags.errorProneExtraJavacFlags) > 0 {
279		if len(flags.javacFlags) > 0 {
280			flags.javacFlags += " " + flags.errorProneExtraJavacFlags
281		} else {
282			flags.javacFlags = flags.errorProneExtraJavacFlags
283		}
284	}
285
286	transformJavaToClasses(ctx, outputFile, -1, srcFiles, srcJars, flags, nil,
287		"errorprone", "errorprone")
288}
289
290// Emits the rule to generate Xref input file (.kzip file) for the given set of source files and source jars
291// to compile with given set of builder flags, etc.
292func emitXrefRule(ctx android.ModuleContext, xrefFile android.WritablePath, idx int,
293	srcFiles, srcJars android.Paths,
294	flags javaBuilderFlags, deps android.Paths) {
295
296	deps = append(deps, srcJars...)
297	classpath := flags.classpath
298
299	var bootClasspath string
300	if flags.javaVersion.usesJavaModules() {
301		var systemModuleDeps android.Paths
302		bootClasspath, systemModuleDeps = flags.systemModules.FormJavaSystemModulesPath(ctx.Device())
303		deps = append(deps, systemModuleDeps...)
304		classpath = append(flags.java9Classpath, classpath...)
305	} else {
306		deps = append(deps, flags.bootClasspath...)
307		if len(flags.bootClasspath) == 0 && ctx.Device() {
308			// explicitly specify -bootclasspath "" if the bootclasspath is empty to
309			// ensure java does not fall back to the default bootclasspath.
310			bootClasspath = `-bootclasspath ""`
311		} else {
312			bootClasspath = flags.bootClasspath.FormJavaClassPath("-bootclasspath")
313		}
314	}
315
316	deps = append(deps, classpath...)
317	deps = append(deps, flags.processorPath...)
318
319	processor := "-proc:none"
320	if len(flags.processors) > 0 {
321		processor = "-processor " + strings.Join(flags.processors, ",")
322	}
323
324	intermediatesDir := "xref"
325	if idx >= 0 {
326		intermediatesDir += strconv.Itoa(idx)
327	}
328
329	ctx.Build(pctx,
330		android.BuildParams{
331			Rule:        kytheExtract,
332			Description: "Xref Java extractor",
333			Output:      xrefFile,
334			Inputs:      srcFiles,
335			Implicits:   deps,
336			Args: map[string]string{
337				"annoDir":       android.PathForModuleOut(ctx, intermediatesDir, "anno").String(),
338				"bootClasspath": bootClasspath,
339				"classpath":     classpath.FormJavaClassPath("-classpath"),
340				"javacFlags":    flags.javacFlags,
341				"javaVersion":   flags.javaVersion.String(),
342				"outDir":        android.PathForModuleOut(ctx, "javac", "classes.xref").String(),
343				"processorpath": flags.processorPath.FormJavaClassPath("-processorpath"),
344				"processor":     processor,
345				"srcJarDir":     android.PathForModuleOut(ctx, intermediatesDir, "srcjars.xref").String(),
346				"srcJars":       strings.Join(srcJars.Strings(), " "),
347			},
348		})
349}
350
351func TransformJavaToHeaderClasses(ctx android.ModuleContext, outputFile android.WritablePath,
352	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
353
354	var deps android.Paths
355	deps = append(deps, srcJars...)
356
357	classpath := flags.classpath
358
359	var bootClasspath string
360	if flags.javaVersion.usesJavaModules() {
361		var systemModuleDeps android.Paths
362		bootClasspath, systemModuleDeps = flags.systemModules.FormTurbineSystemModulesPath(ctx.Device())
363		deps = append(deps, systemModuleDeps...)
364		classpath = append(flags.java9Classpath, classpath...)
365	} else {
366		deps = append(deps, flags.bootClasspath...)
367		if len(flags.bootClasspath) == 0 && ctx.Device() {
368			// explicitly specify -bootclasspath "" if the bootclasspath is empty to
369			// ensure turbine does not fall back to the default bootclasspath.
370			bootClasspath = `--bootclasspath ""`
371		} else {
372			bootClasspath = flags.bootClasspath.FormTurbineClassPath("--bootclasspath ")
373		}
374	}
375
376	deps = append(deps, classpath...)
377	deps = append(deps, flags.processorPath...)
378
379	rule := turbine
380	args := map[string]string{
381		"javacFlags":    flags.javacFlags,
382		"bootClasspath": bootClasspath,
383		"srcJars":       strings.Join(srcJars.Strings(), " "),
384		"classpath":     classpath.FormTurbineClassPath("--classpath "),
385		"outDir":        android.PathForModuleOut(ctx, "turbine", "classes").String(),
386		"javaVersion":   flags.javaVersion.String(),
387	}
388	if ctx.Config().IsEnvTrue("RBE_TURBINE") {
389		rule = turbineRE
390		args["implicits"] = strings.Join(deps.Strings(), ",")
391	}
392	ctx.Build(pctx, android.BuildParams{
393		Rule:        rule,
394		Description: "turbine",
395		Output:      outputFile,
396		Inputs:      srcFiles,
397		Implicits:   deps,
398		Args:        args,
399	})
400}
401
402// transformJavaToClasses takes source files and converts them to a jar containing .class files.
403// srcFiles is a list of paths to sources, srcJars is a list of paths to jar files that contain
404// sources.  flags contains various command line flags to be passed to the compiler.
405//
406// This method may be used for different compilers, including javac and Error Prone.  The rule
407// argument specifies which command line to use and desc sets the description of the rule that will
408// be printed at build time.  The stem argument provides the file name of the output jar, and
409// suffix will be appended to various intermediate files and directories to avoid collisions when
410// this function is called twice in the same module directory.
411func transformJavaToClasses(ctx android.ModuleContext, outputFile android.WritablePath,
412	shardIdx int, srcFiles, srcJars android.Paths,
413	flags javaBuilderFlags, deps android.Paths,
414	intermediatesDir, desc string) {
415
416	deps = append(deps, srcJars...)
417
418	classpath := flags.classpath
419
420	var bootClasspath string
421	if flags.javaVersion.usesJavaModules() {
422		var systemModuleDeps android.Paths
423		bootClasspath, systemModuleDeps = flags.systemModules.FormJavaSystemModulesPath(ctx.Device())
424		deps = append(deps, systemModuleDeps...)
425		classpath = append(flags.java9Classpath, classpath...)
426	} else {
427		deps = append(deps, flags.bootClasspath...)
428		if len(flags.bootClasspath) == 0 && ctx.Device() {
429			// explicitly specify -bootclasspath "" if the bootclasspath is empty to
430			// ensure java does not fall back to the default bootclasspath.
431			bootClasspath = `-bootclasspath ""`
432		} else {
433			bootClasspath = flags.bootClasspath.FormJavaClassPath("-bootclasspath")
434		}
435	}
436
437	deps = append(deps, classpath...)
438	deps = append(deps, flags.processorPath...)
439
440	processor := "-proc:none"
441	if len(flags.processors) > 0 {
442		processor = "-processor " + strings.Join(flags.processors, ",")
443	}
444
445	srcJarDir := "srcjars"
446	outDir := "classes"
447	annoDir := "anno"
448	if shardIdx >= 0 {
449		shardDir := "shard" + strconv.Itoa(shardIdx)
450		srcJarDir = filepath.Join(shardDir, srcJarDir)
451		outDir = filepath.Join(shardDir, outDir)
452		annoDir = filepath.Join(shardDir, annoDir)
453	}
454	rule := javac
455	if ctx.Config().IsEnvTrue("RBE_JAVAC") {
456		rule = javacRE
457	}
458	ctx.Build(pctx, android.BuildParams{
459		Rule:        rule,
460		Description: desc,
461		Output:      outputFile,
462		Inputs:      srcFiles,
463		Implicits:   deps,
464		Args: map[string]string{
465			"javacFlags":    flags.javacFlags,
466			"bootClasspath": bootClasspath,
467			"classpath":     classpath.FormJavaClassPath("-classpath"),
468			"processorpath": flags.processorPath.FormJavaClassPath("-processorpath"),
469			"processor":     processor,
470			"srcJars":       strings.Join(srcJars.Strings(), " "),
471			"srcJarDir":     android.PathForModuleOut(ctx, intermediatesDir, srcJarDir).String(),
472			"outDir":        android.PathForModuleOut(ctx, intermediatesDir, outDir).String(),
473			"annoDir":       android.PathForModuleOut(ctx, intermediatesDir, annoDir).String(),
474			"javaVersion":   flags.javaVersion.String(),
475		},
476	})
477}
478
479func TransformResourcesToJar(ctx android.ModuleContext, outputFile android.WritablePath,
480	jarArgs []string, deps android.Paths) {
481
482	rule := jar
483	if ctx.Config().IsEnvTrue("RBE_JAR") {
484		rule = jarRE
485	}
486	ctx.Build(pctx, android.BuildParams{
487		Rule:        rule,
488		Description: "jar",
489		Output:      outputFile,
490		Implicits:   deps,
491		Args: map[string]string{
492			"jarArgs": strings.Join(proptools.NinjaAndShellEscapeList(jarArgs), " "),
493		},
494	})
495}
496
497func TransformJarsToJar(ctx android.ModuleContext, outputFile android.WritablePath, desc string,
498	jars android.Paths, manifest android.OptionalPath, stripDirEntries bool, filesToStrip []string,
499	dirsToStrip []string) {
500
501	var deps android.Paths
502
503	var jarArgs []string
504	if manifest.Valid() {
505		jarArgs = append(jarArgs, "-m ", manifest.String())
506		deps = append(deps, manifest.Path())
507	}
508
509	for _, dir := range dirsToStrip {
510		jarArgs = append(jarArgs, "-stripDir ", dir)
511	}
512
513	for _, file := range filesToStrip {
514		jarArgs = append(jarArgs, "-stripFile ", file)
515	}
516
517	// Remove any module-info.class files that may have come from prebuilt jars, they cause problems
518	// for downstream tools like desugar.
519	jarArgs = append(jarArgs, "-stripFile module-info.class")
520
521	if stripDirEntries {
522		jarArgs = append(jarArgs, "-D")
523	}
524
525	ctx.Build(pctx, android.BuildParams{
526		Rule:        combineJar,
527		Description: desc,
528		Output:      outputFile,
529		Inputs:      jars,
530		Implicits:   deps,
531		Args: map[string]string{
532			"jarArgs": strings.Join(jarArgs, " "),
533		},
534	})
535}
536
537func TransformJarJar(ctx android.ModuleContext, outputFile android.WritablePath,
538	classesJar android.Path, rulesFile android.Path) {
539	ctx.Build(pctx, android.BuildParams{
540		Rule:        jarjar,
541		Description: "jarjar",
542		Output:      outputFile,
543		Input:       classesJar,
544		Implicit:    rulesFile,
545		Args: map[string]string{
546			"rulesFile": rulesFile.String(),
547		},
548	})
549}
550
551func CheckJarPackages(ctx android.ModuleContext, outputFile android.WritablePath,
552	classesJar android.Path, permittedPackages []string) {
553	ctx.Build(pctx, android.BuildParams{
554		Rule:        packageCheck,
555		Description: "packageCheck",
556		Output:      outputFile,
557		Input:       classesJar,
558		Args: map[string]string{
559			"packages": strings.Join(permittedPackages, " "),
560		},
561	})
562}
563
564func TransformJetifier(ctx android.ModuleContext, outputFile android.WritablePath,
565	inputFile android.Path) {
566	ctx.Build(pctx, android.BuildParams{
567		Rule:        jetifier,
568		Description: "jetifier",
569		Output:      outputFile,
570		Input:       inputFile,
571	})
572}
573
574func GenerateMainClassManifest(ctx android.ModuleContext, outputFile android.WritablePath, mainClass string) {
575	ctx.Build(pctx, android.BuildParams{
576		Rule:        android.WriteFile,
577		Description: "manifest",
578		Output:      outputFile,
579		Args: map[string]string{
580			"content": "Main-Class: " + mainClass + "\n",
581		},
582	})
583}
584
585func TransformZipAlign(ctx android.ModuleContext, outputFile android.WritablePath, inputFile android.Path) {
586	ctx.Build(pctx, android.BuildParams{
587		Rule:        zipalign,
588		Description: "align",
589		Input:       inputFile,
590		Output:      outputFile,
591	})
592}
593
594type classpath android.Paths
595
596func (x *classpath) formJoinedClassPath(optName string, sep string) string {
597	if optName != "" && !strings.HasSuffix(optName, "=") && !strings.HasSuffix(optName, " ") {
598		optName += " "
599	}
600	if len(*x) > 0 {
601		return optName + strings.Join(x.Strings(), sep)
602	} else {
603		return ""
604	}
605}
606func (x *classpath) FormJavaClassPath(optName string) string {
607	return x.formJoinedClassPath(optName, ":")
608}
609
610func (x *classpath) FormTurbineClassPath(optName string) string {
611	return x.formJoinedClassPath(optName, " ")
612}
613
614// FormRepeatedClassPath returns a list of arguments with the given optName prefixed to each element of the classpath.
615func (x *classpath) FormRepeatedClassPath(optName string) []string {
616	if x == nil || *x == nil {
617		return nil
618	}
619	flags := make([]string, len(*x))
620	for i, v := range *x {
621		flags[i] = optName + v.String()
622	}
623
624	return flags
625}
626
627// Convert a classpath to an android.Paths
628func (x *classpath) Paths() android.Paths {
629	return append(android.Paths(nil), (*x)...)
630}
631
632func (x *classpath) Strings() []string {
633	if x == nil {
634		return nil
635	}
636	ret := make([]string, len(*x))
637	for i, path := range *x {
638		ret[i] = path.String()
639	}
640	return ret
641}
642
643type systemModules struct {
644	dir  android.Path
645	deps android.Paths
646}
647
648// Returns a --system argument in the form javac expects with -source 1.9 and the list of files to
649// depend on.  If forceEmpty is true, returns --system=none if the list is empty to ensure javac
650// does not fall back to the default system modules.
651func (x *systemModules) FormJavaSystemModulesPath(forceEmpty bool) (string, android.Paths) {
652	if x != nil {
653		return "--system=" + x.dir.String(), x.deps
654	} else if forceEmpty {
655		return "--system=none", nil
656	} else {
657		return "", nil
658	}
659}
660
661// Returns a --system argument in the form turbine expects with -source 1.9 and the list of files to
662// depend on.  If forceEmpty is true, returns --bootclasspath "" if the list is empty to ensure turbine
663// does not fall back to the default bootclasspath.
664func (x *systemModules) FormTurbineSystemModulesPath(forceEmpty bool) (string, android.Paths) {
665	if x != nil {
666		return "--system " + x.dir.String(), x.deps
667	} else if forceEmpty {
668		return `--bootclasspath ""`, nil
669	} else {
670		return "", nil
671	}
672}
673