Add support for .proto files in java modules

Test: m -j checkbuild
Change-Id: Ia03429948baebff85164a91a34507866c97a08ef
diff --git a/Android.bp b/Android.bp
index 0e57c12..ae31a60 100644
--- a/Android.bp
+++ b/Android.bp
@@ -209,6 +209,7 @@
         "java/builder.go",
         "java/gen.go",
         "java/java.go",
+        "java/proto.go",
         "java/resources.go",
     ],
     testSrcs: [
diff --git a/android/proto.go b/android/proto.go
index 9bb9cfb..1c70656 100644
--- a/android/proto.go
+++ b/android/proto.go
@@ -14,22 +14,6 @@
 
 package android
 
-import (
-	"github.com/google/blueprint"
-)
-
-func init() {
-	pctx.HostBinToolVariable("protocCmd", "aprotoc")
-}
-
-var (
-	proto = pctx.AndroidStaticRule("protoc",
-		blueprint.RuleParams{
-			Command:     "$protocCmd $protoOut=$protoOutFlags:$outDir $protoFlags $in",
-			CommandDeps: []string{"$protocCmd"},
-		}, "protoFlags", "protoOut", "protoOutFlags", "outDir")
-)
-
 // TODO(ccross): protos are often used to communicate between multiple modules.  If the only
 // way to convert a proto to source is to reference it as a source file, and external modules cannot
 // reference source files in other modules, then every module that owns a proto file will need to
@@ -38,30 +22,6 @@
 // and then external modules could depend on the proto module but use their own settings to
 // generate the source.
 
-func GenProto(ctx ModuleContext, protoFile Path,
-	protoFlags string, protoOut, protoOutFlags string, extensions []string) WritablePaths {
-
-	var outFiles WritablePaths
-	for _, ext := range extensions {
-		outFiles = append(outFiles, GenPathWithExt(ctx, "proto", protoFile, ext))
-	}
-
-	ctx.ModuleBuild(pctx, ModuleBuildParams{
-		Rule:        proto,
-		Description: "protoc " + protoFile.Rel(),
-		Outputs:     outFiles,
-		Input:       protoFile,
-		Args: map[string]string{
-			"outDir":        ProtoDir(ctx).String(),
-			"protoOut":      protoOut,
-			"protoOutFlags": protoOutFlags,
-			"protoFlags":    protoFlags,
-		},
-	})
-
-	return outFiles
-}
-
 func ProtoFlags(ctx ModuleContext, p *ProtoProperties) []string {
 	var protoFlags []string
 	if len(p.Proto.Local_include_dirs) > 0 {
diff --git a/cc/gen.go b/cc/gen.go
index 9fc14c5..2280e0f 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -153,10 +153,9 @@
 			srcFiles[i] = cppFile
 			genLex(ctx, srcFile, cppFile)
 		case ".proto":
-			protoFiles := android.GenProto(ctx, srcFile, buildFlags.protoFlags,
-				"--cpp_out", "", []string{"pb.cc", "pb.h"})
-			srcFiles[i] = protoFiles[0]
-			deps = append(deps, protoFiles[1])
+			ccFile, headerFile := genProto(ctx, srcFile, buildFlags.protoFlags)
+			srcFiles[i] = ccFile
+			deps = append(deps, headerFile)
 		case ".aidl":
 			cppFile := android.GenPathWithExt(ctx, "aidl", srcFile, "cpp")
 			srcFiles[i] = cppFile
diff --git a/cc/proto.go b/cc/proto.go
index 6049d44..a01951f 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -15,11 +15,46 @@
 package cc
 
 import (
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
 )
 
+func init() {
+	pctx.HostBinToolVariable("protocCmd", "aprotoc")
+}
+
+var (
+	proto = pctx.AndroidStaticRule("protoc",
+		blueprint.RuleParams{
+			Command:     "$protocCmd --cpp_out=$outDir $protoFlags $in",
+			CommandDeps: []string{"$protocCmd"},
+		}, "protoFlags", "outDir")
+)
+
+// genProto creates a rule to convert a .proto file to generated .pb.cc and .pb.h files and returns
+// the paths to the generated files.
+func genProto(ctx android.ModuleContext, protoFile android.Path,
+	protoFlags string) (ccFile, headerFile android.WritablePath) {
+
+	ccFile = android.GenPathWithExt(ctx, "proto", protoFile, "pb.cc")
+	headerFile = android.GenPathWithExt(ctx, "proto", protoFile, "pb.h")
+
+	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
+		Rule:        proto,
+		Description: "protoc " + protoFile.Rel(),
+		Outputs:     android.WritablePaths{ccFile, headerFile},
+		Input:       protoFile,
+		Args: map[string]string{
+			"outDir":     android.ProtoDir(ctx).String(),
+			"protoFlags": protoFlags,
+		},
+	})
+
+	return ccFile, headerFile
+}
+
 func protoDeps(ctx BaseModuleContext, deps Deps, p *android.ProtoProperties, static bool) Deps {
 	var lib string
 
diff --git a/java/builder.go b/java/builder.go
index 95345d4..b924d65 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -127,6 +127,9 @@
 	desugarFlags  string
 	aidlFlags     string
 	javaVersion   string
+
+	protoFlags   string
+	protoOutFlag string
 }
 
 func TransformJavaToClasses(ctx android.ModuleContext, srcFiles, srcFileLists android.Paths,
@@ -136,7 +139,10 @@
 	annoDir := android.PathForModuleOut(ctx, "anno")
 	classJar := android.PathForModuleOut(ctx, "classes-compiled.jar")
 
-	javacFlags := flags.javacFlags + android.JoinWithPrefix(srcFileLists.Strings(), "@")
+	javacFlags := flags.javacFlags
+	if len(srcFileLists) > 0 {
+		javacFlags += " " + android.JoinWithPrefix(srcFileLists.Strings(), "@")
+	}
 
 	deps = append(deps, srcFileLists...)
 	deps = append(deps, flags.bootClasspath...)
@@ -161,8 +167,8 @@
 	return classJar
 }
 
-func RunErrorProne(ctx android.ModuleContext, srcFiles android.Paths, srcFileLists android.Paths,
-	flags javaBuilderFlags, deps android.Paths) android.Path {
+func RunErrorProne(ctx android.ModuleContext, srcFiles, srcFileLists android.Paths,
+	flags javaBuilderFlags) android.Path {
 
 	if config.ErrorProneJar == "" {
 		ctx.ModuleErrorf("cannot build with Error Prone, missing external/error_prone?")
@@ -173,7 +179,12 @@
 	annoDir := android.PathForModuleOut(ctx, "anno-errorprone")
 	classFileList := android.PathForModuleOut(ctx, "classes-errorprone.list")
 
-	javacFlags := flags.javacFlags + android.JoinWithPrefix(srcFileLists.Strings(), "@")
+	javacFlags := flags.javacFlags
+	if len(srcFileLists) > 0 {
+		javacFlags += " " + android.JoinWithPrefix(srcFileLists.Strings(), "@")
+	}
+
+	var deps android.Paths
 
 	deps = append(deps, srcFileLists...)
 	deps = append(deps, flags.bootClasspath...)
diff --git a/java/gen.go b/java/gen.go
index e473859..e55be91 100644
--- a/java/gen.go
+++ b/java/gen.go
@@ -85,21 +85,37 @@
 }
 
 func (j *Module) genSources(ctx android.ModuleContext, srcFiles android.Paths,
-	flags javaBuilderFlags) android.Paths {
+	flags javaBuilderFlags) (android.Paths, android.Paths) {
 
-	for i, srcFile := range srcFiles {
+	var protoFiles android.Paths
+	outSrcFiles := make(android.Paths, 0, len(srcFiles))
+
+	for _, srcFile := range srcFiles {
 		switch srcFile.Ext() {
 		case ".aidl":
 			javaFile := genAidl(ctx, srcFile, flags.aidlFlags)
-			srcFiles[i] = javaFile
+			outSrcFiles = append(outSrcFiles, javaFile)
 		case ".logtags":
 			j.logtagsSrcs = append(j.logtagsSrcs, srcFile)
 			javaFile := genLogtags(ctx, srcFile)
-			srcFiles[i] = javaFile
+			outSrcFiles = append(outSrcFiles, javaFile)
+		case ".proto":
+			protoFiles = append(protoFiles, srcFile)
+		default:
+			outSrcFiles = append(outSrcFiles, srcFile)
 		}
 	}
 
-	return srcFiles
+	var outSrcFileLists android.Paths
+
+	if len(protoFiles) > 0 {
+		protoFileList := genProto(ctx, protoFiles,
+			flags.protoFlags, flags.protoOutFlag, "")
+
+		outSrcFileLists = append(outSrcFileLists, protoFileList)
+	}
+
+	return outSrcFiles, outSrcFileLists
 }
 
 func LogtagsSingleton() blueprint.Singleton {
diff --git a/java/java.go b/java/java.go
index d8bc0c2..eb94806 100644
--- a/java/java.go
+++ b/java/java.go
@@ -49,7 +49,6 @@
 
 // TODO:
 // Autogenerated files:
-//  Proto
 //  Renderscript
 // Post-jar passes:
 //  Proguard
@@ -145,6 +144,7 @@
 	android.DefaultableModuleBase
 
 	properties       CompilerProperties
+	protoProperties  android.ProtoProperties
 	deviceProperties CompilerDeviceProperties
 
 	// output file suitable for inserting into the classpath of another compile
@@ -296,6 +296,24 @@
 
 	android.ExtractSourcesDeps(ctx, j.properties.Srcs)
 	android.ExtractSourcesDeps(ctx, j.properties.Java_resources)
+
+	if j.hasSrcExt(".proto") {
+		protoDeps(ctx, &j.protoProperties)
+	}
+}
+
+func hasSrcExt(srcs []string, ext string) bool {
+	for _, src := range srcs {
+		if filepath.Ext(src) == ext {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (j *Module) hasSrcExt(ext string) bool {
+	return hasSrcExt(j.properties.Srcs, ext)
 }
 
 func (j *Module) aidlFlags(ctx android.ModuleContext, aidlPreprocess android.OptionalPath,
@@ -416,7 +434,15 @@
 
 	srcFiles := ctx.ExpandSources(j.properties.Srcs, j.properties.Exclude_srcs)
 
-	srcFiles = j.genSources(ctx, srcFiles, flags)
+	if hasSrcExt(srcFiles.Strings(), ".proto") {
+		flags = protoFlags(ctx, &j.protoProperties, flags)
+	}
+
+	var srcFileLists android.Paths
+
+	srcFiles, srcFileLists = j.genSources(ctx, srcFiles, flags)
+
+	srcFileLists = append(srcFileLists, deps.srcFileLists...)
 
 	ctx.VisitDirectDeps(func(module blueprint.Module) {
 		if gen, ok := module.(genrule.SourceFileGenerator); ok {
@@ -424,7 +450,7 @@
 		}
 	})
 
-	deps.srcFileLists = append(deps.srcFileLists, j.ExtraSrcLists...)
+	srcFileLists = append(srcFileLists, j.ExtraSrcLists...)
 
 	var jars android.Paths
 
@@ -436,12 +462,12 @@
 			// a rebuild when error-prone is turned off).
 			// TODO(ccross): Once we always compile with javac9 we may be able to conditionally
 			//    enable error-prone without affecting the output class files.
-			errorprone := RunErrorProne(ctx, srcFiles, deps.srcFileLists, flags, nil)
+			errorprone := RunErrorProne(ctx, srcFiles, srcFileLists, flags)
 			extraJarDeps = append(extraJarDeps, errorprone)
 		}
 
 		// Compile java sources into .class files
-		classes := TransformJavaToClasses(ctx, srcFiles, deps.srcFileLists, flags, extraJarDeps)
+		classes := TransformJavaToClasses(ctx, srcFiles, srcFileLists, flags, extraJarDeps)
 		if ctx.Failed() {
 			return
 		}
@@ -629,7 +655,8 @@
 
 		module.AddProperties(
 			&module.Module.properties,
-			&module.Module.deviceProperties)
+			&module.Module.deviceProperties,
+			&module.Module.protoProperties)
 
 		InitJavaModule(module, android.HostAndDeviceSupported)
 		return module
@@ -639,7 +666,9 @@
 func LibraryHostFactory() android.Module {
 	module := &Library{}
 
-	module.AddProperties(&module.Module.properties)
+	module.AddProperties(
+		&module.Module.properties,
+		&module.Module.protoProperties)
 
 	InitJavaModule(module, android.HostSupported)
 	return module
@@ -685,6 +714,7 @@
 	module.AddProperties(
 		&module.Module.properties,
 		&module.Module.deviceProperties,
+		&module.Module.protoProperties,
 		&module.binaryProperties)
 
 	InitJavaModule(module, android.HostAndDeviceSupported)
@@ -697,6 +727,7 @@
 	module.AddProperties(
 		&module.Module.properties,
 		&module.Module.deviceProperties,
+		&module.Module.protoProperties,
 		&module.binaryProperties)
 
 	InitJavaModule(module, android.HostSupported)
diff --git a/java/proto.go b/java/proto.go
new file mode 100644
index 0000000..324868a
--- /dev/null
+++ b/java/proto.go
@@ -0,0 +1,98 @@
+// Copyright 2017 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 (
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+func init() {
+	pctx.HostBinToolVariable("protocCmd", "aprotoc")
+}
+
+var (
+	proto = pctx.AndroidStaticRule("protoc",
+		blueprint.RuleParams{
+			Command: `rm -rf $outDir && mkdir -p $outDir && ` +
+				`$protocCmd $protoOut=$protoOutFlags:$outDir $protoFlags $in && ` +
+				`find $outDir -name "*.java" > $out`,
+			CommandDeps: []string{"$protocCmd"},
+		}, "protoFlags", "protoOut", "protoOutFlags", "outDir")
+)
+
+func genProto(ctx android.ModuleContext, protoFiles android.Paths,
+	protoFlags string, protoOut, protoOutFlags string) android.WritablePath {
+
+	protoFileList := android.PathForModuleGen(ctx, "proto.filelist")
+
+	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
+		Rule:        proto,
+		Description: "protoc " + protoFiles[0].Rel(),
+		Output:      protoFileList,
+		Inputs:      protoFiles,
+		Args: map[string]string{
+			"outDir":        android.ProtoDir(ctx).String(),
+			"protoOut":      protoOut,
+			"protoOutFlags": protoOutFlags,
+			"protoFlags":    protoFlags,
+		},
+	})
+
+	return protoFileList
+}
+
+func protoDeps(ctx android.BottomUpMutatorContext, p *android.ProtoProperties) {
+	switch proptools.String(p.Proto.Type) {
+	case "micro":
+		ctx.AddDependency(ctx.Module(), staticLibTag, "libprotobuf-java-micro")
+	case "nano":
+		ctx.AddDependency(ctx.Module(), staticLibTag, "libprotobuf-java-nano")
+	case "stream":
+		// TODO(ccross): add dependency on protoc-gen-java-stream binary
+		ctx.PropertyErrorf("proto.type", `"stream" not supported yet`)
+		// No library for stream protobufs
+	case "lite", "":
+		ctx.AddDependency(ctx.Module(), staticLibTag, "libprotobuf-java-lite")
+	case "full":
+		if ctx.Host() {
+			ctx.AddDependency(ctx.Module(), staticLibTag, "libprotobuf-java-full")
+		} else {
+			ctx.PropertyErrorf("proto.type", "full java protos only supported on the host")
+		}
+	default:
+		ctx.PropertyErrorf("proto.type", "unknown proto type %q",
+			proptools.String(p.Proto.Type))
+	}
+}
+
+func protoFlags(ctx android.ModuleContext, p *android.ProtoProperties, flags javaBuilderFlags) javaBuilderFlags {
+	switch proptools.String(p.Proto.Type) {
+	case "micro":
+		flags.protoOutFlag = "--javamicro_out"
+	case "nano":
+		flags.protoOutFlag = "--javanano_out"
+	case "stream":
+		flags.protoOutFlag = "--javastream_out"
+	case "lite", "":
+		flags.protoOutFlag = "--java_out"
+	default:
+		ctx.PropertyErrorf("proto.type", "unknown proto type %q",
+			proptools.String(p.Proto.Type))
+	}
+	return flags
+}