JS: support internal visibility from friend modules

Friend modules should be provided using the -Xfriend-modules flag
in the same format as -libraries. No manual configuration required for
JPS, Gradle and Maven plugins.

Friend modules could be switched off using the -Xfriend-modules-disabled
flag. Doing that will
  * prevent internal declarations from being exported,
  * values provided by -Xfriend-modules ignored,
  * raise a compilation error on attemps to use internal declarations from other modules

Fixes #KT-15135 and #KT-16568.

Original commit: 2e9a59819a
This commit is contained in:
Anton Bannykh
2017-03-30 15:03:38 +03:00
parent 8c25a1982e
commit 48328f7989
12 changed files with 165 additions and 20 deletions
@@ -373,6 +373,11 @@ class KotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() {
makeAll().assertSuccessful()
}
fun testKotlinJavaScriptInternalFromSpecialRelatedModule() {
initProject(JS_STDLIB)
makeAll().assertSuccessful()
}
fun testKotlinJavaScriptProjectWithTests() {
initProject(JS_STDLIB)
makeAll().assertSuccessful()
@@ -82,6 +82,7 @@ class JpsKotlinCompilerRunner : KotlinCompilerRunner<JpsCompilerEnvironment>() {
environment: JpsCompilerEnvironment,
sourceFiles: Collection<File>,
libraries: List<String>,
friendModules: List<String>,
outputFile: File
) {
log.debug("K2JS: common arguments: " + ArgumentUtils.convertArgumentsToStringList(commonArguments))
@@ -90,7 +91,7 @@ class JpsKotlinCompilerRunner : KotlinCompilerRunner<JpsCompilerEnvironment>() {
val arguments = mergeBeans(commonArguments, XmlSerializerUtil.createCopy(k2jsArguments))
log.debug("K2JS: merged arguments: " + ArgumentUtils.convertArgumentsToStringList(arguments))
setupK2JsArguments(outputFile, sourceFiles, libraries, arguments)
setupK2JsArguments(outputFile, sourceFiles, libraries, friendModules, arguments)
log.debug("K2JS: arguments after setup" + ArgumentUtils.convertArgumentsToStringList(arguments))
withCompilerSettings(compilerSettings) {
@@ -203,13 +204,14 @@ class JpsKotlinCompilerRunner : KotlinCompilerRunner<JpsCompilerEnvironment>() {
}
}
private fun setupK2JsArguments(_outputFile: File, sourceFiles: Collection<File>, _libraries: List<String>, settings: K2JSCompilerArguments) {
private fun setupK2JsArguments(_outputFile: File, sourceFiles: Collection<File>, _libraries: List<String>, _friendModules: List<String>, settings: K2JSCompilerArguments) {
with(settings) {
noStdlib = true
freeArgs = sourceFiles.map { it.path }
outputFile = _outputFile.path
metaInfo = true
libraries = _libraries.joinToString(File.pathSeparator)
friendModules = _friendModules.joinToString(File.pathSeparator)
}
}
@@ -51,18 +51,16 @@ object JpsJsModuleUtils {
if (module.moduleType != JpsJavaModuleType.INSTANCE) return
if ((module != target.module || target.isTests) && module.sourceRoots.any { it.rootType == JavaSourceRootType.SOURCE}) {
addTarget(module, JavaModuleBuildTargetType.PRODUCTION)
addTarget(module, isTests = false)
}
if (module != target.module && target.isTests && module.sourceRoots.any { it.rootType == JavaSourceRootType.TEST_SOURCE}) {
addTarget(module, JavaModuleBuildTargetType.TEST)
addTarget(module, isTests = true)
}
}
fun addTarget(module: JpsModule, targetType: JavaModuleBuildTargetType) {
val moduleBuildTarget = ModuleBuildTarget(module, targetType)
val outputDir = KotlinBuilderModuleScriptGenerator.getOutputDirSafe(moduleBuildTarget)
val metaInfoFile = getOutputMetaFile(outputDir, module.name, targetType.isTests)
fun addTarget(module: JpsModule, isTests: Boolean) {
val metaInfoFile = getOutputMetaFile(module, isTests)
if (metaInfoFile.exists()) {
result.add(metaInfoFile.absolutePath)
}
@@ -70,6 +68,13 @@ object JpsJsModuleUtils {
})
}
@JvmStatic
fun getOutputMetaFile(module: JpsModule, isTests: Boolean): File {
val moduleBuildTarget = ModuleBuildTarget(module, if (isTests) JavaModuleBuildTargetType.TEST else JavaModuleBuildTargetType.PRODUCTION)
val outputDir = KotlinBuilderModuleScriptGenerator.getOutputDirSafe(moduleBuildTarget)
return getOutputMetaFile(outputDir, module.name, isTests)
}
@JvmStatic
fun getOutputFile(outputDir: File, moduleName: String, isTests: Boolean)
= File(outputDir, moduleName + suffix(isTests) + KotlinJavascriptMetadataUtils.JS_EXT)
@@ -57,6 +57,7 @@ import org.jetbrains.kotlin.daemon.common.isDaemonEnabled
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings
import org.jetbrains.kotlin.jps.build.JpsJsModuleUtils.getOutputMetaFile
import org.jetbrains.kotlin.jps.incremental.*
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
@@ -661,8 +662,13 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
val compilerSettings = JpsKotlinCompilerSettings.getCompilerSettings(representativeModule)
val k2JsArguments = JpsKotlinCompilerSettings.getK2JsCompilerArguments(representativeModule)
val friendPaths = KotlinBuilderModuleScriptGenerator.getProductionModulesWhichInternalsAreVisible(representativeTarget).mapNotNull {
val file = getOutputMetaFile(it, false)
if (file.exists()) file.absolutePath.toString() else null
}
val compilerRunner = JpsKotlinCompilerRunner()
compilerRunner.runK2JsCompiler(commonArguments, k2JsArguments, compilerSettings, environment, sourceFiles, libraries, outputFile)
compilerRunner.runK2JsCompiler(commonArguments, k2JsArguments, compilerSettings, environment, sourceFiles, libraries, friendPaths, outputFile)
return environment.outputItemsCollector
}
@@ -132,20 +132,21 @@ object KotlinBuilderModuleScriptGenerator {
fun getOutputDirSafe(target: ModuleBuildTarget): File =
target.outputDir ?: throw ProjectBuildException("No output directory found for " + target)
private fun getAdditionalOutputDirsWhereInternalsAreVisible(target: ModuleBuildTarget): List<File> {
if (!target.isTests) return emptyList()
fun getProductionModulesWhichInternalsAreVisible(from: ModuleBuildTarget): List<JpsModule> {
if (!from.isTests) return emptyList()
val result = SmartList<File>()
result.addIfNotNull(JpsJavaExtensionService.getInstance().getOutputDirectory(target.module, false))
getRelatedProductionModule(target.module)?.let {
result.addIfNotNull(JpsJavaExtensionService.getInstance().getOutputDirectory(it, false))
}
val result = SmartList<JpsModule>(from.module)
result.addIfNotNull(getRelatedProductionModule(from.module))
return result
}
fun getAdditionalOutputDirsWhereInternalsAreVisible(target: ModuleBuildTarget): List<File> {
return getProductionModulesWhichInternalsAreVisible(target).mapNotNullTo(SmartList<File>()) {
JpsJavaExtensionService.getInstance().getOutputDirectory(it, false)
}
}
private fun findClassPathRoots(target: ModuleBuildTarget): Collection<File> {
return getAllDependencies(target).classes().roots.filter { file ->
if (!file.exists()) {
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="DEFAULT_COMPILER" value="Javac" />
<resourceExtensions />
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
<component name="CopyrightManager" default="">
<module2copyright />
</component>
<component name="DependencyValidationManager">
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</component>
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/module1/module1.iml" filepath="$PROJECT_DIR$/module1/module1.iml" />
<module fileurl="file://$PROJECT_DIR$/module2/module2.iml" filepath="$PROJECT_DIR$/module2/module2.iml" />
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="IDEA_JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="" />
</component>
</project>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="IDEA_JDK" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinRuntime" level="project" />
</component>
</module>
@@ -0,0 +1,27 @@
package test1
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.SOURCE)
internal annotation class InternalFileAnnotation1()
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
internal annotation class InternalClassAnnotation1()
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
internal annotation class InternalFunctionAnnotation1()
internal open class InternalClass1
abstract class ClassA1(internal val member: Int)
abstract class ClassB1 {
internal abstract val member: Int
internal fun func() = 1
}
internal val internalProp = 1
internal fun internalFun() {}
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="jdk" jdkName="IDEA_JDK" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="module1" />
<orderEntry type="library" name="KotlinRuntime" level="project" />
</component>
<component name="TestModuleProperties" production-module="module1" />
</module>
@@ -0,0 +1,33 @@
@file:InternalFileAnnotation1
package test2
import test1.*
internal class FromInternalClass1: InternalClass1()
@InternalClassAnnotation1
class FromClassA1 : ClassA1(10) {
@InternalClassAnnotation1
class Nested {
@InternalFunctionAnnotation1
fun foo() {}
}
}
class FromClassB1 : ClassB1() {
internal override val member = 10
}
@InternalFunctionAnnotation1
fun foo() {}
fun box() {
internalProp
internalFun()
InternalClass1()
FromClassA1().member
FromClassB1().member
FromClassB1().func()
}
@@ -1,2 +1,6 @@
fun main() {
}
}
internal fun internalFun() {}
internal val internalVal = 10
@@ -1,3 +1,5 @@
fun testMain() {
main()
}
internalFun()
var a = internalVal
}