Support java libraries, binaries, and prebuilts

Add support for compiling java libraries (.jar files with
or without .dex), java binaries (.jar files with a wrapper
script to run them), and java prebuilts (for the SDK .jars)

Change-Id: Id624da64c92cf20c6d9577c6bb06e5b212af0d1b
This commit is contained in:
Colin Cross
2015-03-30 17:20:39 -07:00
parent 35cec12a11
commit 2fe6687847
11 changed files with 996 additions and 18 deletions

189
java/builder.go Normal file
View File

@@ -0,0 +1,189 @@
// Copyright 2015 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
// This file generates the final rules for compiling all Java. All properties related to
// compiling should have been translated into javaBuilderFlags or another argument to the Transform*
// functions.
import (
"path/filepath"
"strings"
"android/soong/common"
"github.com/google/blueprint"
"github.com/google/blueprint/bootstrap"
)
var (
pctx = blueprint.NewPackageContext("android/soong/java")
// Compiling java is not conducive to proper dependency tracking. The path-matches-class-name
// requirement leads to unpredictable generated source file names, and a single .java file
// will get compiled into multiple .class files if it contains inner classes. To work around
// this, all java rules write into separate directories and then a post-processing step lists
// the files in the the directory into a list file that later rules depend on (and sometimes
// read from directly using @<listfile>)
cc = pctx.StaticRule("javac",
blueprint.RuleParams{
Command: `$javacCmd -encoding UTF-8 $javacFlags $bootClasspath $classpath ` +
`-extdirs "" -d $outDir @$out.rsp || ( rm -rf $outDir; exit 41 ) && ` +
`find $outDir -name "*.class" > $out`,
Rspfile: "$out.rsp",
RspfileContent: "$in",
Description: "javac $outDir",
},
"javacCmd", "javacFlags", "bootClasspath", "classpath", "outDir")
jar = pctx.StaticRule("jar",
blueprint.RuleParams{
Command: `$jarCmd -o $out $jarArgs`,
Description: "jar $out",
},
"jarCmd", "jarArgs")
dx = pctx.StaticRule("dx",
blueprint.RuleParams{
Command: "$dxCmd --dex --output=$out $dxFlags $in",
Description: "dex $out",
},
"outDir", "dxFlags")
)
func init() {
pctx.StaticVariable("commonJdkFlags", "-source 1.7 -target 1.7 -Xmaxerrs 9999999")
pctx.StaticVariable("javacCmd", "javac -J-Xmx1024M $commonJdkFlags")
pctx.StaticVariable("jarCmd", filepath.Join(bootstrap.BinDir, "soong_jar"))
pctx.VariableFunc("dxCmd", func(c interface{}) (string, error) {
return c.(Config).HostBinTool("dx")
})
}
type javaBuilderFlags struct {
javacFlags string
dxFlags string
bootClasspath string
classpath string
}
type jarSpec struct {
fileList, dir string
}
func (j jarSpec) soongJarArgs() string {
return "-C " + j.dir + " -l " + j.fileList
}
func TransformJavaToClasses(ctx common.AndroidModuleContext, srcFiles []string,
flags javaBuilderFlags, deps []string) jarSpec {
classDir := filepath.Join(common.ModuleOutDir(ctx), "classes")
classFileList := filepath.Join(classDir, "classes.list")
ctx.Build(pctx, blueprint.BuildParams{
Rule: cc,
Outputs: []string{classFileList},
Inputs: srcFiles,
Implicits: deps,
Args: map[string]string{
"javacFlags": flags.javacFlags,
"bootClasspath": flags.bootClasspath,
"classpath": flags.classpath,
"outDir": classDir,
},
})
return jarSpec{classFileList, classDir}
}
func TransformClassesToJar(ctx common.AndroidModuleContext, classes []jarSpec,
manifest string) string {
outputFile := filepath.Join(common.ModuleOutDir(ctx), "classes-full-debug.jar")
deps := []string{}
jarArgs := []string{}
for _, j := range classes {
deps = append(deps, j.fileList)
jarArgs = append(jarArgs, j.soongJarArgs())
}
if manifest != "" {
deps = append(deps, manifest)
jarArgs = append(jarArgs, "-m "+manifest)
}
deps = append(deps, "$jarCmd")
ctx.Build(pctx, blueprint.BuildParams{
Rule: jar,
Outputs: []string{outputFile},
Implicits: deps,
Args: map[string]string{
"jarArgs": strings.Join(jarArgs, " "),
},
})
return outputFile
}
func TransformClassesJarToDex(ctx common.AndroidModuleContext, classesJar string,
flags javaBuilderFlags) string {
outputFile := filepath.Join(common.ModuleOutDir(ctx), "classes.dex")
ctx.Build(pctx, blueprint.BuildParams{
Rule: dx,
Outputs: []string{outputFile},
Inputs: []string{classesJar},
Implicits: []string{"$dxCmd"},
Args: map[string]string{
"dxFlags": flags.dxFlags,
},
})
return outputFile
}
func TransformDexToJavaLib(ctx common.AndroidModuleContext, resources []jarSpec,
dexFile string) string {
outputFile := filepath.Join(common.ModuleOutDir(ctx), "javalib.jar")
var deps []string
var jarArgs []string
for _, j := range resources {
deps = append(deps, j.fileList)
jarArgs = append(jarArgs, j.soongJarArgs())
}
dexDir, _ := filepath.Split(dexFile)
jarArgs = append(jarArgs, "-C "+dexDir+" -f "+dexFile)
deps = append(deps, "$jarCmd", dexFile)
ctx.Build(pctx, blueprint.BuildParams{
Rule: jar,
Outputs: []string{outputFile},
Implicits: deps,
Args: map[string]string{
"jarArgs": strings.Join(jarArgs, " "),
},
})
return outputFile
}

403
java/java.go Normal file
View File

@@ -0,0 +1,403 @@
// Copyright 2015 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
// This file contains the module types for compiling Java for Android, and converts the properties
// into the flags and filenames necessary to pass to the compiler. The final creation of the rules
// is handled in builder.go
import (
"fmt"
"path/filepath"
"strings"
"github.com/google/blueprint"
"github.com/google/blueprint/pathtools"
"android/soong/common"
)
type Config interface {
SrcDir() string
PrebuiltOS() string
HostBinTool(string) (string, error)
Getenv(string) string
}
// TODO:
// Autogenerated files:
// AIDL
// Proto
// Renderscript
// Post-jar passes:
// Proguard
// Emma
// Jarjar
// Dex
// Rmtypedefs
// Jack
// DroidDoc
// Findbugs
// javaBase contains the properties and members used by all java module types, and implements
// the blueprint.Module interface.
type javaBase struct {
common.AndroidModuleBase
module JavaModuleType
properties struct {
// srcs: list of source files used to compile the Java module. May be .java, .logtags, .proto,
// or .aidl files.
Srcs []string `android:"arch_variant,arch_subtract"`
// resource_dirs: list of directories containing resources
Resource_dirs []string `android:"arch_variant"`
// no_standard_libraries: don't build against the default libraries (core-libart, core-junit,
// ext, and framework for device targets)
No_standard_libraries bool
// javacflags: list of module-specific flags that will be used for javac compiles
Javacflags []string `android:"arch_variant"`
// dxflags: list of module-specific flags that will be used for dex compiles
Dxflags []string `android:"arch_variant"`
// java_libs: list of of java libraries that will be in the classpath
Java_libs []string `android:"arch_variant"`
// java_static_libs: list of java libraries that will be compiled into the resulting jar
Java_static_libs []string `android:"arch_variant"`
// manifest: manifest file to be included in resulting jar
Manifest string
// sdk_version: if not blank, set to the version of the sdk to compile against
Sdk_version string
// Set for device java libraries, and for host versions of device java libraries
// built for testing
Dex bool `blueprint:"mutated"`
}
// output file suitable for inserting into the classpath of another compile
classpathFile string
// jarSpecs suitable for inserting classes from a static library into another jar
classJarSpecs []jarSpec
// jarSpecs suitable for inserting resources from a static library into another jar
resourceJarSpecs []jarSpec
// installed file for binary dependency
installFile string
}
type JavaModuleType interface {
GenerateJavaBuildActions(ctx common.AndroidModuleContext)
}
type JavaDependency interface {
ClasspathFile() string
ClassJarSpecs() []jarSpec
ResourceJarSpecs() []jarSpec
}
func NewJavaBase(base *javaBase, module JavaModuleType, hod common.HostOrDeviceSupported,
props ...interface{}) (blueprint.Module, []interface{}) {
base.module = module
props = append(props, &base.properties)
return common.InitAndroidArchModule(base, hod, common.MultilibCommon, props...)
}
func (j *javaBase) BootClasspath(ctx common.AndroidBaseContext) string {
if ctx.Device() {
if j.properties.Sdk_version == "" {
return "core-libart"
} else if j.properties.Sdk_version == "current" {
// TODO: !TARGET_BUILD_APPS
return "android_stubs_current"
} else if j.properties.Sdk_version == "system_current" {
return "android_system_stubs_current"
} else {
return "sdk_v" + j.properties.Sdk_version
}
} else {
if j.properties.Dex {
return "core-libart"
} else {
return ""
}
}
}
func (j *javaBase) AndroidDynamicDependencies(ctx common.AndroidDynamicDependerModuleContext) []string {
var deps []string
if !j.properties.No_standard_libraries {
bootClasspath := j.BootClasspath(ctx)
if bootClasspath != "" {
deps = append(deps, bootClasspath)
}
}
deps = append(deps, j.properties.Java_libs...)
deps = append(deps, j.properties.Java_static_libs...)
return deps
}
func (j *javaBase) collectDeps(ctx common.AndroidModuleContext) (classpath []string,
bootClasspath string, classJarSpecs, resourceJarSpecs []jarSpec) {
ctx.VisitDirectDeps(func(module blueprint.Module) {
otherName := ctx.OtherModuleName(module)
if javaDep, ok := module.(JavaDependency); ok {
if inList(otherName, j.properties.Java_libs) {
classpath = append(classpath, javaDep.ClasspathFile())
} else if inList(otherName, j.properties.Java_static_libs) {
classpath = append(classpath, javaDep.ClasspathFile())
classJarSpecs = append(classJarSpecs, javaDep.ClassJarSpecs()...)
resourceJarSpecs = append(resourceJarSpecs, javaDep.ResourceJarSpecs()...)
} else if otherName == j.BootClasspath(ctx) {
bootClasspath = javaDep.ClasspathFile()
} else {
panic(fmt.Errorf("unknown dependency %q for %q", otherName, ctx.ModuleName()))
}
} else {
ctx.ModuleErrorf("unknown dependency module type for %q", otherName)
}
})
return classpath, bootClasspath, classJarSpecs, resourceJarSpecs
}
func (j *javaBase) GenerateAndroidBuildActions(ctx common.AndroidModuleContext) {
j.module.GenerateJavaBuildActions(ctx)
}
func (j *javaBase) GenerateJavaBuildActions(ctx common.AndroidModuleContext) {
flags := javaBuilderFlags{
javacFlags: strings.Join(j.properties.Javacflags, " "),
}
var javacDeps []string
srcFiles := j.properties.Srcs
srcFiles = pathtools.PrefixPaths(srcFiles, common.ModuleSrcDir(ctx))
srcFiles = common.ExpandGlobs(ctx, srcFiles)
classpath, bootClasspath, classJarSpecs, resourceJarSpecs := j.collectDeps(ctx)
if bootClasspath != "" {
flags.bootClasspath = "-bootclasspath " + bootClasspath
javacDeps = append(javacDeps, bootClasspath)
}
if len(classpath) > 0 {
flags.classpath = "-classpath " + strings.Join(classpath, ":")
javacDeps = append(javacDeps, classpath...)
}
// Compile java sources into .class files
classes := TransformJavaToClasses(ctx, srcFiles, flags, javacDeps)
if ctx.Failed() {
return
}
resourceJarSpecs = append(ResourceDirsToJarSpecs(ctx, j.properties.Resource_dirs), resourceJarSpecs...)
classJarSpecs = append([]jarSpec{classes}, classJarSpecs...)
manifest := j.properties.Manifest
if manifest != "" {
manifest = filepath.Join(common.ModuleSrcDir(ctx), manifest)
}
allJarSpecs := append([]jarSpec(nil), classJarSpecs...)
allJarSpecs = append(allJarSpecs, resourceJarSpecs...)
// Combine classes + resources into classes-full-debug.jar
outputFile := TransformClassesToJar(ctx, allJarSpecs, manifest)
if ctx.Failed() {
return
}
j.classJarSpecs = classJarSpecs
j.resourceJarSpecs = resourceJarSpecs
j.classpathFile = outputFile
if j.properties.Dex {
dxFlags := j.properties.Dxflags
if false /* emma enabled */ {
// If you instrument class files that have local variable debug information in
// them emma does not correctly maintain the local variable table.
// This will cause an error when you try to convert the class files for Android.
// The workaround here is to build different dex file here based on emma switch
// then later copy into classes.dex. When emma is on, dx is run with --no-locals
// option to remove local variable information
dxFlags = append(dxFlags, "--no-locals")
}
if ctx.Config().(Config).Getenv("NO_OPTIMIZE_DX") != "" {
dxFlags = append(dxFlags, "--no-optimize")
}
if ctx.Config().(Config).Getenv("GENERATE_DEX_DEBUG") != "" {
dxFlags = append(dxFlags,
"--debug",
"--verbose",
"--dump-to="+filepath.Join(common.ModuleOutDir(ctx), "classes.lst"),
"--dump-width=1000")
}
flags.dxFlags = strings.Join(dxFlags, " ")
// Compile classes.jar into classes.dex
dexFile := TransformClassesJarToDex(ctx, outputFile, flags)
if ctx.Failed() {
return
}
// Combine classes.dex + resources into javalib.jar
outputFile = TransformDexToJavaLib(ctx, resourceJarSpecs, dexFile)
}
j.installFile = ctx.InstallFileName("framework", ctx.ModuleName()+".jar", outputFile)
}
var _ JavaDependency = (*JavaLibrary)(nil)
func (j *javaBase) ClasspathFile() string {
return j.classpathFile
}
func (j *javaBase) ClassJarSpecs() []jarSpec {
return j.classJarSpecs
}
func (j *javaBase) ResourceJarSpecs() []jarSpec {
return j.resourceJarSpecs
}
//
// Java libraries (.jar file)
//
type JavaLibrary struct {
javaBase
}
func JavaLibraryFactory() (blueprint.Module, []interface{}) {
module := &JavaLibrary{}
module.properties.Dex = true
return NewJavaBase(&module.javaBase, module, common.HostAndDeviceSupported)
}
func JavaLibraryHostFactory() (blueprint.Module, []interface{}) {
module := &JavaLibrary{}
return NewJavaBase(&module.javaBase, module, common.HostSupported)
}
//
// Java Binaries (.jar file plus wrapper script)
//
type JavaBinary struct {
JavaLibrary
binaryProperties struct {
// wrapper: installable script to execute the resulting jar
Wrapper string
}
}
func (j *JavaBinary) GenerateJavaBuildActions(ctx common.AndroidModuleContext) {
j.JavaLibrary.GenerateJavaBuildActions(ctx)
// Depend on the installed jar (j.installFile) so that the wrapper doesn't get executed by
// another build rule before the jar has been installed.
ctx.InstallFile("bin", filepath.Join(common.ModuleSrcDir(ctx), j.binaryProperties.Wrapper),
j.installFile)
}
func JavaBinaryFactory() (blueprint.Module, []interface{}) {
module := &JavaBinary{}
module.properties.Dex = true
return NewJavaBase(&module.javaBase, module, common.HostAndDeviceSupported, &module.binaryProperties)
}
func JavaBinaryHostFactory() (blueprint.Module, []interface{}) {
module := &JavaBinary{}
return NewJavaBase(&module.javaBase, module, common.HostSupported, &module.binaryProperties)
}
//
// Java prebuilts
//
type JavaPrebuilt struct {
common.AndroidModuleBase
properties struct {
Srcs []string
}
classpathFile string
}
func (j *JavaPrebuilt) GenerateAndroidBuildActions(ctx common.AndroidModuleContext) {
if len(j.properties.Srcs) != 1 {
ctx.ModuleErrorf("expected exactly one jar in srcs")
return
}
j.classpathFile = filepath.Join(common.ModuleSrcDir(ctx), j.properties.Srcs[0])
}
var _ JavaDependency = (*JavaPrebuilt)(nil)
func (j *JavaPrebuilt) ClasspathFile() string {
return j.classpathFile
}
func (j *JavaPrebuilt) ClassJarSpecs() []jarSpec {
return nil
}
func (j *JavaPrebuilt) ResourceJarSpecs() []jarSpec {
return nil
}
func JavaPrebuiltFactory() (blueprint.Module, []interface{}) {
module := &JavaPrebuilt{}
return common.InitAndroidArchModule(module, common.HostAndDeviceSupported,
common.MultilibCommon, &module.properties)
}
func inList(s string, l []string) bool {
for _, e := range l {
if e == s {
return true
}
}
return false
}

45
java/resources.go Normal file
View File

@@ -0,0 +1,45 @@
// Copyright 2015 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 (
"path/filepath"
"android/soong/common"
)
var resourceExcludes = []string{
"*.java",
"package.html",
"overview.html",
".*.swp",
".DS_Store",
"*~",
}
func ResourceDirsToJarSpecs(ctx common.AndroidModuleContext, dirs []string) []jarSpec {
jarSpecs := make([]jarSpec, len(dirs))
for i, dir := range dirs {
fileListFile := filepath.Join(common.ModuleOutDir(ctx), "res", dir, "resources.list")
depFile := fileListFile + ".d"
dir := filepath.Join(common.ModuleSrcDir(ctx), dir)
glob := filepath.Join(dir, "**/*")
common.GlobRule(ctx, glob, resourceExcludes, fileListFile, depFile)
jarSpecs[i] = jarSpec{fileListFile, dir}
}
return jarSpecs
}