diff options
Diffstat (limited to 'java')
-rw-r--r-- | java/builder.go | 6 | ||||
-rw-r--r-- | java/config/kotlin.go | 11 | ||||
-rw-r--r-- | java/dex.go | 4 | ||||
-rw-r--r-- | java/java.go | 25 | ||||
-rw-r--r-- | java/java_test.go | 1 | ||||
-rw-r--r-- | java/kotlin.go | 107 | ||||
-rw-r--r-- | java/kotlin_test.go | 107 |
7 files changed, 247 insertions, 14 deletions
diff --git a/java/builder.go b/java/builder.go index f1a6ead0..40f72e10 100644 --- a/java/builder.go +++ b/java/builder.go @@ -200,7 +200,7 @@ func TransformJavaToHeaderClasses(ctx android.ModuleContext, outputFile android. // ensure java does not fall back to the default bootclasspath. bootClasspath = `--bootclasspath ""` } else { - bootClasspath = strings.Join(flags.bootClasspath.FormTurbineClasspath("--bootclasspath"), " ") + bootClasspath = strings.Join(flags.bootClasspath.FormTurbineClasspath("--bootclasspath "), " ") } ctx.Build(pctx, android.BuildParams{ @@ -213,7 +213,7 @@ func TransformJavaToHeaderClasses(ctx android.ModuleContext, outputFile android. "javacFlags": flags.javacFlags, "bootClasspath": bootClasspath, "srcJars": strings.Join(srcJars.Strings(), " "), - "classpath": strings.Join(flags.classpath.FormTurbineClasspath("--classpath"), " "), + "classpath": strings.Join(flags.classpath.FormTurbineClasspath("--classpath "), " "), "outDir": android.PathForModuleOut(ctx, "turbine", "classes").String(), "javaVersion": flags.javaVersion, }, @@ -415,7 +415,7 @@ func (x *classpath) FormTurbineClasspath(optName string) []string { } flags := make([]string, len(*x)) for i, v := range *x { - flags[i] = optName + " " + v.String() + flags[i] = optName + v.String() } return flags diff --git a/java/config/kotlin.go b/java/config/kotlin.go index 432840e5..2af7b3c3 100644 --- a/java/config/kotlin.go +++ b/java/config/kotlin.go @@ -14,6 +14,8 @@ package config +import "strings" + var ( KotlinStdlibJar = "external/kotlinc/lib/kotlin-stdlib.jar" KotlincIllegalFlags = []string{ @@ -25,5 +27,14 @@ var ( func init() { pctx.SourcePathVariable("KotlincCmd", "external/kotlinc/bin/kotlinc") pctx.SourcePathVariable("KotlinCompilerJar", "external/kotlinc/lib/kotlin-compiler.jar") + pctx.SourcePathVariable("KotlinKaptJar", "external/kotlinc/lib/kotlin-annotation-processing.jar") pctx.SourcePathVariable("KotlinStdlibJar", KotlinStdlibJar) + + // These flags silence "Illegal reflective access" warnings when running kotlinc in OpenJDK9 + pctx.StaticVariable("KotlincSuppressJDK9Warnings", strings.Join([]string{ + "-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "-J--add-opens=java.base/sun.net.www.protocol.jar=ALL-UNNAMED", + }, " ")) } diff --git a/java/dex.go b/java/dex.go index a6d486ab..25319701 100644 --- a/java/dex.go +++ b/java/dex.go @@ -85,8 +85,8 @@ func (j *Module) dexCommonFlags(ctx android.ModuleContext) []string { func (j *Module) d8Flags(ctx android.ModuleContext, flags javaBuilderFlags) ([]string, android.Paths) { d8Flags := j.dexCommonFlags(ctx) - d8Flags = append(d8Flags, flags.bootClasspath.FormTurbineClasspath("--lib")...) - d8Flags = append(d8Flags, flags.classpath.FormTurbineClasspath("--lib")...) + d8Flags = append(d8Flags, flags.bootClasspath.FormTurbineClasspath("--lib ")...) + d8Flags = append(d8Flags, flags.classpath.FormTurbineClasspath("--lib ")...) var d8Deps android.Paths d8Deps = append(d8Deps, flags.bootClasspath...) diff --git a/java/java.go b/java/java.go index d2710a80..f3b47be3 100644 --- a/java/java.go +++ b/java/java.go @@ -379,6 +379,7 @@ var ( frameworkResTag = dependencyTag{name: "framework-res"} frameworkApkTag = dependencyTag{name: "framework-apk"} kotlinStdlibTag = dependencyTag{name: "kotlin-stdlib"} + kotlinAnnotationsTag = dependencyTag{name: "kotlin-annotations"} proguardRaiseTag = dependencyTag{name: "proguard-raise"} certificateTag = dependencyTag{name: "certificate"} instrumentationForTag = dependencyTag{name: "instrumentation_for"} @@ -483,6 +484,9 @@ func (j *Module) deps(ctx android.BottomUpMutatorContext) { // TODO(ccross): move this to a mutator pass that can tell if generated sources contain // Kotlin files ctx.AddVariationDependencies(nil, kotlinStdlibTag, "kotlin-stdlib") + if len(j.properties.Annotation_processors) > 0 { + ctx.AddVariationDependencies(nil, kotlinAnnotationsTag, "kotlin-annotations") + } } if j.shouldInstrumentStatic(ctx) { @@ -564,6 +568,7 @@ type deps struct { systemModules android.Path aidlPreprocess android.OptionalPath kotlinStdlib android.Paths + kotlinAnnotations android.Paths } func checkProducesJars(ctx android.ModuleContext, dep android.SourceFileProducer) { @@ -723,6 +728,8 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { } case kotlinStdlibTag: deps.kotlinStdlib = dep.HeaderJars() + case kotlinAnnotationsTag: + deps.kotlinAnnotations = dep.HeaderJars() } deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...) @@ -969,9 +976,20 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path kotlinSrcFiles = append(kotlinSrcFiles, uniqueSrcFiles...) kotlinSrcFiles = append(kotlinSrcFiles, srcFiles.FilterByExt(".kt")...) - flags.kotlincClasspath = append(flags.kotlincClasspath, deps.bootClasspath...) - flags.kotlincClasspath = append(flags.kotlincClasspath, deps.kotlinStdlib...) - flags.kotlincClasspath = append(flags.kotlincClasspath, deps.classpath...) + flags.classpath = append(flags.classpath, deps.kotlinStdlib...) + flags.classpath = append(flags.classpath, deps.kotlinAnnotations...) + + flags.kotlincClasspath = append(flags.kotlincClasspath, flags.bootClasspath...) + flags.kotlincClasspath = append(flags.kotlincClasspath, flags.classpath...) + + if len(flags.processorPath) > 0 { + // Use kapt for annotation processing + kaptSrcJar := android.PathForModuleOut(ctx, "kapt", "kapt-sources.jar") + kotlinKapt(ctx, kaptSrcJar, kotlinSrcFiles, srcJars, flags) + srcJars = append(srcJars, kaptSrcJar) + // Disable annotation processing in javac, it's already been handled by kapt + flags.processorPath = nil + } kotlinJar := android.PathForModuleOut(ctx, "kotlin", jarName) kotlinCompile(ctx, kotlinJar, kotlinSrcFiles, srcJars, flags) @@ -980,7 +998,6 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path } // Make javac rule depend on the kotlinc rule - flags.classpath = append(flags.classpath, deps.kotlinStdlib...) flags.classpath = append(flags.classpath, kotlinJar) // Jar kotlin classes into the final jar after javac diff --git a/java/java_test.go b/java/java_test.go index e16ae355..9f805bb6 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -121,6 +121,7 @@ func testContext(config android.Config, bp string, "core.current.stubs", "core.platform.api.stubs", "kotlin-stdlib", + "kotlin-annotations", } for _, extra := range extraModules { diff --git a/java/kotlin.go b/java/kotlin.go index 5bc7441c..c72f2de0 100644 --- a/java/kotlin.go +++ b/java/kotlin.go @@ -15,9 +15,13 @@ package java import ( - "android/soong/android" + "bytes" + "encoding/base64" + "encoding/binary" "strings" + "android/soong/android" + "github.com/google/blueprint" ) @@ -26,7 +30,7 @@ var kotlinc = pctx.AndroidGomaStaticRule("kotlinc", Command: `rm -rf "$classesDir" "$srcJarDir" "$kotlinBuildFile" && mkdir -p "$classesDir" "$srcJarDir" && ` + `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + `${config.GenKotlinBuildFileCmd} $classpath $classesDir $out.rsp $srcJarDir/list > $kotlinBuildFile &&` + - `${config.KotlincCmd} $kotlincFlags ` + + `${config.KotlincCmd} ${config.JavacHeapFlags} $kotlincFlags ` + `-jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile && ` + `${config.SoongZipCmd} -jar -o $out -C $classesDir -D $classesDir`, CommandDeps: []string{ @@ -41,12 +45,11 @@ var kotlinc = pctx.AndroidGomaStaticRule("kotlinc", }, "kotlincFlags", "classpath", "srcJars", "srcJarDir", "classesDir", "kotlinJvmTarget", "kotlinBuildFile") +// kotlinCompile takes .java and .kt sources and srcJars, and compiles the .kt sources into a classes jar in outputFile. func kotlinCompile(ctx android.ModuleContext, outputFile android.WritablePath, srcFiles, srcJars android.Paths, flags javaBuilderFlags) { - inputs := append(android.Paths(nil), srcFiles...) - var deps android.Paths deps = append(deps, flags.kotlincClasspath...) deps = append(deps, srcJars...) @@ -55,7 +58,7 @@ func kotlinCompile(ctx android.ModuleContext, outputFile android.WritablePath, Rule: kotlinc, Description: "kotlinc", Output: outputFile, - Inputs: inputs, + Inputs: srcFiles, Implicits: deps, Args: map[string]string{ "classpath": flags.kotlincClasspath.FormJavaClassPath("-classpath"), @@ -69,3 +72,97 @@ func kotlinCompile(ctx android.ModuleContext, outputFile android.WritablePath, }, }) } + +var kapt = pctx.AndroidGomaStaticRule("kapt", + blueprint.RuleParams{ + Command: `rm -rf "$srcJarDir" "$kotlinBuildFile" "$kaptDir" && mkdir -p "$srcJarDir" "$kaptDir" && ` + + `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` + + `${config.GenKotlinBuildFileCmd} $classpath "" $out.rsp $srcJarDir/list > $kotlinBuildFile &&` + + `${config.KotlincCmd} ${config.KotlincSuppressJDK9Warnings} ${config.JavacHeapFlags} $kotlincFlags ` + + `-Xplugin=${config.KotlinKaptJar} ` + + `-P plugin:org.jetbrains.kotlin.kapt3:sources=$kaptDir/sources ` + + `-P plugin:org.jetbrains.kotlin.kapt3:classes=$kaptDir/classes ` + + `-P plugin:org.jetbrains.kotlin.kapt3:stubs=$kaptDir/stubs ` + + `-P plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true ` + + `-P plugin:org.jetbrains.kotlin.kapt3:aptMode=stubsAndApt ` + + `-P plugin:org.jetbrains.kotlin.kapt3:javacArguments=$encodedJavacFlags ` + + `$kaptProcessorPath ` + + `-Xbuild-file=$kotlinBuildFile && ` + + `${config.SoongZipCmd} -jar -o $out -C $kaptDir/sources -D $kaptDir/sources`, + CommandDeps: []string{ + "${config.KotlincCmd}", + "${config.KotlinCompilerJar}", + "${config.KotlinKaptJar}", + "${config.GenKotlinBuildFileCmd}", + "${config.SoongZipCmd}", + "${config.ZipSyncCmd}", + }, + Rspfile: "$out.rsp", + RspfileContent: `$in`, + }, + "kotlincFlags", "encodedJavacFlags", "kaptProcessorPath", "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 +// added as an additional input to kotlinc and javac rules, and the javac rule should have annotation processing +// disabled. +func kotlinKapt(ctx android.ModuleContext, outputFile android.WritablePath, + srcFiles, srcJars android.Paths, + flags javaBuilderFlags) { + + var deps android.Paths + deps = append(deps, flags.kotlincClasspath...) + deps = append(deps, srcJars...) + deps = append(deps, flags.processorPath...) + + kaptProcessorPath := flags.processorPath.FormTurbineClasspath("-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=") + + encodedJavacFlags := kaptEncodeFlags([][2]string{ + {"-source", flags.javaVersion}, + {"-target", flags.javaVersion}, + }) + + ctx.Build(pctx, android.BuildParams{ + Rule: kapt, + Description: "kapt", + Output: outputFile, + Inputs: srcFiles, + Implicits: deps, + Args: map[string]string{ + "classpath": flags.kotlincClasspath.FormJavaClassPath("-classpath"), + "kotlincFlags": flags.kotlincFlags, + "srcJars": strings.Join(srcJars.Strings(), " "), + "srcJarDir": android.PathForModuleOut(ctx, "kapt", "srcJars").String(), + "kotlinBuildFile": android.PathForModuleOut(ctx, "kapt", "build.xml").String(), + "kaptProcessorPath": strings.Join(kaptProcessorPath, " "), + "kaptDir": android.PathForModuleOut(ctx, "kapt/gen").String(), + "encodedJavacFlags": encodedJavacFlags, + }, + }) +} + +// kapt converts a list of key, value pairs into a base64 encoded Java serialization, which is what kapt expects. +func kaptEncodeFlags(options [][2]string) string { + buf := &bytes.Buffer{} + + binary.Write(buf, binary.BigEndian, uint32(len(options))) + for _, option := range options { + binary.Write(buf, binary.BigEndian, uint16(len(option[0]))) + buf.WriteString(option[0]) + binary.Write(buf, binary.BigEndian, uint16(len(option[1]))) + buf.WriteString(option[1]) + } + + header := &bytes.Buffer{} + header.Write([]byte{0xac, 0xed, 0x00, 0x05}) // java serialization header + + if buf.Len() < 256 { + header.WriteByte(0x77) // blockdata + header.WriteByte(byte(buf.Len())) + } else { + header.WriteByte(0x7a) // blockdatalong + binary.Write(header, binary.BigEndian, uint32(buf.Len())) + } + + return base64.StdEncoding.EncodeToString(append(header.Bytes(), buf.Bytes()...)) +} diff --git a/java/kotlin_test.go b/java/kotlin_test.go index 08a30851..1069f426 100644 --- a/java/kotlin_test.go +++ b/java/kotlin_test.go @@ -15,6 +15,7 @@ package java import ( + "strconv" "strings" "testing" ) @@ -80,3 +81,109 @@ func TestKotlin(t *testing.T) { bazHeaderJar.Output.String(), barKotlinc.Implicits.Strings()) } } + +func TestKapt(t *testing.T) { + ctx := testJava(t, ` + java_library { + name: "foo", + srcs: ["a.java", "b.kt"], + annotation_processors: ["bar"], + } + + java_library_host { + name: "bar", + } + `) + + kapt := ctx.ModuleForTests("foo", "android_common").Rule("kapt") + kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc") + javac := ctx.ModuleForTests("foo", "android_common").Rule("javac") + + // Test that the kotlin and java sources are passed to kapt and kotlinc + if len(kapt.Inputs) != 2 || kapt.Inputs[0].String() != "a.java" || kapt.Inputs[1].String() != "b.kt" { + t.Errorf(`foo kapt inputs %v != ["a.java", "b.kt"]`, kapt.Inputs) + } + if len(kotlinc.Inputs) != 2 || kotlinc.Inputs[0].String() != "a.java" || kotlinc.Inputs[1].String() != "b.kt" { + t.Errorf(`foo kotlinc inputs %v != ["a.java", "b.kt"]`, kotlinc.Inputs) + } + + // Test that only the java sources are passed to javac + if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" { + t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs) + } + + // Test that the kapt srcjar is a dependency of kotlinc and javac rules + if !inList(kapt.Output.String(), kotlinc.Implicits.Strings()) { + t.Errorf("expected %q in kotlinc implicits %v", kapt.Output.String(), kotlinc.Implicits.Strings()) + } + if !inList(kapt.Output.String(), javac.Implicits.Strings()) { + t.Errorf("expected %q in javac implicits %v", kapt.Output.String(), javac.Implicits.Strings()) + } + + // Test that the kapt srcjar is extracted by the kotlinc and javac rules + if kotlinc.Args["srcJars"] != kapt.Output.String() { + t.Errorf("expected %q in kotlinc srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"]) + } + if javac.Args["srcJars"] != kapt.Output.String() { + t.Errorf("expected %q in javac srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"]) + } +} + +func TestKaptEncodeFlags(t *testing.T) { + // Compares the kaptEncodeFlags against the results of the example implementation at + // https://kotlinlang.org/docs/reference/kapt.html#apjavac-options-encoding + tests := []struct { + in [][2]string + out string + }{ + { + // empty input + in: [][2]string{}, + out: "rO0ABXcEAAAAAA==", + }, + { + // common input + in: [][2]string{ + {"-source", "1.8"}, + {"-target", "1.8"}, + }, + out: "rO0ABXcgAAAAAgAHLXNvdXJjZQADMS44AActdGFyZ2V0AAMxLjg=", + }, + { + // input that serializes to a 255 byte block + in: [][2]string{ + {"-source", "1.8"}, + {"-target", "1.8"}, + {"a", strings.Repeat("b", 218)}, + }, + out: "rO0ABXf/AAAAAwAHLXNvdXJjZQADMS44AActdGFyZ2V0AAMxLjgAAWEA2mJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJi", + }, + { + // input that serializes to a 256 byte block + in: [][2]string{ + {"-source", "1.8"}, + {"-target", "1.8"}, + {"a", strings.Repeat("b", 219)}, + }, + out: "rO0ABXoAAAEAAAAAAwAHLXNvdXJjZQADMS44AActdGFyZ2V0AAMxLjgAAWEA22JiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYg==", + }, + { + // input that serializes to a 257 byte block + in: [][2]string{ + {"-source", "1.8"}, + {"-target", "1.8"}, + {"a", strings.Repeat("b", 220)}, + }, + out: "rO0ABXoAAAEBAAAAAwAHLXNvdXJjZQADMS44AActdGFyZ2V0AAMxLjgAAWEA3GJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI=", + }, + } + + for i, test := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + got := kaptEncodeFlags(test.in) + if got != test.out { + t.Errorf("\nwant %q\n got %q", test.out, got) + } + }) + } +} |