Support -Xmodule-path and -Xadd-modules command line arguments

#KT-18598 In Progress
 #KT-18599 Fixed
This commit is contained in:
Alexander Udalov
2017-05-26 15:20:05 +03:00
parent e32880d9a3
commit 03d83db660
43 changed files with 355 additions and 14 deletions
@@ -88,6 +88,17 @@ public class K2JVMCompilerArguments extends CommonCompilerArguments {
// Advanced options
@Argument(value = "-Xmodule-path", valueDescription = "<path>", description = "Paths where to find Java 9+ modules")
public String javaModulePath;
@Argument(
value = "-Xadd-modules",
valueDescription = "<module[,]>",
description = "Root modules to resolve in addition to the initial modules,\n" +
"or all modules on the module path if <module> is ALL-MODULE-PATH"
)
public String[] additionalJavaModules;
@Argument(value = "-Xno-call-assertions", description = "Don't generate not-null assertion after each invocation of method returning not-null")
public boolean noCallAssertions;
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.CompileEnvironmentUtil
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
import org.jetbrains.kotlin.cli.jvm.config.JvmModulePathRoot
import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoot
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
@@ -106,6 +107,9 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
val classpath = getClasspath(paths, arguments)
configuration.addJvmClasspathRoots(classpath)
for (modularRoot in arguments.javaModulePath?.split(File.pathSeparatorChar).orEmpty()) {
configuration.add(JVMConfigurationKeys.CONTENT_ROOTS, JvmModulePathRoot(File(modularRoot)))
}
configuration.put(CommonConfigurationKeys.MODULE_NAME, arguments.moduleName ?: JvmAbi.DEFAULT_MODULE_NAME)
@@ -299,6 +303,10 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
configuration.put(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS, components)
}
}
arguments.additionalJavaModules?.let { additionalJavaModules ->
configuration.addAll(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES, additionalJavaModules.toList())
}
}
override fun createArguments(): K2JVMCompilerArguments = K2JVMCompilerArguments().apply {
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.cli.common.messages.MessageUtil
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRoot
import org.jetbrains.kotlin.cli.jvm.config.JvmModulePathRoot
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder
import org.jetbrains.kotlin.config.ContentRoot
@@ -41,6 +42,7 @@ import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleInfo
internal class ClasspathRootsResolver(
private val psiManager: PsiManager,
private val messageCollector: MessageCollector?,
private val additionalModules: List<String>,
private val contentRootToVirtualFile: (JvmContentRoot) -> VirtualFile?
) {
private val javaModuleFinder = CliJavaModuleFinder(VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL))
@@ -75,6 +77,15 @@ internal class ClasspathRootsResolver(
is JvmClasspathRoot -> {
result += JavaRoot(root, JavaRoot.RootType.BINARY)
}
is JvmModulePathRoot -> {
// TODO: sanitize the automatic module name exactly as in javac
// TODO: read Automatic-Module-Name manifest entry
val module = modularBinaryRoot(root, automaticModuleName = { contentRoot.file.name })
if (module != null) {
// TODO: report something in case of several modules with the same name?
modules += module
}
}
else -> error("Unknown root type: $contentRoot")
}
}
@@ -97,6 +108,17 @@ internal class ClasspathRootsResolver(
return JavaModule.Explicit(JavaModuleInfo.create(psiJavaModule), root, moduleInfoFile, isBinary = false)
}
private fun modularBinaryRoot(root: VirtualFile, automaticModuleName: () -> String): JavaModule? {
val moduleInfoFile = root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE)
return if (moduleInfoFile != null) {
val moduleInfo = JavaModuleInfo.read(moduleInfoFile) ?: return null
JavaModule.Explicit(moduleInfo, root, moduleInfoFile, isBinary = true)
}
else {
JavaModule.Automatic(automaticModuleName(), root)
}
}
private fun addModularRoots(modules: List<JavaModule>, result: MutableList<JavaRoot>) {
val sourceModules = modules.filterIsInstance<JavaModule.Explicit>().filterNot(JavaModule::isBinary)
if (sourceModules.size > 1) {
@@ -113,12 +135,17 @@ internal class ClasspathRootsResolver(
if (javaModuleFinder.allObservableModules.none()) return
val rootModules =
if (sourceModules.isNotEmpty()) {
// TODO: support an option similar to --add-modules
listOf(sourceModules.single().name)
}
else computeDefaultRootModules() // TODO: + everything from module path
val addAllModulePathToRoots = "ALL-MODULE-PATH" in additionalModules
if (addAllModulePathToRoots && sourceModules.isNotEmpty()) {
report(ERROR, "-Xadd-modules=ALL-MODULE-PATH can only be used when compiling the unnamed module")
return
}
val rootModules = when {
sourceModules.isNotEmpty() -> listOf(sourceModules.single().name) + additionalModules
addAllModulePathToRoots -> modules.map(JavaModule::name)
else -> computeDefaultRootModules() + modules.map(JavaModule::name)
}
// TODO: if at least one automatic module is added, add all automatic modules as per java.lang.module javadoc
val allDependencies = javaModuleGraph.getAllDependencies(rootModules).also { loadedModules ->
@@ -194,7 +194,11 @@ class KotlinCoreEnvironment private constructor(
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
classpathRootsResolver = ClasspathRootsResolver(PsiManager.getInstance(project), messageCollector, this::contentRootToVirtualFile)
classpathRootsResolver = ClasspathRootsResolver(
PsiManager.getInstance(project), messageCollector,
configuration.getList(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES),
this::contentRootToVirtualFile
)
val (initialRoots, javaModules) =
classpathRootsResolver.convertClasspathRoots(configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS))
@@ -304,7 +308,8 @@ class KotlinCoreEnvironment private constructor(
private fun contentRootToVirtualFile(root: JvmContentRoot): VirtualFile? {
return when (root) {
is JvmClasspathRoot -> if (root.file.isFile) findJarRoot(root) else findLocalFile(root)
is JvmClasspathRoot -> if (root.file.isFile) findJarRoot(root.file) else findLocalFile(root)
is JvmModulePathRoot -> if (root.file.isFile) findJarRoot(root.file) else findLocalFile(root)
is JavaSourceRoot -> findLocalFile(root)
else -> throw IllegalStateException("Unexpected root: $root")
}
@@ -320,8 +325,8 @@ class KotlinCoreEnvironment private constructor(
}
}
private fun findJarRoot(root: JvmClasspathRoot): VirtualFile? =
applicationEnvironment.jarFileSystem.findFileByPath("${root.file}${URLUtil.JAR_SEPARATOR}")
private fun findJarRoot(file: File): VirtualFile? =
applicationEnvironment.jarFileSystem.findFileByPath("$file${URLUtil.JAR_SEPARATOR}")
private fun getSourceRootsCheckingForDuplicates(): Collection<String> {
val uniqueSourceRoots = linkedSetOf<String>()
@@ -30,6 +30,8 @@ data class JvmClasspathRoot(override val file: File) : JvmContentRoot
data class JavaSourceRoot(override val file: File, val packagePrefix: String?) : JvmContentRoot
data class JvmModulePathRoot(override val file: File) : JvmContentRoot
fun CompilerConfiguration.addJvmClasspathRoot(file: File) {
add(JVMConfigurationKeys.CONTENT_ROOTS, JvmClasspathRoot(file))
}
@@ -117,4 +117,6 @@ public class JVMConfigurationKeys {
public static final CompilerConfigurationKey<Boolean> USE_JAVAC =
CompilerConfigurationKey.create("use javac");
public static final CompilerConfigurationKey<List<String>> ADDITIONAL_JAVA_MODULES =
CompilerConfigurationKey.create("additional Java modules");
}
+3
View File
@@ -1,5 +1,8 @@
Usage: kotlinc-jvm <options> <source files>
where advanced options include:
-Xmodule-path=<path> Paths where to find Java 9+ modules
-Xadd-modules=<module[,]> Root modules to resolve in addition to the initial modules,
or all modules on the module path if <module> is ALL-MODULE-PATH
-Xno-call-assertions Don't generate not-null assertion after each invocation of method returning not-null
-Xno-param-assertions Don't generate not-null assertions on parameters of methods accessible from Java
-Xno-optimize Disable optimizations
@@ -0,0 +1,2 @@
error: -Xadd-modules=ALL-MODULE-PATH can only be used when compiling the unnamed module
COMPILATION_ERROR
@@ -0,0 +1,3 @@
package foo
class Foo
@@ -0,0 +1,3 @@
module main {
exports foo;
}
@@ -0,0 +1,3 @@
package a;
public class A {}
@@ -0,0 +1,3 @@
module moduleA {
exports a;
}
@@ -0,0 +1,3 @@
package b;
public class B {}
@@ -0,0 +1,3 @@
module moduleB {
exports b;
}
@@ -0,0 +1,3 @@
package c;
public class C {}
@@ -0,0 +1,3 @@
module moduleC {
exports c;
}
@@ -0,0 +1 @@
OK
@@ -0,0 +1,5 @@
module moduleD {
requires moduleA;
requires moduleB;
requires moduleC;
}
@@ -0,0 +1,9 @@
import a.*
import b.B
import c.C
fun usage() {
A()
B()
C()
}
@@ -0,0 +1,4 @@
compiler/testData/javaModules/jdkModulesFromNamed/main/test.kt:11:24: error: unresolved reference: httpserver
val s: com.sun.net.httpserver.HttpServer? = null
^
COMPILATION_ERROR
@@ -0,0 +1,5 @@
module main {
requires java.naming;
requires jdk.net;
requires oracle.desktop;
}
@@ -0,0 +1,17 @@
fun main(args: Array<String>) {
// Module java.naming
val b: javax.naming.Binding? = null
println(b)
// Module jdk.net
val j: jdk.net.Sockets? = null
println(j)
// Module jdk.httpserver (this module doesn't depend on it)
val s: com.sun.net.httpserver.HttpServer? = null
println(s)
// Module oracle.desktop
val a: com.oracle.awt.AWTUtils? = null
println(a)
}
@@ -0,0 +1 @@
OK
@@ -0,0 +1,17 @@
fun main(args: Array<String>) {
// Module java.naming
val b: javax.naming.Binding? = null
println(b)
// Module jdk.net
val j: jdk.net.Sockets? = null
println(j)
// Module jdk.httpserver
val s: com.sun.net.httpserver.HttpServer? = null
println(s)
// Module oracle.desktop
val a: com.oracle.awt.AWTUtils? = null
println(a)
}
@@ -0,0 +1,3 @@
package foo;
public class Foo {}
@@ -0,0 +1,3 @@
module moduleA {
exports foo;
}
+1
View File
@@ -0,0 +1 @@
OK
@@ -0,0 +1,3 @@
module moduleB {
requires moduleA;
}
+5
View File
@@ -0,0 +1,5 @@
import foo.Foo
fun usage() {
Foo()
}
@@ -0,0 +1,9 @@
package a;
import a.impl.AImpl;
public class A {
public static AImpl getInstance() {
return new AImpl();
}
}
@@ -0,0 +1,5 @@
package a.impl;
import a.A;
public class AImpl extends A {}
@@ -0,0 +1,3 @@
module moduleA {
exports a;
}
@@ -0,0 +1,4 @@
compiler/testData/javaModules/simpleUseNonExportedPackage/moduleB/usage.kt:8:9: error: symbol is declared in module 'moduleA' which does not export package 'a.impl'
val a3: AImpl = A.getInstance()
^
COMPILATION_ERROR
@@ -0,0 +1,3 @@
module moduleB {
requires moduleA;
}
@@ -0,0 +1,8 @@
package test
import a.*
import a.impl.*
val a1: A = A()
val a2: A = A.getInstance()
val a3: AImpl = A.getInstance()
@@ -0,0 +1,3 @@
package foo;
public class Foo {}
@@ -0,0 +1,3 @@
module moduleA {
exports foo;
}
@@ -0,0 +1 @@
OK
@@ -0,0 +1,5 @@
public class Usage {
public static void main(String[] args) {
new foo.Foo();
}
}
@@ -0,0 +1,5 @@
import foo.Foo
fun usage() {
Foo()
}
@@ -912,6 +912,27 @@ public class KotlinTestUtils {
}
}
public static boolean compileJavaFilesExternallyWithJava9(@NotNull Collection<File> files, @NotNull List<String> options) {
File jdk9 = getJdk9HomeIfPossible();
assert jdk9 != null : "Environment variable JDK_19 is not set";
List<String> command = new ArrayList<>();
command.add(new File(jdk9, "bin/javac").getPath());
command.addAll(options);
for (File file : files) {
command.add(file.getPath());
}
try {
Process process = new ProcessBuilder().command(command).inheritIO().start();
process.waitFor();
return process.exitValue() == 0;
}
catch (Exception e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
private static String errorsToString(@NotNull DiagnosticCollector<JavaFileObject> diagnosticCollector, boolean humanReadable) {
StringBuilder builder = new StringBuilder();
@@ -39,6 +39,8 @@ abstract class AbstractKotlinCompilerIntegrationTest : TestCaseWithTmpdir() {
return File(testDataDirectory, "${getTestName(true)}.$extension")
}
class JavaCompilationError : AssertionError("Java files are not compiled successfully")
/**
* Compiles all sources (.java and .kt) under the directory named [libraryName] to [destination].
* [destination] should be either a path to the directory under [tmpdir], or a path to the resulting .jar file (also under [tmpdir]).
@@ -50,10 +52,15 @@ abstract class AbstractKotlinCompilerIntegrationTest : TestCaseWithTmpdir() {
libraryName: String,
destination: File = File(tmpdir, "$libraryName.jar"),
additionalOptions: List<String> = emptyList(),
compileJava: (sourceDir: File, javaFiles: List<File>, outputDir: File) -> Boolean = { _, javaFiles, outputDir ->
KotlinTestUtils.compileJavaFiles(javaFiles, listOf("-d", outputDir.path))
},
checkKotlinOutput: (String) -> Unit = { actual -> assertEquals(normalizeOutput("" to ExitCode.OK), actual) },
vararg extraClassPath: File
): File {
val javaFiles = FileUtil.findFilesByMask(JAVA_FILES, File(testDataDirectory, libraryName))
val kotlinFiles = FileUtil.findFilesByMask(KOTLIN_FILES, File(testDataDirectory, libraryName))
val sourceDir = File(testDataDirectory, libraryName)
val javaFiles = FileUtil.findFilesByMask(JAVA_FILES, sourceDir)
val kotlinFiles = FileUtil.findFilesByMask(KOTLIN_FILES, sourceDir)
assert(javaFiles.isNotEmpty() || kotlinFiles.isNotEmpty()) { "There should be either .kt or .java files in the directory" }
val isJar = destination.name.endsWith(".jar")
@@ -61,12 +68,14 @@ abstract class AbstractKotlinCompilerIntegrationTest : TestCaseWithTmpdir() {
val outputDir = if (isJar) File(tmpdir, "output-$libraryName") else destination
if (kotlinFiles.isNotEmpty()) {
val output = compileKotlin(libraryName, outputDir, extraClassPath.toList(), K2JVMCompiler(), additionalOptions, expectedFileName = null)
assertEquals(normalizeOutput("" to ExitCode.OK), normalizeOutput(output))
checkKotlinOutput(normalizeOutput(output))
}
if (javaFiles.isNotEmpty()) {
outputDir.mkdirs()
KotlinTestUtils.compileJavaFiles(javaFiles, listOf("-d", outputDir.path))
if (!compileJava(sourceDir, javaFiles, outputDir)) {
throw JavaCompilationError()
}
}
if (isJar) {
@@ -0,0 +1,104 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.jvm.compiler
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
class Java9ModulesIntegrationTest : AbstractKotlinCompilerIntegrationTest() {
override val testDataPath: String
get() = "compiler/testData/javaModules/"
private fun module(
name: String,
modulePath: List<File> = emptyList(),
addModules: List<String> = emptyList()
): File {
val jdk9Home = KotlinTestUtils.getJdk9HomeIfPossible() ?: return File("<test-skipped>")
val paths = modulePath.joinToString(separator = File.pathSeparator) { it.path }
val kotlinOptions = mutableListOf(
"-jdk-home", jdk9Home.path,
"-Xmodule-path=$paths"
)
if (addModules.isNotEmpty()) {
kotlinOptions += "-Xadd-modules=${addModules.joinToString()}"
}
return compileLibrary(
name,
additionalOptions = kotlinOptions,
compileJava = { _, javaFiles, outputDir ->
val javaOptions = mutableListOf(
"-d", outputDir.path,
"--module-path", paths
)
if (addModules.isNotEmpty()) {
javaOptions += "--add-modules"
javaOptions += addModules.joinToString()
}
KotlinTestUtils.compileJavaFilesExternallyWithJava9(javaFiles, javaOptions)
},
checkKotlinOutput = { actual ->
KotlinTestUtils.assertEqualsToFile(File(testDataDirectory, "$name.txt"), actual)
}
)
}
fun testSimple() {
val a = module("moduleA")
module("moduleB", listOf(a))
}
fun testSimpleUseNonExportedPackage() {
val a = module("moduleA")
module("moduleB", listOf(a))
}
fun testDependOnManyModules() {
val a = module("moduleA")
val b = module("moduleB")
val c = module("moduleC")
module("moduleD", listOf(a, b, c))
}
fun testUnnamedDependsOnNamed() {
val a = module("moduleA")
module("moduleB", listOf(a), listOf("moduleA"))
// Also check that -Xadd-modules=ALL-MODULE-PATH has the same effect as -Xadd-module=moduleA, i.e. adds moduleA to the roots
module("moduleB", listOf(a), listOf("ALL-MODULE-PATH"))
}
fun testAllModulePathAndNamedModule() {
try {
module("main", addModules = listOf("ALL-MODULE-PATH"))
}
catch (e: JavaCompilationError) {
// Java compilation should fail, it's expected
}
}
fun testJdkModulesFromNamed() {
module("main")
}
fun testJdkModulesFromUnnamed() {
module("main")
}
}