diff --git a/Android.bp b/Android.bp index ae31a60a8..1153aac60 100644 --- a/Android.bp +++ b/Android.bp @@ -211,6 +211,7 @@ bootstrap_go_package { "java/java.go", "java/proto.go", "java/resources.go", + "java/system_modules.go", ], testSrcs: [ "java/java_test.go", diff --git a/android/config.go b/android/config.go index 5abda26fc..dca998f5a 100644 --- a/android/config.go +++ b/android/config.go @@ -88,6 +88,9 @@ type config struct { captureBuild bool // true for tests, saves build parameters for each module ignoreEnvironment bool // true for tests, returns empty from all Getenv calls + useOpenJDK9 bool // Use OpenJDK9, but possibly target 1.8 + targetOpenJDK9 bool // Use OpenJDK9 and target 1.9 + OncePer } @@ -183,6 +186,10 @@ func TestConfig(buildDir string, env map[string]string) Config { config: config, } + if err := config.fromEnv(); err != nil { + panic(err) + } + return Config{config} } @@ -273,9 +280,31 @@ func NewConfig(srcDir, buildDir string) (Config, error) { config.Targets = targets config.BuildOsVariant = targets[Host][0].String() + if err := config.fromEnv(); err != nil { + return Config{}, err + } + return Config{config}, nil } +func (c *config) fromEnv() error { + switch c.Getenv("EXPERIMENTAL_USE_OPENJDK9") { + case "": + // Use OpenJDK8 + case "1.8": + // Use OpenJDK9, but target 1.8 + c.useOpenJDK9 = true + case "true": + // Use OpenJDK9 and target 1.9 + c.useOpenJDK9 = true + c.targetOpenJDK9 = true + default: + return fmt.Errorf(`Invalid value for EXPERIMENTAL_USE_OPENJDK9, should be "", "1.8", or "true"`) + } + + return nil +} + func (c *config) RemoveAbandonedFiles() bool { return false } @@ -518,6 +547,16 @@ func (c *config) UseGoma() bool { return Bool(c.ProductVariables.UseGoma) } +// Returns true if OpenJDK9 prebuilts are being used +func (c *config) UseOpenJDK9() bool { + return c.useOpenJDK9 +} + +// Returns true if -source 1.9 -target 1.9 is being passed to javac +func (c *config) TargetOpenJDK9() bool { + return c.targetOpenJDK9 +} + func (c *config) ClangTidy() bool { return Bool(c.ProductVariables.ClangTidy) } diff --git a/java/builder.go b/java/builder.go index 118f23979..8992f681a 100644 --- a/java/builder.go +++ b/java/builder.go @@ -126,6 +126,7 @@ type javaBuilderFlags struct { dxFlags string bootClasspath classpath classpath classpath + systemModules classpath desugarFlags string aidlFlags string javaVersion string @@ -177,7 +178,16 @@ func transformJavaToClasses(ctx android.ModuleContext, srcFiles, srcFileLists an } deps = append(deps, srcFileLists...) - deps = append(deps, flags.bootClasspath...) + + var bootClasspath string + if flags.javaVersion == "1.9" { + deps = append(deps, flags.systemModules...) + bootClasspath = flags.systemModules.JavaSystemModules(ctx.Device()) + } else { + deps = append(deps, flags.bootClasspath...) + bootClasspath = flags.bootClasspath.JavaBootClasspath(ctx.Device()) + } + deps = append(deps, flags.classpath...) ctx.ModuleBuild(pctx, android.ModuleBuildParams{ @@ -188,7 +198,7 @@ func transformJavaToClasses(ctx android.ModuleContext, srcFiles, srcFileLists an Implicits: deps, Args: map[string]string{ "javacFlags": javacFlags, - "bootClasspath": flags.bootClasspath.JavaBootClasspath(ctx.Device()), + "bootClasspath": bootClasspath, "classpath": flags.classpath.JavaClasspath(), "outDir": android.PathForModuleOut(ctx, "classes"+suffix).String(), "annoDir": android.PathForModuleOut(ctx, "anno"+suffix).String(), @@ -259,7 +269,7 @@ func TransformDesugar(ctx android.ModuleContext, classesJar android.Path, dumpDir := android.PathForModuleOut(ctx, "desugar_dumped_classes") javaFlags := "" - if ctx.AConfig().Getenv("EXPERIMENTAL_USE_OPENJDK9") != "" { + if ctx.AConfig().UseOpenJDK9() { javaFlags = "--add-opens java.base/java.lang.invoke=ALL-UNNAMED" } @@ -359,6 +369,21 @@ func (x *classpath) JavaBootClasspath(forceEmpty bool) string { } } +// Returns a --system argument in the form javac expects with -source 1.9. If forceEmpty is true, +// returns --system=none if the list is empty to ensure javac does not fall back to the default +// system modules. +func (x *classpath) JavaSystemModules(forceEmpty bool) string { + if len(*x) > 1 { + panic("more than one system module") + } else if len(*x) == 1 { + return "--system=" + strings.TrimSuffix((*x)[0].String(), "lib/modules") + } else if forceEmpty { + return "--system=none" + } else { + return "" + } +} + func (x *classpath) DesugarBootClasspath() []string { if x == nil || *x == nil { return nil diff --git a/java/config/config.go b/java/config/config.go index 70b8fe52e..7d1fa29bf 100644 --- a/java/config/config.go +++ b/java/config/config.go @@ -27,6 +27,7 @@ var ( pctx = android.NewPackageContext("android/soong/java/config") DefaultBootclasspathLibraries = []string{"core-oj", "core-libart"} + DefaultSystemModules = "core-system-modules" DefaultLibraries = []string{"ext", "framework", "okhttp"} ) @@ -47,9 +48,10 @@ func init() { // If a different javac is used the flag will be ignored and extra bridges will be inserted. // The flag is implemented by https://android-review.googlesource.com/c/486427 `-XDskipDuplicateBridges=true`, - }, " ")) - pctx.StaticVariable("DefaultJavaVersion", "1.8") + // b/65004097: prevent using java.lang.invoke.StringConcatFactory when using -target 1.9 + `-XDstringConcat=inline`, + }, " ")) pctx.VariableConfigMethod("hostPrebuiltTag", android.Config.PrebuiltOS) @@ -57,7 +59,7 @@ func init() { if override := config.(android.Config).Getenv("OVERRIDE_ANDROID_JAVA_HOME"); override != "" { return override, nil } - if jdk9 := config.(android.Config).Getenv("EXPERIMENTAL_USE_OPENJDK9"); jdk9 != "" { + if config.(android.Config).UseOpenJDK9() { return "prebuilts/jdk/jdk9/${hostPrebuiltTag}", nil } return "prebuilts/jdk/jdk8/${hostPrebuiltTag}", nil @@ -71,6 +73,7 @@ func init() { pctx.SourcePathVariable("JavadocCmd", "${JavaToolchain}/javadoc") pctx.SourcePathVariable("JlinkCmd", "${JavaToolchain}/jlink") pctx.SourcePathVariable("JmodCmd", "${JavaToolchain}/jmod") + pctx.SourcePathVariable("JrtFsJar", "${JavaHome}/lib/jrt-fs.jar") pctx.SourcePathVariable("JarArgsCmd", "build/soong/scripts/jar-args.sh") pctx.StaticVariable("SoongZipCmd", filepath.Join("${bootstrap.ToolDir}", "soong_zip")) @@ -86,17 +89,3 @@ func init() { return "", nil }) } - -func StripJavac9Flags(flags []string) []string { - var ret []string - for _, f := range flags { - switch { - case strings.HasPrefix(f, "-J--add-modules="): - // drop - default: - ret = append(ret, f) - } - } - - return ret -} diff --git a/java/config/makevars.go b/java/config/makevars.go index 937d597a4..1453a0784 100644 --- a/java/config/makevars.go +++ b/java/config/makevars.go @@ -27,8 +27,13 @@ func init() { func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("TARGET_DEFAULT_JAVA_LIBRARIES", strings.Join(DefaultLibraries, " ")) ctx.Strict("TARGET_DEFAULT_BOOTCLASSPATH_LIBRARIES", strings.Join(DefaultBootclasspathLibraries, " ")) + ctx.Strict("DEFAULT_SYSTEM_MODULES", DefaultSystemModules) - ctx.Strict("DEFAULT_JAVA_LANGUAGE_VERSION", "${DefaultJavaVersion}") + if ctx.Config().TargetOpenJDK9() { + ctx.Strict("DEFAULT_JAVA_LANGUAGE_VERSION", "1.9") + } else { + ctx.Strict("DEFAULT_JAVA_LANGUAGE_VERSION", "1.8") + } ctx.Strict("ANDROID_JAVA_HOME", "${JavaHome}") ctx.Strict("ANDROID_JAVA_TOOLCHAIN", "${JavaToolchain}") @@ -47,7 +52,7 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("HOST_JAVAC", "${JavacCmd} ${CommonJdkFlags}") } - if ctx.Config().IsEnvTrue("EXPERIMENTAL_USE_OPENJDK9") { + if ctx.Config().UseOpenJDK9() { ctx.Strict("JLINK", "${JlinkCmd}") ctx.Strict("JMOD", "${JmodCmd}") } diff --git a/java/java.go b/java/java.go index 5393b065f..c3b7557fd 100644 --- a/java/java.go +++ b/java/java.go @@ -116,6 +116,14 @@ type CompilerProperties struct { // List of classes to pass to javac to use as annotation processors Annotation_processor_classes []string + + Openjdk9 struct { + // List of source files that should only be used when passing -source 1.9 + Srcs []string + + // List of javac flags that should only be used when passing -source 1.9 + Javacflags []string + } } type CompilerDeviceProperties struct { @@ -134,6 +142,9 @@ type CompilerDeviceProperties struct { // If true, export a copy of the module as a -hostdex module for host testing. Hostdex *bool + + // When targeting 1.9, override the modules to use with --system + System_modules *string } // Module contains the properties and members used by all java module types @@ -185,26 +196,39 @@ var ( staticLibTag = dependencyTag{name: "staticlib"} libTag = dependencyTag{name: "javalib"} bootClasspathTag = dependencyTag{name: "bootclasspath"} + systemModulesTag = dependencyTag{name: "system modules"} frameworkResTag = dependencyTag{name: "framework-res"} ) type sdkDep struct { useModule, useFiles, useDefaultLibs, invalidVersion bool - module string - jar android.Path - aidl android.Path + module string + systemModules string + + jar android.Path + aidl android.Path +} + +func sdkStringToNumber(ctx android.BaseContext, v string) int { + switch v { + case "", "current", "system_current", "test_current": + return 10000 + default: + if i, err := strconv.Atoi(v); err != nil { + ctx.PropertyErrorf("sdk_version", "invalid sdk version") + return -1 + } else { + return i + } + } } func decodeSdkDep(ctx android.BaseContext, v string) sdkDep { - switch v { - case "", "current", "system_current", "test_current": - // OK - default: - if _, err := strconv.Atoi(v); err != nil { - ctx.PropertyErrorf("sdk_version", "invalid sdk version") - return sdkDep{} - } + i := sdkStringToNumber(ctx, v) + if i == -1 { + // Invalid sdk version, error handled by sdkStringToNumber. + return sdkDep{} } toFile := func(v string) sdkDep { @@ -240,8 +264,9 @@ func decodeSdkDep(ctx android.BaseContext, v string) sdkDep { toModule := func(m string) sdkDep { return sdkDep{ - useModule: true, - module: m, + useModule: true, + module: m, + systemModules: m + "_system_modules", } } @@ -266,20 +291,31 @@ func decodeSdkDep(ctx android.BaseContext, v string) sdkDep { } func (j *Module) deps(ctx android.BottomUpMutatorContext) { - if !proptools.Bool(j.properties.No_standard_libs) { - if ctx.Device() { + if ctx.Device() { + if !proptools.Bool(j.properties.No_standard_libs) { sdkDep := decodeSdkDep(ctx, j.deviceProperties.Sdk_version) if sdkDep.useDefaultLibs { ctx.AddDependency(ctx.Module(), bootClasspathTag, config.DefaultBootclasspathLibraries...) + if ctx.AConfig().TargetOpenJDK9() { + ctx.AddDependency(ctx.Module(), systemModulesTag, config.DefaultSystemModules) + } if !proptools.Bool(j.properties.No_framework_libs) { ctx.AddDependency(ctx.Module(), libTag, config.DefaultLibraries...) } - } - if sdkDep.useModule { + } else if sdkDep.useModule { + if ctx.AConfig().TargetOpenJDK9() { + ctx.AddDependency(ctx.Module(), systemModulesTag, sdkDep.systemModules) + } ctx.AddDependency(ctx.Module(), bootClasspathTag, sdkDep.module) } + } else if j.deviceProperties.System_modules == nil { + ctx.PropertyErrorf("no_standard_libs", + "system_modules is required to be set when no_standard_libs is true, did you mean no_framework_libs?") + } else if *j.deviceProperties.System_modules != "none" && ctx.AConfig().TargetOpenJDK9() { + ctx.AddDependency(ctx.Module(), systemModulesTag, *j.deviceProperties.System_modules) } } + ctx.AddDependency(ctx.Module(), libTag, j.properties.Libs...) ctx.AddDependency(ctx.Module(), staticLibTag, j.properties.Static_libs...) ctx.AddDependency(ctx.Module(), libTag, j.properties.Annotation_processors...) @@ -335,6 +371,7 @@ type deps struct { staticJarResources android.Paths aidlIncludeDirs android.Paths srcFileLists android.Paths + systemModules android.Path aidlPreprocess android.OptionalPath } @@ -358,6 +395,15 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { switch tag { case android.DefaultsDepTag, android.SourceDepTag: // Nothing to do + case systemModulesTag: + if deps.systemModules != nil { + panic("Found two system module dependencies") + } + sm := module.(*SystemModules) + if sm.outputFile == nil { + panic("Missing directory for system module dependency") + } + deps.systemModules = sm.outputFile default: ctx.ModuleErrorf("depends on non-java module %q", otherName) } @@ -397,19 +443,29 @@ func (j *Module) compile(ctx android.ModuleContext) { var flags javaBuilderFlags javacFlags := j.properties.Javacflags - if ctx.AConfig().Getenv("EXPERIMENTAL_USE_OPENJDK9") == "" { - javacFlags = config.StripJavac9Flags(javacFlags) + if ctx.AConfig().TargetOpenJDK9() { + javacFlags = append(javacFlags, j.properties.Openjdk9.Javacflags...) + j.properties.Srcs = append(j.properties.Srcs, j.properties.Openjdk9.Srcs...) } + sdk := sdkStringToNumber(ctx, j.deviceProperties.Sdk_version) if j.properties.Java_version != nil { flags.javaVersion = *j.properties.Java_version + } else if ctx.Device() && sdk <= 23 { + flags.javaVersion = "1.7" + } else if ctx.Device() && sdk <= 26 || !ctx.AConfig().TargetOpenJDK9() { + flags.javaVersion = "1.8" } else { - flags.javaVersion = "${config.DefaultJavaVersion}" + flags.javaVersion = "1.9" } flags.bootClasspath.AddPaths(deps.bootClasspath) flags.classpath.AddPaths(deps.classpath) + if deps.systemModules != nil { + flags.systemModules = append(flags.systemModules, deps.systemModules) + } + if len(javacFlags) > 0 { ctx.Variable(pctx, "javacFlags", strings.Join(javacFlags, " ")) flags.javacFlags = "$javacFlags" diff --git a/java/java_test.go b/java/java_test.go index a86973dd0..472931317 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -50,9 +50,12 @@ func TestMain(m *testing.M) { os.Exit(run()) } - func testJava(t *testing.T, bp string) *android.TestContext { - config := android.TestArchConfig(buildDir, nil) + return testJavaWithEnv(t, bp, nil) +} + +func testJavaWithEnv(t *testing.T, bp string, env map[string]string) *android.TestContext { + config := android.TestArchConfig(buildDir, env) ctx := android.NewTestArchContext() ctx.RegisterModuleType("android_app", android.ModuleFactoryAdaptor(AndroidAppFactory)) @@ -60,6 +63,7 @@ func testJava(t *testing.T, bp string) *android.TestContext { ctx.RegisterModuleType("java_library_host", android.ModuleFactoryAdaptor(LibraryHostFactory)) ctx.RegisterModuleType("java_import", android.ModuleFactoryAdaptor(ImportFactory)) ctx.RegisterModuleType("java_defaults", android.ModuleFactoryAdaptor(defaultsFactory)) + ctx.RegisterModuleType("java_system_modules", android.ModuleFactoryAdaptor(SystemModulesFactory)) ctx.RegisterModuleType("filegroup", android.ModuleFactoryAdaptor(genrule.FileGroupFactory)) ctx.RegisterModuleType("genrule", android.ModuleFactoryAdaptor(genrule.GenRuleFactory)) ctx.PreArchMutators(android.RegisterPrebuiltsPreArchMutators) @@ -84,10 +88,28 @@ func testJava(t *testing.T, bp string) *android.TestContext { name: "%s", srcs: ["a.java"], no_standard_libs: true, + system_modules: "core-system-modules", } `, extra) } + if config.TargetOpenJDK9() { + systemModules := []string{ + "core-system-modules", + "android_stubs_current_system_modules", + "android_system_stubs_current_system_modules", + "android_test_stubs_current_system_modules", + } + + for _, extra := range systemModules { + bp += fmt.Sprintf(` + java_system_modules { + name: "%s", + } + `, extra) + } + } + ctx.MockFileSystem(map[string][]byte{ "Android.bp": []byte(bp), "a.java": nil, @@ -190,17 +212,20 @@ var classpathTestcases = []struct { host android.OsClass properties string bootclasspath []string + system string classpath []string }{ { name: "default", bootclasspath: []string{"core-oj", "core-libart"}, + system: "core-system-modules", classpath: []string{"ext", "framework", "okhttp"}, }, { name: "blank sdk version", properties: `sdk_version: "",`, bootclasspath: []string{"core-oj", "core-libart"}, + system: "core-system-modules", classpath: []string{"ext", "framework", "okhttp"}, }, { @@ -208,6 +233,7 @@ var classpathTestcases = []struct { name: "sdk v14", properties: `sdk_version: "14",`, bootclasspath: []string{`""`}, + system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath classpath: []string{"prebuilts/sdk/14/android.jar"}, }, { @@ -215,6 +241,7 @@ var classpathTestcases = []struct { name: "current", properties: `sdk_version: "current",`, bootclasspath: []string{"android_stubs_current"}, + system: "android_stubs_current_system_modules", classpath: []string{}, }, { @@ -222,6 +249,7 @@ var classpathTestcases = []struct { name: "system_current", properties: `sdk_version: "system_current",`, bootclasspath: []string{"android_system_stubs_current"}, + system: "android_system_stubs_current_system_modules", classpath: []string{}, }, { @@ -229,12 +257,22 @@ var classpathTestcases = []struct { name: "test_current", properties: `sdk_version: "test_current",`, bootclasspath: []string{"android_test_stubs_current"}, + system: "android_test_stubs_current_system_modules", classpath: []string{}, }, { name: "nostdlib", - properties: `no_standard_libs: true`, + properties: `no_standard_libs: true, system_modules: "none"`, + system: "none", + bootclasspath: []string{`""`}, + classpath: []string{}, + }, + { + + name: "nostdlib system_modules", + properties: `no_standard_libs: true, system_modules: "core-system-modules"`, + system: "core-system-modules", bootclasspath: []string{`""`}, classpath: []string{}, }, @@ -263,7 +301,7 @@ var classpathTestcases = []struct { { name: "host supported nostdlib", host: android.Host, - properties: `host_supported: true, no_standard_libs: true`, + properties: `host_supported: true, no_standard_libs: true, system_modules: "none"`, classpath: []string{}, }, } @@ -275,12 +313,17 @@ func TestClasspath(t *testing.T) { if testcase.moduleType != "" { moduleType = testcase.moduleType } - ctx := testJava(t, moduleType+` { + + bp := moduleType + ` { name: "foo", srcs: ["a.java"], - `+testcase.properties+` + ` + testcase.properties + ` + }` + + variant := "android_common" + if testcase.host == android.Host { + variant = android.BuildOs.String() + "_common" } - `) convertModulesToPaths := func(cp []string) []string { ret := make([]string, len(cp)) @@ -293,33 +336,63 @@ func TestClasspath(t *testing.T) { bootclasspath := convertModulesToPaths(testcase.bootclasspath) classpath := convertModulesToPaths(testcase.classpath) - variant := "android_common" - if testcase.host == android.Host { - variant = android.BuildOs.String() + "_common" - } - javac := ctx.ModuleForTests("foo", variant).Rule("javac") - - got := strings.TrimPrefix(javac.Args["bootClasspath"], "-bootclasspath ") bc := strings.Join(bootclasspath, ":") - if got != bc { - t.Errorf("bootclasspath expected %q != got %q", bc, got) + if bc != "" { + bc = "-bootclasspath " + bc } - got = strings.TrimPrefix(javac.Args["classpath"], "-classpath ") c := strings.Join(classpath, ":") - if got != c { - t.Errorf("classpath expected %q != got %q", c, got) + if c != "" { + c = "-classpath " + c + } + system := "" + if testcase.system == "none" { + system = "--system=none" + } else if testcase.system != "" { + system = "--system=" + filepath.Join(buildDir, ".intermediates", testcase.system, "android_common", "system") + "/" } - var deps []string - if len(bootclasspath) > 0 && bootclasspath[0] != `""` { - deps = append(deps, bootclasspath...) - } - deps = append(deps, classpath...) + t.Run("1.8", func(t *testing.T) { + // Test default javac 1.8 + ctx := testJava(t, bp) - if !reflect.DeepEqual(javac.Implicits.Strings(), deps) { - t.Errorf("implicits expected %q != got %q", deps, javac.Implicits.Strings()) - } + javac := ctx.ModuleForTests("foo", variant).Rule("javac") + + got := javac.Args["bootClasspath"] + if got != bc { + t.Errorf("bootclasspath expected %q != got %q", bc, got) + } + + got = javac.Args["classpath"] + if got != c { + t.Errorf("classpath expected %q != got %q", c, got) + } + + var deps []string + if len(bootclasspath) > 0 && bootclasspath[0] != `""` { + deps = append(deps, bootclasspath...) + } + deps = append(deps, classpath...) + + if !reflect.DeepEqual(javac.Implicits.Strings(), deps) { + t.Errorf("implicits expected %q != got %q", deps, javac.Implicits.Strings()) + } + }) + + // Test again with javac 1.9 + t.Run("1.9", func(t *testing.T) { + ctx := testJavaWithEnv(t, bp, map[string]string{"EXPERIMENTAL_USE_OPENJDK9": "true"}) + + javac := ctx.ModuleForTests("foo", variant).Rule("javac") + got := javac.Args["bootClasspath"] + expected := system + if testcase.system == "bootclasspath" { + expected = bc + } + if got != expected { + t.Errorf("bootclasspath expected %q != got %q", expected, got) + } + }) }) } diff --git a/java/system_modules.go b/java/system_modules.go new file mode 100644 index 000000000..ddfc5cfcb --- /dev/null +++ b/java/system_modules.go @@ -0,0 +1,144 @@ +// 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 ( + "fmt" + "io" + "strings" + + "github.com/google/blueprint" + + "android/soong/android" +) + +// OpenJDK 9 introduces the concept of "system modules", which replace the bootclasspath. This +// file will produce the rules necessary to convert each unique set of bootclasspath jars into +// system modules in a runtime image using the jmod and jlink tools. + +func init() { + android.RegisterModuleType("java_system_modules", SystemModulesFactory) + + pctx.SourcePathVariable("moduleInfoJavaPath", "build/soong/scripts/jars-to-module-info-java.sh") +} + +var ( + jarsTosystemModules = pctx.AndroidStaticRule("jarsTosystemModules", blueprint.RuleParams{ + Command: `rm -rf ${outDir} ${workDir} && mkdir -p ${workDir}/jmod && ` + + `${moduleInfoJavaPath} ${moduleName} $in > ${workDir}/module-info.java && ` + + `${config.JavacCmd} --system=none --patch-module=java.base=${classpath} ${workDir}/module-info.java && ` + + `${config.SoongZipCmd} -jar -o ${workDir}/classes.jar -C ${workDir} -f ${workDir}/module-info.class && ` + + `${config.MergeZipsCmd} -j ${workDir}/module.jar ${workDir}/classes.jar $in && ` + + `${config.JmodCmd} create --module-version 9 --target-platform android ` + + ` --class-path ${workDir}/module.jar ${workDir}/jmod/${moduleName}.jmod && ` + + `${config.JlinkCmd} --module-path ${workDir}/jmod --add-modules ${moduleName} --output ${outDir} && ` + + `cp ${config.JrtFsJar} ${outDir}/lib/`, + CommandDeps: []string{ + "${moduleInfoJavaPath}", + "${config.JavacCmd}", + "${config.SoongZipCmd}", + "${config.MergeZipsCmd}", + "${config.JmodCmd}", + "${config.JlinkCmd}", + "${config.JrtFsJar}", + }, + }, + "moduleName", "classpath", "outDir", "workDir") +) + +func TransformJarsToSystemModules(ctx android.ModuleContext, moduleName string, jars android.Paths) android.WritablePath { + outDir := android.PathForModuleOut(ctx, "system") + workDir := android.PathForModuleOut(ctx, "modules") + outputFile := android.PathForModuleOut(ctx, "system/lib/modules") + outputs := android.WritablePaths{ + outputFile, + android.PathForModuleOut(ctx, "system/lib/jrt-fs.jar"), + android.PathForModuleOut(ctx, "system/release"), + } + + ctx.ModuleBuild(pctx, android.ModuleBuildParams{ + Rule: jarsTosystemModules, + Description: "system modules", + Outputs: outputs, + Inputs: jars, + Args: map[string]string{ + "moduleName": moduleName, + "classpath": strings.Join(jars.Strings(), ":"), + "workDir": workDir.String(), + "outDir": outDir.String(), + }, + }) + + return outputFile +} + +func SystemModulesFactory() android.Module { + module := &SystemModules{} + module.AddProperties(&module.properties) + android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon) + return module +} + +type SystemModules struct { + android.ModuleBase + + properties SystemModulesProperties + + outputFile android.Path +} + +type SystemModulesProperties struct { + // List of java library modules that should be included in the system modules + Libs []string + + // List of prebuilt jars that should be included in the system modules + Jars []string + + // Sdk version that should be included in the system modules + Sdk_version *string +} + +func (system *SystemModules) GenerateAndroidBuildActions(ctx android.ModuleContext) { + var jars android.Paths + + ctx.VisitDirectDeps(func(module blueprint.Module) { + if ctx.OtherModuleDependencyTag(module) == libTag { + dep, _ := module.(Dependency) + jars = append(jars, dep.ClasspathFiles()...) + } + }) + + jars = append(jars, android.PathsForModuleSrc(ctx, system.properties.Jars)...) + + if ctx.AConfig().TargetOpenJDK9() { + system.outputFile = TransformJarsToSystemModules(ctx, "java.base", jars) + } +} + +func (system *SystemModules) DepsMutator(ctx android.BottomUpMutatorContext) { + ctx.AddDependency(ctx.Module(), libTag, system.properties.Libs...) +} + +func (system *SystemModules) AndroidMk() android.AndroidMkData { + return android.AndroidMkData{ + Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { + if system.outputFile != nil { + makevar := "SOONG_SYSTEM_MODULES_" + name + fmt.Fprintln(w) + fmt.Fprintln(w, makevar, ":=", system.outputFile.String()) + fmt.Fprintln(w, ".KATI_READONLY", ":=", makevar) + } + }, + } +} diff --git a/scripts/jars-to-module-info-java.sh b/scripts/jars-to-module-info-java.sh new file mode 100755 index 000000000..44be54949 --- /dev/null +++ b/scripts/jars-to-module-info-java.sh @@ -0,0 +1,20 @@ +#!/bin/bash -e + +# Extracts the Java package names of all classes in the .jar files and writes a module-info.java +# file to stdout that exports all of those packages. + +if [ -z "$1" ]; then + echo "usage: $0 [ ...]" >&2 + exit 1 +fi + +module_name=$1 +shift + +echo "module ${module_name} {" +for j in "$@"; do zipinfo -1 $j ; done \ + | grep -E '/[^/]*\.class$' \ + | sed 's|\(.*\)/[^/]*\.class$| exports \1;|g' \ + | sed 's|/|.|g' \ + | sort -u +echo "}"