diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java index f6c66fbbb73..28cba71d8e2 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java @@ -88,6 +88,17 @@ public class K2JVMCompilerArguments extends CommonCompilerArguments { // Advanced options + @Argument(value = "-Xmodule-path", valueDescription = "", description = "Paths where to find Java 9+ modules") + public String javaModulePath; + + @Argument( + value = "-Xadd-modules", + valueDescription = "", + description = "Root modules to resolve in addition to the initial modules,\n" + + "or all modules on the module path if 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; diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt index 7ec20ed2304..4f0f1f06980 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt @@ -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() { 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() { 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 { diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/ClasspathRootsResolver.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/ClasspathRootsResolver.kt index 3eb47e8a7f6..152f6e2e1e6 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/ClasspathRootsResolver.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/ClasspathRootsResolver.kt @@ -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, 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, result: MutableList) { val sourceModules = modules.filterIsInstance().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 -> diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index 43a6b68eaa7..72af831569c 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -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 { val uniqueSourceRoots = linkedSetOf() diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/config/JvmContentRoots.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/config/JvmContentRoots.kt index 8b4b0e40da8..ef4be2a0732 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/config/JvmContentRoots.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/config/JvmContentRoots.kt @@ -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)) } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java index 30194da8c06..2083df84718 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java @@ -117,4 +117,6 @@ public class JVMConfigurationKeys { public static final CompilerConfigurationKey USE_JAVAC = CompilerConfigurationKey.create("use javac"); + public static final CompilerConfigurationKey> ADDITIONAL_JAVA_MODULES = + CompilerConfigurationKey.create("additional Java modules"); } diff --git a/compiler/testData/cli/jvm/extraHelp.out b/compiler/testData/cli/jvm/extraHelp.out index d91e850301a..b9003a52934 100644 --- a/compiler/testData/cli/jvm/extraHelp.out +++ b/compiler/testData/cli/jvm/extraHelp.out @@ -1,5 +1,8 @@ Usage: kotlinc-jvm where advanced options include: + -Xmodule-path= Paths where to find Java 9+ modules + -Xadd-modules= Root modules to resolve in addition to the initial modules, + or all modules on the module path if 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 diff --git a/compiler/testData/javaModules/allModulePathAndNamedModule/main.txt b/compiler/testData/javaModules/allModulePathAndNamedModule/main.txt new file mode 100644 index 00000000000..8a201a6b45e --- /dev/null +++ b/compiler/testData/javaModules/allModulePathAndNamedModule/main.txt @@ -0,0 +1,2 @@ +error: -Xadd-modules=ALL-MODULE-PATH can only be used when compiling the unnamed module +COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/testData/javaModules/allModulePathAndNamedModule/main/Foo.kt b/compiler/testData/javaModules/allModulePathAndNamedModule/main/Foo.kt new file mode 100644 index 00000000000..88605abfc6c --- /dev/null +++ b/compiler/testData/javaModules/allModulePathAndNamedModule/main/Foo.kt @@ -0,0 +1,3 @@ +package foo + +class Foo diff --git a/compiler/testData/javaModules/allModulePathAndNamedModule/main/module-info.java b/compiler/testData/javaModules/allModulePathAndNamedModule/main/module-info.java new file mode 100644 index 00000000000..90a3e80ebb4 --- /dev/null +++ b/compiler/testData/javaModules/allModulePathAndNamedModule/main/module-info.java @@ -0,0 +1,3 @@ +module main { + exports foo; +} diff --git a/compiler/testData/javaModules/dependOnManyModules/moduleA/a/A.java b/compiler/testData/javaModules/dependOnManyModules/moduleA/a/A.java new file mode 100644 index 00000000000..e3676563ca3 --- /dev/null +++ b/compiler/testData/javaModules/dependOnManyModules/moduleA/a/A.java @@ -0,0 +1,3 @@ +package a; + +public class A {} diff --git a/compiler/testData/javaModules/dependOnManyModules/moduleA/module-info.java b/compiler/testData/javaModules/dependOnManyModules/moduleA/module-info.java new file mode 100644 index 00000000000..8e8f5b048e3 --- /dev/null +++ b/compiler/testData/javaModules/dependOnManyModules/moduleA/module-info.java @@ -0,0 +1,3 @@ +module moduleA { + exports a; +} diff --git a/compiler/testData/javaModules/dependOnManyModules/moduleB/b/B.java b/compiler/testData/javaModules/dependOnManyModules/moduleB/b/B.java new file mode 100644 index 00000000000..f49ee7e84f8 --- /dev/null +++ b/compiler/testData/javaModules/dependOnManyModules/moduleB/b/B.java @@ -0,0 +1,3 @@ +package b; + +public class B {} diff --git a/compiler/testData/javaModules/dependOnManyModules/moduleB/module-info.java b/compiler/testData/javaModules/dependOnManyModules/moduleB/module-info.java new file mode 100644 index 00000000000..2038e26cabe --- /dev/null +++ b/compiler/testData/javaModules/dependOnManyModules/moduleB/module-info.java @@ -0,0 +1,3 @@ +module moduleB { + exports b; +} diff --git a/compiler/testData/javaModules/dependOnManyModules/moduleC/c/C.java b/compiler/testData/javaModules/dependOnManyModules/moduleC/c/C.java new file mode 100644 index 00000000000..f2f01c42d23 --- /dev/null +++ b/compiler/testData/javaModules/dependOnManyModules/moduleC/c/C.java @@ -0,0 +1,3 @@ +package c; + +public class C {} diff --git a/compiler/testData/javaModules/dependOnManyModules/moduleC/module-info.java b/compiler/testData/javaModules/dependOnManyModules/moduleC/module-info.java new file mode 100644 index 00000000000..521ecbaa9af --- /dev/null +++ b/compiler/testData/javaModules/dependOnManyModules/moduleC/module-info.java @@ -0,0 +1,3 @@ +module moduleC { + exports c; +} diff --git a/compiler/testData/javaModules/dependOnManyModules/moduleD.txt b/compiler/testData/javaModules/dependOnManyModules/moduleD.txt new file mode 100644 index 00000000000..d86bac9de59 --- /dev/null +++ b/compiler/testData/javaModules/dependOnManyModules/moduleD.txt @@ -0,0 +1 @@ +OK diff --git a/compiler/testData/javaModules/dependOnManyModules/moduleD/module-info.java b/compiler/testData/javaModules/dependOnManyModules/moduleD/module-info.java new file mode 100644 index 00000000000..42bc3beeee6 --- /dev/null +++ b/compiler/testData/javaModules/dependOnManyModules/moduleD/module-info.java @@ -0,0 +1,5 @@ +module moduleD { + requires moduleA; + requires moduleB; + requires moduleC; +} \ No newline at end of file diff --git a/compiler/testData/javaModules/dependOnManyModules/moduleD/usage.kt b/compiler/testData/javaModules/dependOnManyModules/moduleD/usage.kt new file mode 100644 index 00000000000..d1981ef9597 --- /dev/null +++ b/compiler/testData/javaModules/dependOnManyModules/moduleD/usage.kt @@ -0,0 +1,9 @@ +import a.* +import b.B +import c.C + +fun usage() { + A() + B() + C() +} diff --git a/compiler/testData/javaModules/jdkModulesFromNamed/main.txt b/compiler/testData/javaModules/jdkModulesFromNamed/main.txt new file mode 100644 index 00000000000..639eeba6f2d --- /dev/null +++ b/compiler/testData/javaModules/jdkModulesFromNamed/main.txt @@ -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 diff --git a/compiler/testData/javaModules/jdkModulesFromNamed/main/module-info.java b/compiler/testData/javaModules/jdkModulesFromNamed/main/module-info.java new file mode 100644 index 00000000000..fe3861e3055 --- /dev/null +++ b/compiler/testData/javaModules/jdkModulesFromNamed/main/module-info.java @@ -0,0 +1,5 @@ +module main { + requires java.naming; + requires jdk.net; + requires oracle.desktop; +} diff --git a/compiler/testData/javaModules/jdkModulesFromNamed/main/test.kt b/compiler/testData/javaModules/jdkModulesFromNamed/main/test.kt new file mode 100644 index 00000000000..38a46ba0042 --- /dev/null +++ b/compiler/testData/javaModules/jdkModulesFromNamed/main/test.kt @@ -0,0 +1,17 @@ +fun main(args: Array) { + // 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) +} diff --git a/compiler/testData/javaModules/jdkModulesFromUnnamed/main.txt b/compiler/testData/javaModules/jdkModulesFromUnnamed/main.txt new file mode 100644 index 00000000000..a0aba9318ad --- /dev/null +++ b/compiler/testData/javaModules/jdkModulesFromUnnamed/main.txt @@ -0,0 +1 @@ +OK \ No newline at end of file diff --git a/compiler/testData/javaModules/jdkModulesFromUnnamed/main/test.kt b/compiler/testData/javaModules/jdkModulesFromUnnamed/main/test.kt new file mode 100644 index 00000000000..b9626822910 --- /dev/null +++ b/compiler/testData/javaModules/jdkModulesFromUnnamed/main/test.kt @@ -0,0 +1,17 @@ +fun main(args: Array) { + // 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) +} diff --git a/compiler/testData/javaModules/simple/moduleA/foo/Foo.java b/compiler/testData/javaModules/simple/moduleA/foo/Foo.java new file mode 100644 index 00000000000..7f566bafd45 --- /dev/null +++ b/compiler/testData/javaModules/simple/moduleA/foo/Foo.java @@ -0,0 +1,3 @@ +package foo; + +public class Foo {} diff --git a/compiler/testData/javaModules/simple/moduleA/module-info.java b/compiler/testData/javaModules/simple/moduleA/module-info.java new file mode 100644 index 00000000000..cbfe65e9b40 --- /dev/null +++ b/compiler/testData/javaModules/simple/moduleA/module-info.java @@ -0,0 +1,3 @@ +module moduleA { + exports foo; +} diff --git a/compiler/testData/javaModules/simple/moduleB.txt b/compiler/testData/javaModules/simple/moduleB.txt new file mode 100644 index 00000000000..d86bac9de59 --- /dev/null +++ b/compiler/testData/javaModules/simple/moduleB.txt @@ -0,0 +1 @@ +OK diff --git a/compiler/testData/javaModules/simple/moduleB/module-info.java b/compiler/testData/javaModules/simple/moduleB/module-info.java new file mode 100644 index 00000000000..b8f934c3a2d --- /dev/null +++ b/compiler/testData/javaModules/simple/moduleB/module-info.java @@ -0,0 +1,3 @@ +module moduleB { + requires moduleA; +} diff --git a/compiler/testData/javaModules/simple/moduleB/usage.kt b/compiler/testData/javaModules/simple/moduleB/usage.kt new file mode 100644 index 00000000000..91c127ff438 --- /dev/null +++ b/compiler/testData/javaModules/simple/moduleB/usage.kt @@ -0,0 +1,5 @@ +import foo.Foo + +fun usage() { + Foo() +} diff --git a/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleA/a/A.java b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleA/a/A.java new file mode 100644 index 00000000000..cb13605d9bb --- /dev/null +++ b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleA/a/A.java @@ -0,0 +1,9 @@ +package a; + +import a.impl.AImpl; + +public class A { + public static AImpl getInstance() { + return new AImpl(); + } +} diff --git a/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleA/a/impl/AImpl.java b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleA/a/impl/AImpl.java new file mode 100644 index 00000000000..ebe2a1cc774 --- /dev/null +++ b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleA/a/impl/AImpl.java @@ -0,0 +1,5 @@ +package a.impl; + +import a.A; + +public class AImpl extends A {} diff --git a/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleA/module-info.java b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleA/module-info.java new file mode 100644 index 00000000000..8e8f5b048e3 --- /dev/null +++ b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleA/module-info.java @@ -0,0 +1,3 @@ +module moduleA { + exports a; +} diff --git a/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleB.txt b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleB.txt new file mode 100644 index 00000000000..a5c1fd42798 --- /dev/null +++ b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleB.txt @@ -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 \ No newline at end of file diff --git a/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleB/module-info.java b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleB/module-info.java new file mode 100644 index 00000000000..b8f934c3a2d --- /dev/null +++ b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleB/module-info.java @@ -0,0 +1,3 @@ +module moduleB { + requires moduleA; +} diff --git a/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleB/usage.kt b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleB/usage.kt new file mode 100644 index 00000000000..af9c0090b3a --- /dev/null +++ b/compiler/testData/javaModules/simpleUseNonExportedPackage/moduleB/usage.kt @@ -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() diff --git a/compiler/testData/javaModules/unnamedDependsOnNamed/moduleA/foo/Foo.java b/compiler/testData/javaModules/unnamedDependsOnNamed/moduleA/foo/Foo.java new file mode 100644 index 00000000000..7f566bafd45 --- /dev/null +++ b/compiler/testData/javaModules/unnamedDependsOnNamed/moduleA/foo/Foo.java @@ -0,0 +1,3 @@ +package foo; + +public class Foo {} diff --git a/compiler/testData/javaModules/unnamedDependsOnNamed/moduleA/module-info.java b/compiler/testData/javaModules/unnamedDependsOnNamed/moduleA/module-info.java new file mode 100644 index 00000000000..cbfe65e9b40 --- /dev/null +++ b/compiler/testData/javaModules/unnamedDependsOnNamed/moduleA/module-info.java @@ -0,0 +1,3 @@ +module moduleA { + exports foo; +} diff --git a/compiler/testData/javaModules/unnamedDependsOnNamed/moduleB.txt b/compiler/testData/javaModules/unnamedDependsOnNamed/moduleB.txt new file mode 100644 index 00000000000..a0aba9318ad --- /dev/null +++ b/compiler/testData/javaModules/unnamedDependsOnNamed/moduleB.txt @@ -0,0 +1 @@ +OK \ No newline at end of file diff --git a/compiler/testData/javaModules/unnamedDependsOnNamed/moduleB/Usage.java b/compiler/testData/javaModules/unnamedDependsOnNamed/moduleB/Usage.java new file mode 100644 index 00000000000..2b95c3a8624 --- /dev/null +++ b/compiler/testData/javaModules/unnamedDependsOnNamed/moduleB/Usage.java @@ -0,0 +1,5 @@ +public class Usage { + public static void main(String[] args) { + new foo.Foo(); + } +} diff --git a/compiler/testData/javaModules/unnamedDependsOnNamed/moduleB/usage.kt b/compiler/testData/javaModules/unnamedDependsOnNamed/moduleB/usage.kt new file mode 100644 index 00000000000..91c127ff438 --- /dev/null +++ b/compiler/testData/javaModules/unnamedDependsOnNamed/moduleB/usage.kt @@ -0,0 +1,5 @@ +import foo.Foo + +fun usage() { + Foo() +} diff --git a/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java b/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java index a6e485b8c2d..c840a8c62ec 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java +++ b/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java @@ -912,6 +912,27 @@ public class KotlinTestUtils { } } + public static boolean compileJavaFilesExternallyWithJava9(@NotNull Collection files, @NotNull List options) { + File jdk9 = getJdk9HomeIfPossible(); + assert jdk9 != null : "Environment variable JDK_19 is not set"; + + List 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 diagnosticCollector, boolean humanReadable) { StringBuilder builder = new StringBuilder(); diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractKotlinCompilerIntegrationTest.kt b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractKotlinCompilerIntegrationTest.kt index faeaac17ab5..61ea790d369 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractKotlinCompilerIntegrationTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractKotlinCompilerIntegrationTest.kt @@ -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 = emptyList(), + compileJava: (sourceDir: File, javaFiles: List, 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) { diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/Java9ModulesIntegrationTest.kt b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/Java9ModulesIntegrationTest.kt new file mode 100644 index 00000000000..39ee19fa7e5 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/Java9ModulesIntegrationTest.kt @@ -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 = emptyList(), + addModules: List = emptyList() + ): File { + val jdk9Home = KotlinTestUtils.getJdk9HomeIfPossible() ?: return File("") + + 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") + } +}