diff --git a/Android.bp b/Android.bp index 2c400cf4a..03071d1d3 100644 --- a/Android.bp +++ b/Android.bp @@ -249,6 +249,7 @@ bootstrap_go_package { "java/jdeps.go", "java/java_resources.go", "java/kotlin.go", + "java/plugin.go", "java/prebuilt_apis.go", "java/proto.go", "java/sdk.go", @@ -262,6 +263,7 @@ bootstrap_go_package { "java/java_test.go", "java/jdeps_test.go", "java/kotlin_test.go", + "java/plugin_test.go", "java/sdk_test.go", ], pluginFor: ["soong_build"], diff --git a/java/builder.go b/java/builder.go index 40f72e107..67e8235b6 100644 --- a/java/builder.go +++ b/java/builder.go @@ -44,7 +44,7 @@ var ( `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + `(if [ -s $srcJarDir/list ] || [ -s $out.rsp ] ; then ` + `${config.SoongJavacWrapper} ${config.JavacWrapper}${config.JavacCmd} ${config.JavacHeapFlags} ${config.CommonJdkFlags} ` + - `$processorpath $javacFlags $bootClasspath $classpath ` + + `$processorpath $processor $javacFlags $bootClasspath $classpath ` + `-source $javaVersion -target $javaVersion ` + `-d $outDir -s $annoDir @$out.rsp @$srcJarDir/list ; fi ) && ` + `${config.SoongZipCmd} -jar -o $out -C $outDir -D $outDir`, @@ -57,7 +57,7 @@ var ( Rspfile: "$out.rsp", RspfileContent: "$in", }, - "javacFlags", "bootClasspath", "classpath", "processorpath", "srcJars", "srcJarDir", + "javacFlags", "bootClasspath", "classpath", "processorpath", "processor", "srcJars", "srcJarDir", "outDir", "annoDir", "javaVersion") turbine = pctx.AndroidStaticRule("turbine", @@ -141,6 +141,7 @@ type javaBuilderFlags struct { bootClasspath classpath classpath classpath processorPath classpath + processor string systemModules classpath aidlFlags string javaVersion string @@ -254,6 +255,12 @@ func transformJavaToClasses(ctx android.ModuleContext, outputFile android.Writab deps = append(deps, flags.classpath...) deps = append(deps, flags.processorPath...) + // TODO(b/77284273): pass -processor:none if no plugins are listed + processor := "" + if flags.processor != "" { + processor = "-processor " + flags.processor + } + srcJarDir := "srcjars" outDir := "classes" annoDir := "anno" @@ -274,6 +281,7 @@ func transformJavaToClasses(ctx android.ModuleContext, outputFile android.Writab "bootClasspath": bootClasspath, "classpath": flags.classpath.FormJavaClassPath("-classpath"), "processorpath": flags.processorPath.FormJavaClassPath("-processorpath"), + "processor": processor, "srcJars": strings.Join(srcJars.Strings(), " "), "srcJarDir": android.PathForModuleOut(ctx, intermediatesDir, srcJarDir).String(), "outDir": android.PathForModuleOut(ctx, intermediatesDir, outDir).String(), diff --git a/java/java.go b/java/java.go index e64b99eb3..7014668f4 100644 --- a/java/java.go +++ b/java/java.go @@ -114,11 +114,11 @@ type CompilerProperties struct { // If set to true, include sources used to compile the module in to the final jar Include_srcs *bool - // List of modules to use as annotation processors + // List of modules to use as annotation processors. Deprecated, use plugins instead. Annotation_processors []string - // List of classes to pass to javac to use as annotation processors - Annotation_processor_classes []string + // List of modules to use as annotation processors + Plugins []string // The number of Java source entries each Javac instance can process Javac_shard_size *int64 @@ -377,6 +377,7 @@ var ( staticLibTag = dependencyTag{name: "staticlib"} libTag = dependencyTag{name: "javalib"} annoTag = dependencyTag{name: "annotation processor"} + pluginTag = dependencyTag{name: "plugin"} bootClasspathTag = dependencyTag{name: "bootclasspath"} systemModulesTag = dependencyTag{name: "system modules"} frameworkResTag = dependencyTag{name: "framework-res"} @@ -474,6 +475,10 @@ func (j *Module) deps(ctx android.BottomUpMutatorContext) { {Mutator: "arch", Variation: ctx.Config().BuildOsCommonVariant}, }, annoTag, j.properties.Annotation_processors...) + ctx.AddFarVariationDependencies([]blueprint.Variation{ + {Mutator: "arch", Variation: ctx.Config().BuildOsCommonVariant}, + }, pluginTag, j.properties.Plugins...) + android.ExtractSourcesDeps(ctx, j.properties.Srcs) android.ExtractSourcesDeps(ctx, j.properties.Exclude_srcs) android.ExtractSourcesDeps(ctx, j.properties.Java_resources) @@ -563,6 +568,7 @@ type deps struct { classpath classpath bootClasspath classpath processorPath classpath + processorClasses []string staticJars android.Paths staticHeaderJars android.Paths staticResourceJars android.Paths @@ -573,6 +579,8 @@ type deps struct { aidlPreprocess android.OptionalPath kotlinStdlib android.Paths kotlinAnnotations android.Paths + + disableTurbine bool } func checkProducesJars(ctx android.ModuleContext, dep android.SourceFileProducer) { @@ -712,6 +720,16 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { j.exportedSdkLibs = append(j.exportedSdkLibs, dep.ExportedSdkLibs()...) case annoTag: deps.processorPath = append(deps.processorPath, dep.ImplementationAndResourcesJars()...) + case pluginTag: + if plugin, ok := dep.(*Plugin); ok { + deps.processorPath = append(deps.processorPath, dep.ImplementationAndResourcesJars()...) + if plugin.pluginProperties.Processor_class != nil { + deps.processorClasses = append(deps.processorClasses, *plugin.pluginProperties.Processor_class) + } + deps.disableTurbine = deps.disableTurbine || Bool(plugin.pluginProperties.Generates_api) + } else { + ctx.PropertyErrorf("plugins", "%q is not a java_plugin module", otherName) + } case frameworkResTag: if (ctx.ModuleName() == "framework") || (ctx.ModuleName() == "framework-annotation-proc") { // framework.jar has a one-off dependency on the R.java and Manifest.java files @@ -859,6 +877,8 @@ func (j *Module) collectBuilderFlags(ctx android.ModuleContext, deps deps) javaB flags.classpath = append(flags.classpath, deps.classpath...) flags.processorPath = append(flags.processorPath, deps.processorPath...) + flags.processor = strings.Join(deps.processorClasses, ",") + if len(flags.bootClasspath) == 0 && ctx.Host() && flags.javaVersion != "1.9" && !Bool(j.properties.No_standard_libs) && inList(flags.javaVersion, []string{"1.6", "1.7", "1.8"}) { @@ -1020,7 +1040,7 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path j.compiledSrcJars = srcJars enable_sharding := false - if ctx.Device() && !ctx.Config().IsEnvFalse("TURBINE_ENABLED") { + if ctx.Device() && !ctx.Config().IsEnvFalse("TURBINE_ENABLED") && !deps.disableTurbine { if j.properties.Javac_shard_size != nil && *(j.properties.Javac_shard_size) > 0 { enable_sharding = true // Formerly, there was a check here that prevented annotation processors diff --git a/java/java_test.go b/java/java_test.go index 9f805bb64..a0d962e3e 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -83,6 +83,7 @@ func testContext(config android.Config, bp string, ctx.RegisterModuleType("java_defaults", android.ModuleFactoryAdaptor(defaultsFactory)) ctx.RegisterModuleType("java_system_modules", android.ModuleFactoryAdaptor(SystemModulesFactory)) ctx.RegisterModuleType("java_genrule", android.ModuleFactoryAdaptor(genRuleFactory)) + ctx.RegisterModuleType("java_plugin", android.ModuleFactoryAdaptor(PluginFactory)) ctx.RegisterModuleType("filegroup", android.ModuleFactoryAdaptor(android.FileGroupFactory)) ctx.RegisterModuleType("genrule", android.ModuleFactoryAdaptor(genrule.GenRuleFactory)) ctx.RegisterModuleType("droiddoc", android.ModuleFactoryAdaptor(DroiddocFactory)) diff --git a/java/kotlin.go b/java/kotlin.go index c72f2de02..1b2e6429e 100644 --- a/java/kotlin.go +++ b/java/kotlin.go @@ -87,6 +87,7 @@ var kapt = pctx.AndroidGomaStaticRule("kapt", `-P plugin:org.jetbrains.kotlin.kapt3:aptMode=stubsAndApt ` + `-P plugin:org.jetbrains.kotlin.kapt3:javacArguments=$encodedJavacFlags ` + `$kaptProcessorPath ` + + `$kaptProcessor ` + `-Xbuild-file=$kotlinBuildFile && ` + `${config.SoongZipCmd} -jar -o $out -C $kaptDir/sources -D $kaptDir/sources`, CommandDeps: []string{ @@ -100,7 +101,8 @@ var kapt = pctx.AndroidGomaStaticRule("kapt", Rspfile: "$out.rsp", RspfileContent: `$in`, }, - "kotlincFlags", "encodedJavacFlags", "kaptProcessorPath", "classpath", "srcJars", "srcJarDir", "kaptDir", "kotlinJvmTarget", "kotlinBuildFile") + "kotlincFlags", "encodedJavacFlags", "kaptProcessorPath", "kaptProcessor", + "classpath", "srcJars", "srcJarDir", "kaptDir", "kotlinJvmTarget", "kotlinBuildFile") // kotlinKapt performs Kotlin-compatible annotation processing. It takes .kt and .java sources and srcjars, and runs // annotation processors over all of them, producing a srcjar of generated code in outputFile. The srcjar should be @@ -117,6 +119,11 @@ func kotlinKapt(ctx android.ModuleContext, outputFile android.WritablePath, kaptProcessorPath := flags.processorPath.FormTurbineClasspath("-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=") + kaptProcessor := "" + if flags.processor != "" { + kaptProcessor = "-P plugin:org.jetbrains.kotlin.kapt3:processor=" + flags.processor + } + encodedJavacFlags := kaptEncodeFlags([][2]string{ {"-source", flags.javaVersion}, {"-target", flags.javaVersion}, @@ -135,6 +142,7 @@ func kotlinKapt(ctx android.ModuleContext, outputFile android.WritablePath, "srcJarDir": android.PathForModuleOut(ctx, "kapt", "srcJars").String(), "kotlinBuildFile": android.PathForModuleOut(ctx, "kapt", "build.xml").String(), "kaptProcessorPath": strings.Join(kaptProcessorPath, " "), + "kaptProcessor": kaptProcessor, "kaptDir": android.PathForModuleOut(ctx, "kapt/gen").String(), "encodedJavacFlags": encodedJavacFlags, }, diff --git a/java/kotlin_test.go b/java/kotlin_test.go index 1069f4262..3deea1373 100644 --- a/java/kotlin_test.go +++ b/java/kotlin_test.go @@ -87,10 +87,10 @@ func TestKapt(t *testing.T) { java_library { name: "foo", srcs: ["a.java", "b.kt"], - annotation_processors: ["bar"], + plugins: ["bar"], } - java_library_host { + java_plugin { name: "bar", } `) diff --git a/java/plugin.go b/java/plugin.go new file mode 100644 index 000000000..a5e8292e7 --- /dev/null +++ b/java/plugin.go @@ -0,0 +1,50 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package java + +import "android/soong/android" + +func init() { + android.RegisterModuleType("java_plugin", PluginFactory) +} + +// A java_plugin module describes a host java library that will be used by javac as an annotation processor. +func PluginFactory() android.Module { + module := &Plugin{} + + module.AddProperties( + &module.Module.properties, + &module.Module.protoProperties, + &module.pluginProperties) + + InitJavaModule(module, android.HostSupported) + return module +} + +type Plugin struct { + Library + + pluginProperties PluginProperties +} + +type PluginProperties struct { + // The optional name of the class that javac will use to run the annotation processor. + Processor_class *string + + // If true, assume the annotation processor will generate classes that are referenced from outside the module. + // This necessitates disabling the turbine optimization on modules that use this plugin, which will reduce + // parallelism and cause more recompilation for modules that depend on modules that use this plugin. + Generates_api *bool +} diff --git a/java/plugin_test.go b/java/plugin_test.go new file mode 100644 index 000000000..7aa0164f1 --- /dev/null +++ b/java/plugin_test.go @@ -0,0 +1,124 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package java + +import ( + "android/soong/android" + "testing" +) + +func TestNoPlugin(t *testing.T) { + ctx := testJava(t, ` + java_library { + name: "foo", + srcs: ["a.java"], + } + `) + + javac := ctx.ModuleForTests("foo", "android_common").Rule("javac") + turbine := ctx.ModuleForTests("foo", "android_common").MaybeRule("turbine") + + if turbine.Rule == nil { + t.Errorf("expected turbine to be enabled") + } + + if javac.Args["processsorpath"] != "" { + t.Errorf("want empty processorpath, got %q", javac.Args["processorpath"]) + } + + // TODO(b/77284273): test for -processor:none if no plugins are enabled + if javac.Args["processor"] != "" { + t.Errorf("want no -processor argument, got %q", javac.Args["processor"]) + } +} + +func TestPlugin(t *testing.T) { + ctx := testJava(t, ` + java_library { + name: "foo", + srcs: ["a.java"], + plugins: ["bar"], + } + + java_plugin { + name: "bar", + processor_class: "com.bar", + srcs: ["b.java"], + } + `) + + buildOS := android.BuildOs.String() + + javac := ctx.ModuleForTests("foo", "android_common").Rule("javac") + turbine := ctx.ModuleForTests("foo", "android_common").MaybeRule("turbine") + + if turbine.Rule == nil { + t.Errorf("expected turbine to be enabled") + } + + bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String() + + if !inList(bar, javac.Implicits.Strings()) { + t.Errorf("foo implicits %v does not contain %q", javac.Implicits.Strings(), bar) + } + + if javac.Args["processorpath"] != "-processorpath "+bar { + t.Errorf("foo processorpath %q != '-processorpath %s'", javac.Args["processorpath"], bar) + } + + if javac.Args["processor"] != "-processor com.bar" { + t.Errorf("foo processor %q != '-processor com.bar'", javac.Args["processor"]) + } +} + +func TestPluginGeneratesApi(t *testing.T) { + ctx := testJava(t, ` + java_library { + name: "foo", + srcs: ["a.java"], + plugins: ["bar"], + } + + java_plugin { + name: "bar", + processor_class: "com.bar", + generates_api: true, + srcs: ["b.java"], + } + `) + + buildOS := android.BuildOs.String() + + javac := ctx.ModuleForTests("foo", "android_common").Rule("javac") + turbine := ctx.ModuleForTests("foo", "android_common").MaybeRule("turbine") + + if turbine.Rule != nil { + t.Errorf("expected turbine to be disabled") + } + + bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String() + + if !inList(bar, javac.Implicits.Strings()) { + t.Errorf("foo implicits %v does not contain %q", javac.Implicits.Strings(), bar) + } + + if javac.Args["processorpath"] != "-processorpath "+bar { + t.Errorf("foo processorpath %q != '-processorpath %s'", javac.Args["processorpath"], bar) + } + + if javac.Args["processor"] != "-processor com.bar" { + t.Errorf("foo processor %q != '-processor com.bar'", javac.Args["processor"]) + } +}