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.
This commit is contained in:
Anton Bannykh
2017-03-30 15:03:38 +03:00
parent 7bbf9861d0
commit 2e9a59819a
48 changed files with 765 additions and 133 deletions
@@ -79,9 +79,20 @@ public class K2JSCompilerArguments extends CommonCompilerArguments {
// Advanced options // Advanced options
@GradleOption(DefaultValues.BooleanFalseDefault.class) @GradleOption(DefaultValues.BooleanFalseDefault.class)
@Argument(value = "-Xtypedarrays", description = "Translate primitive arrays to JS typed arrays") @Argument(value = "-Xtyped-arrays", description = "Translate primitive arrays to JS typed arrays")
public boolean typedArrays; public boolean typedArrays;
@GradleOption(DefaultValues.BooleanFalseDefault.class)
@Argument(value = "-Xfriend-modules-disabled", description = "Disable internal declaration export")
public boolean friendModulesDisabled;
@Argument(
value = "-Xfriend-modules",
valueDescription = "<path>",
description = "Paths to friend modules"
)
public String friendModules;
@NotNull @NotNull
public static K2JSCompilerArguments createDefaultInstance() { public static K2JSCompilerArguments createDefaultInstance() {
K2JSCompilerArguments arguments = new K2JSCompilerArguments(); K2JSCompilerArguments arguments = new K2JSCompilerArguments();
@@ -274,11 +274,19 @@ public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
ContainerUtil.addAll(libraries, ArraysKt.filterNot(arguments.libraries.split(File.pathSeparator), String::isEmpty)); ContainerUtil.addAll(libraries, ArraysKt.filterNot(arguments.libraries.split(File.pathSeparator), String::isEmpty));
} }
configuration.put(JSConfigurationKeys.LIBRARIES, libraries);
if (arguments.typedArrays) { if (arguments.typedArrays) {
configuration.put(JSConfigurationKeys.TYPED_ARRAYS_ENABLED, true); configuration.put(JSConfigurationKeys.TYPED_ARRAYS_ENABLED, true);
} }
configuration.put(JSConfigurationKeys.LIBRARIES, libraries); configuration.put(JSConfigurationKeys.FRIEND_PATHS_DISABLED, arguments.friendModulesDisabled);
if (!arguments.friendModulesDisabled && arguments.friendModules != null) {
List<String> friendPaths = ArraysKt.filterNot(arguments.friendModules.split(File.pathSeparator), String::isEmpty);
configuration.put(JSConfigurationKeys.FRIEND_PATHS, friendPaths);
}
String moduleKindName = arguments.moduleKind; String moduleKindName = arguments.moduleKind;
ModuleKind moduleKind = moduleKindName != null ? moduleKindMap.get(moduleKindName) : ModuleKind.PLAIN; ModuleKind moduleKind = moduleKindName != null ? moduleKindMap.get(moduleKindName) : ModuleKind.PLAIN;
+4 -2
View File
@@ -1,6 +1,8 @@
Usage: kotlinc-js <options> <source files> Usage: kotlinc-js <options> <source files>
where advanced options include: where advanced options include:
-Xtypedarrays Translate primitive arrays to JS typed arrays -Xtyped-arrays Translate primitive arrays to JS typed arrays
-Xfriend-modules-disabled Disable internal declaration export
-Xfriend-modules=<path> Paths to friend modules
-Xno-inline Disable method inlining -Xno-inline Disable method inlining
-Xrepeat=<count> Repeat compilation (for performance analysis) -Xrepeat=<count> Repeat compilation (for performance analysis)
-Xskip-metadata-version-check Load classes with bad metadata version anyway (incl. pre-release classes) -Xskip-metadata-version-check Load classes with bad metadata version anyway (incl. pre-release classes)
@@ -14,4 +16,4 @@ where advanced options include:
Enable coroutines or report warnings or errors on declarations and use sites of 'suspend' modifier Enable coroutines or report warnings or errors on declarations and use sites of 'suspend' modifier
Advanced options are non-standard and may be changed or removed without any notice. Advanced options are non-standard and may be changed or removed without any notice.
OK OK
+37
View File
@@ -0,0 +1,37 @@
// MODULE: lib
// FILE: lib.kt
package lib
internal fun foo() = 1
internal val bar = 2
internal class A {
internal fun baz(a: Int): Int {
return a * 10
}
internal val foo = 3
internal inner class B {
internal fun foo() = 4
}
}
// MODULE: main(lib)(lib)
// FILE: main.kt
package main
import lib.*
fun box(): String {
if (foo() != 1) return "fail 1: ${foo()}"
if (bar != 2) return "fail 2: ${bar}"
val a = A()
if (a.baz(10) != 100) return "fail 3: ${a.baz(10)}"
if (a.foo != 3) return "fail 4: ${a.foo}"
if (a.B().foo() != 4) return "fail 5: ${a.B().foo()}"
return "OK"
}
@@ -46,10 +46,12 @@ public abstract class KotlinMultiFileTestWithJava<M, F> extends KotlinTestWithEn
public class ModuleAndDependencies { public class ModuleAndDependencies {
final M module; final M module;
final List<String> dependencies; final List<String> dependencies;
final List<String> friends;
ModuleAndDependencies(M module, List<String> dependencies) { ModuleAndDependencies(M module, List<String> dependencies, List<String> friends) {
this.module = module; this.module = module;
this.dependencies = dependencies; this.dependencies = dependencies;
this.friends = friends;
} }
} }
@@ -129,9 +131,9 @@ public abstract class KotlinMultiFileTestWithJava<M, F> extends KotlinTestWithEn
} }
@Override @Override
public M createModule(@NotNull String name, @NotNull List<String> dependencies) { public M createModule(@NotNull String name, @NotNull List<String> dependencies, @NotNull List<String> friends) {
M module = createTestModule(name); M module = createTestModule(name);
ModuleAndDependencies oldValue = modules.put(name, new ModuleAndDependencies(module, dependencies)); ModuleAndDependencies oldValue = modules.put(name, new ModuleAndDependencies(module, dependencies, friends));
assert oldValue == null : "Module " + name + " declared more than once"; assert oldValue == null : "Module " + name + " declared more than once";
return module; return module;
@@ -118,7 +118,7 @@ public class KotlinTestUtils {
private static final String MODULE_DELIMITER = ",\\s*"; private static final String MODULE_DELIMITER = ",\\s*";
private static final Pattern FILE_OR_MODULE_PATTERN = Pattern.compile( private static final Pattern FILE_OR_MODULE_PATTERN = Pattern.compile(
"(?://\\s*MODULE:\\s*([^()\\n]+)(?:\\(([^()]+(?:" + MODULE_DELIMITER + "[^()]+)*)\\))?\\s*)?" + "(?://\\s*MODULE:\\s*([^()\\n]+)(?:\\(([^()]+(?:" + MODULE_DELIMITER + "[^()]+)*)\\))?\\s*(?:\\(([^()]+(?:" + MODULE_DELIMITER + "[^()]+)*)\\))?\\s*)?" +
"//\\s*FILE:\\s*(.*)$", Pattern.MULTILINE); "//\\s*FILE:\\s*(.*)$", Pattern.MULTILINE);
private static final Pattern DIRECTIVE_PATTERN = Pattern.compile("^//\\s*!([\\w_]+)(:\\s*(.*)$)?", Pattern.MULTILINE); private static final Pattern DIRECTIVE_PATTERN = Pattern.compile("^//\\s*!([\\w_]+)(:\\s*(.*)$)?", Pattern.MULTILINE);
@@ -610,7 +610,7 @@ public class KotlinTestUtils {
public interface TestFileFactory<M, F> { public interface TestFileFactory<M, F> {
F createFile(@Nullable M module, @NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives); F createFile(@Nullable M module, @NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives);
M createModule(@NotNull String name, @NotNull List<String> dependencies); M createModule(@NotNull String name, @NotNull List<String> dependencies, @NotNull List<String> friends);
} }
public static abstract class TestFileFactoryNoModules<F> implements TestFileFactory<Void, F> { public static abstract class TestFileFactoryNoModules<F> implements TestFileFactory<Void, F> {
@@ -628,7 +628,7 @@ public class KotlinTestUtils {
public abstract F create(@NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives); public abstract F create(@NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives);
@Override @Override
public Void createModule(@NotNull String name, @NotNull List<String> dependencies) { public Void createModule(@NotNull String name, @NotNull List<String> dependencies, @NotNull List<String> friends) {
return null; return null;
} }
} }
@@ -651,12 +651,13 @@ public class KotlinTestUtils {
while (true) { while (true) {
String moduleName = matcher.group(1); String moduleName = matcher.group(1);
String moduleDependencies = matcher.group(2); String moduleDependencies = matcher.group(2);
String moduleFriends = matcher.group(3);
if (moduleName != null) { if (moduleName != null) {
hasModules = true; hasModules = true;
module = factory.createModule(moduleName, parseDependencies(moduleDependencies)); module = factory.createModule(moduleName, parseModuleList(moduleDependencies), parseModuleList(moduleFriends));
} }
String fileName = matcher.group(3); String fileName = matcher.group(4);
int start = processedChars; int start = processedChars;
boolean nextFileExists = matcher.find(); boolean nextFileExists = matcher.find();
@@ -681,7 +682,7 @@ public class KotlinTestUtils {
} }
if (isDirectiveDefined(expectedText, "WITH_COROUTINES")) { if (isDirectiveDefined(expectedText, "WITH_COROUTINES")) {
M supportModule = hasModules ? factory.createModule("support", Collections.emptyList()) : null; M supportModule = hasModules ? factory.createModule("support", Collections.emptyList(), Collections.emptyList()) : null;
testFiles.add(factory.createFile(supportModule, testFiles.add(factory.createFile(supportModule,
"CoroutineUtil.kt", "CoroutineUtil.kt",
"import kotlin.coroutines.experimental.*\n" + "import kotlin.coroutines.experimental.*\n" +
@@ -715,7 +716,7 @@ public class KotlinTestUtils {
return testFiles; return testFiles;
} }
private static List<String> parseDependencies(@Nullable String dependencies) { private static List<String> parseModuleList(@Nullable String dependencies) {
if (dependencies == null) return Collections.emptyList(); if (dependencies == null) return Collections.emptyList();
return StringsKt.split(dependencies, Pattern.compile(MODULE_DELIMITER), 0); return StringsKt.split(dependencies, Pattern.compile(MODULE_DELIMITER), 0);
} }
@@ -10535,6 +10535,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
doTest(fileName); doTest(fileName);
} }
@TestMetadata("internal.kt")
public void testInternal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internal.kt");
doTest(fileName);
}
@TestMetadata("internalOverride.kt") @TestMetadata("internalOverride.kt")
public void testInternalOverride() throws Exception { public void testInternalOverride() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internalOverride.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internalOverride.kt");
@@ -10535,6 +10535,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("internal.kt")
public void testInternal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internal.kt");
doTest(fileName);
}
@TestMetadata("internalOverride.kt") @TestMetadata("internalOverride.kt")
public void testInternalOverride() throws Exception { public void testInternalOverride() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internalOverride.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internalOverride.kt");
@@ -10535,6 +10535,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
doTest(fileName); doTest(fileName);
} }
@TestMetadata("internal.kt")
public void testInternal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internal.kt");
doTest(fileName);
}
@TestMetadata("internalOverride.kt") @TestMetadata("internalOverride.kt")
public void testInternalOverride() throws Exception { public void testInternalOverride() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internalOverride.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internalOverride.kt");
@@ -97,6 +97,10 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
setDependencies(ModuleDependenciesImpl(descriptors, emptySet())) setDependencies(ModuleDependenciesImpl(descriptors, emptySet()))
} }
fun setDependencies(descriptors: List<ModuleDescriptorImpl>, friends: Set<ModuleDescriptorImpl>) {
setDependencies(ModuleDependenciesImpl(descriptors, friends))
}
override fun shouldSeeInternalsOf(targetModule: ModuleDescriptor): Boolean { override fun shouldSeeInternalsOf(targetModule: ModuleDescriptor): Boolean {
return this == targetModule || targetModule in dependencies!!.modulesWhoseInternalsAreVisible return this == targetModule || targetModule in dependencies!!.modulesWhoseInternalsAreVisible
} }
@@ -373,6 +373,11 @@ class KotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() {
makeAll().assertSuccessful() makeAll().assertSuccessful()
} }
fun testKotlinJavaScriptInternalFromSpecialRelatedModule() {
initProject(JS_STDLIB)
makeAll().assertSuccessful()
}
fun testKotlinJavaScriptProjectWithTests() { fun testKotlinJavaScriptProjectWithTests() {
initProject(JS_STDLIB) initProject(JS_STDLIB)
makeAll().assertSuccessful() makeAll().assertSuccessful()
@@ -82,6 +82,7 @@ class JpsKotlinCompilerRunner : KotlinCompilerRunner<JpsCompilerEnvironment>() {
environment: JpsCompilerEnvironment, environment: JpsCompilerEnvironment,
sourceFiles: Collection<File>, sourceFiles: Collection<File>,
libraries: List<String>, libraries: List<String>,
friendModules: List<String>,
outputFile: File outputFile: File
) { ) {
log.debug("K2JS: common arguments: " + ArgumentUtils.convertArgumentsToStringList(commonArguments)) log.debug("K2JS: common arguments: " + ArgumentUtils.convertArgumentsToStringList(commonArguments))
@@ -90,7 +91,7 @@ class JpsKotlinCompilerRunner : KotlinCompilerRunner<JpsCompilerEnvironment>() {
val arguments = mergeBeans(commonArguments, XmlSerializerUtil.createCopy(k2jsArguments)) val arguments = mergeBeans(commonArguments, XmlSerializerUtil.createCopy(k2jsArguments))
log.debug("K2JS: merged arguments: " + ArgumentUtils.convertArgumentsToStringList(arguments)) 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)) log.debug("K2JS: arguments after setup" + ArgumentUtils.convertArgumentsToStringList(arguments))
withCompilerSettings(compilerSettings) { 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) { with(settings) {
noStdlib = true noStdlib = true
freeArgs = sourceFiles.map { it.path } freeArgs = sourceFiles.map { it.path }
outputFile = _outputFile.path outputFile = _outputFile.path
metaInfo = true metaInfo = true
libraries = _libraries.joinToString(File.pathSeparator) libraries = _libraries.joinToString(File.pathSeparator)
friendModules = _friendModules.joinToString(File.pathSeparator)
} }
} }
@@ -51,18 +51,16 @@ object JpsJsModuleUtils {
if (module.moduleType != JpsJavaModuleType.INSTANCE) return if (module.moduleType != JpsJavaModuleType.INSTANCE) return
if ((module != target.module || target.isTests) && module.sourceRoots.any { it.rootType == JavaSourceRootType.SOURCE}) { 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}) { 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) { fun addTarget(module: JpsModule, isTests: Boolean) {
val moduleBuildTarget = ModuleBuildTarget(module, targetType) val metaInfoFile = getOutputMetaFile(module, isTests)
val outputDir = KotlinBuilderModuleScriptGenerator.getOutputDirSafe(moduleBuildTarget)
val metaInfoFile = getOutputMetaFile(outputDir, module.name, targetType.isTests)
if (metaInfoFile.exists()) { if (metaInfoFile.exists()) {
result.add(metaInfoFile.absolutePath) 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 @JvmStatic
fun getOutputFile(outputDir: File, moduleName: String, isTests: Boolean) fun getOutputFile(outputDir: File, moduleName: String, isTests: Boolean)
= File(outputDir, moduleName + suffix(isTests) + KotlinJavascriptMetadataUtils.JS_EXT) = 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.*
import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings
import org.jetbrains.kotlin.jps.build.JpsJsModuleUtils.getOutputMetaFile
import org.jetbrains.kotlin.jps.incremental.* import org.jetbrains.kotlin.jps.incremental.*
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents 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 compilerSettings = JpsKotlinCompilerSettings.getCompilerSettings(representativeModule)
val k2JsArguments = JpsKotlinCompilerSettings.getK2JsCompilerArguments(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() 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 return environment.outputItemsCollector
} }
@@ -132,20 +132,21 @@ object KotlinBuilderModuleScriptGenerator {
fun getOutputDirSafe(target: ModuleBuildTarget): File = fun getOutputDirSafe(target: ModuleBuildTarget): File =
target.outputDir ?: throw ProjectBuildException("No output directory found for " + target) target.outputDir ?: throw ProjectBuildException("No output directory found for " + target)
private fun getAdditionalOutputDirsWhereInternalsAreVisible(target: ModuleBuildTarget): List<File> { fun getProductionModulesWhichInternalsAreVisible(from: ModuleBuildTarget): List<JpsModule> {
if (!target.isTests) return emptyList() if (!from.isTests) return emptyList()
val result = SmartList<File>() val result = SmartList<JpsModule>(from.module)
result.addIfNotNull(getRelatedProductionModule(from.module))
result.addIfNotNull(JpsJavaExtensionService.getInstance().getOutputDirectory(target.module, false))
getRelatedProductionModule(target.module)?.let {
result.addIfNotNull(JpsJavaExtensionService.getInstance().getOutputDirectory(it, false))
}
return result 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> { private fun findClassPathRoots(target: ModuleBuildTarget): Collection<File> {
return getAllDependencies(target).classes().roots.filter { file -> return getAllDependencies(target).classes().roots.filter { file ->
if (!file.exists()) { 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() { fun main() {
} }
internal fun internalFun() {}
internal val internalVal = 10
@@ -1,3 +1,5 @@
fun testMain() { fun testMain() {
main() main()
} internalFun()
var a = internalVal
}
@@ -39,10 +39,11 @@ object TopDownAnalyzerFacadeForJS {
val context = ContextForNewModule( val context = ContextForNewModule(
ProjectContext(config.project), Name.special("<${config.moduleId}>"), JsPlatform.builtIns, null ProjectContext(config.project), Name.special("<${config.moduleId}>"), JsPlatform.builtIns, null
) )
context.setDependencies( context.module.setDependencies(
listOf(context.module) + listOf(context.module) +
config.moduleDescriptors.map { it.data } + config.moduleDescriptors.map { it.data } +
listOf(JsPlatform.builtIns.builtInsModule) listOf(JsPlatform.builtIns.builtInsModule),
config.friendModuleDescriptors.map { it.data }.toSet()
) )
val trace = BindingTraceContext() val trace = BindingTraceContext()
trace.record(MODULE_KIND, context.module, config.moduleKind) trace.record(MODULE_KIND, context.module, config.moduleKind)
@@ -45,4 +45,10 @@ public class JSConfigurationKeys {
CompilerConfigurationKey.create("fallback metadata"); CompilerConfigurationKey.create("fallback metadata");
public static final CompilerConfigurationKey<Boolean> SERIALIZE_FRAGMENTS = CompilerConfigurationKey.create("serialize fragments"); public static final CompilerConfigurationKey<Boolean> SERIALIZE_FRAGMENTS = CompilerConfigurationKey.create("serialize fragments");
public static final CompilerConfigurationKey<Boolean> FRIEND_PATHS_DISABLED =
CompilerConfigurationKey.create("disable support for friend paths");
public static final CompilerConfigurationKey<List<String>> FRIEND_PATHS =
CompilerConfigurationKey.create("friend module paths");
} }
@@ -26,7 +26,7 @@ import com.intellij.util.SmartList;
import com.intellij.util.io.URLUtil; import com.intellij.util.io.URLUtil;
import kotlin.Unit; import kotlin.Unit;
import kotlin.collections.CollectionsKt; import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1; import kotlin.jvm.functions.Function2;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.config.*; import org.jetbrains.kotlin.config.*;
@@ -63,10 +63,14 @@ public class JsConfig {
private final LockBasedStorageManager storageManager = new LockBasedStorageManager(); private final LockBasedStorageManager storageManager = new LockBasedStorageManager();
private final List<KotlinJavascriptMetadata> metadata = new SmartList<>(); private final List<KotlinJavascriptMetadata> metadata = new SmartList<>();
private final List<KotlinJavascriptMetadata> friends = new SmartList<>();
@Nullable @Nullable
private List<JsModuleDescriptor<ModuleDescriptorImpl>> moduleDescriptors = null; private List<JsModuleDescriptor<ModuleDescriptorImpl>> moduleDescriptors = null;
@Nullable
private List<JsModuleDescriptor<ModuleDescriptorImpl>> friendModuleDescriptors = null;
private boolean initialized = false; private boolean initialized = false;
public JsConfig(@NotNull Project project, @NotNull CompilerConfiguration configuration) { public JsConfig(@NotNull Project project, @NotNull CompilerConfiguration configuration) {
@@ -99,6 +103,13 @@ public class JsConfig {
return getConfiguration().getList(JSConfigurationKeys.LIBRARIES); return getConfiguration().getList(JSConfigurationKeys.LIBRARIES);
} }
@NotNull
public List<String> getFriends() {
if (getConfiguration().getBoolean(JSConfigurationKeys.FRIEND_PATHS_DISABLED)) return Collections.emptyList();
return getConfiguration().getList(JSConfigurationKeys.FRIEND_PATHS);
}
public static abstract class Reporter { public static abstract class Reporter {
public void error(@NotNull String message) { /*Do nothing*/ } public void error(@NotNull String message) { /*Do nothing*/ }
@@ -109,8 +120,15 @@ public class JsConfig {
return checkLibFilesAndReportErrors(report, null); return checkLibFilesAndReportErrors(report, null);
} }
private boolean checkLibFilesAndReportErrors(@NotNull JsConfig.Reporter report, @Nullable Function1<VirtualFile, Unit> action) { private boolean checkLibFilesAndReportErrors(@NotNull JsConfig.Reporter report, @Nullable Function2<VirtualFile, String, Unit> action) {
List<String> libraries = getLibraries(); return checkLibFilesAndReportErrors(getLibraries(), report, action);
}
private boolean checkLibFilesAndReportErrors(
@NotNull Collection<String> libraries,
@NotNull JsConfig.Reporter report,
@Nullable Function2<VirtualFile, String, Unit> action
) {
if (libraries.isEmpty()) { if (libraries.isEmpty()) {
return false; return false;
} }
@@ -163,7 +181,7 @@ public class JsConfig {
} }
if (action != null) { if (action != null) {
action.invoke(file); action.invoke(file, path);
} }
} }
@@ -193,49 +211,85 @@ public class JsConfig {
return moduleDescriptors; return moduleDescriptors;
} }
@NotNull
public List<JsModuleDescriptor<ModuleDescriptorImpl>> getFriendModuleDescriptors() {
init();
if (friendModuleDescriptors != null) return friendModuleDescriptors;
friendModuleDescriptors = new SmartList<>();
for (KotlinJavascriptMetadata metadataEntry : friends) {
JsModuleDescriptor<ModuleDescriptorImpl> descriptor = createModuleDescriptor(metadataEntry);
friendModuleDescriptors.add(descriptor);
}
friendModuleDescriptors = Collections.unmodifiableList(friendModuleDescriptors);
return friendModuleDescriptors;
}
private void init() { private void init() {
if (initialized) return; if (initialized) return;
if (!getLibraries().isEmpty()) { if (!getLibraries().isEmpty()) {
Function1<VirtualFile, Unit> action = file -> { JsConfig.Reporter reporter = new Reporter() {
String libraryPath = PathUtil.getLocalPath(file);
assert libraryPath != null : "libraryPath for " + file + " should not be null";
metadata.addAll(KotlinJavascriptMetadataUtils.loadMetadata(libraryPath));
return Unit.INSTANCE;
};
boolean hasErrors = checkLibFilesAndReportErrors(new Reporter() {
@Override @Override
public void error(@NotNull String message) { public void error(@NotNull String message) {
throw new IllegalStateException(message); throw new IllegalStateException(message);
} }
}, action); };
boolean hasErrors = checkLibFilesAndReportErrors(getFriends(), reporter, (file, path) -> {
List<KotlinJavascriptMetadata> metaList = loadMetadata(file, "friendPath");
metadata.addAll(metaList);
friends.addAll(metaList);
return Unit.INSTANCE;
});
hasErrors |= checkLibFilesAndReportErrors(CollectionsKt.subtract(getLibraries(), getFriends()), reporter, (file, path) -> {
metadata.addAll(loadMetadata(file, "libraryPath"));
return Unit.INSTANCE;
});
assert !hasErrors : "hasErrors should be false"; assert !hasErrors : "hasErrors should be false";
} }
initialized = true; initialized = true;
} }
@NotNull
private static List<KotlinJavascriptMetadata> loadMetadata(@NotNull VirtualFile file, @NotNull String name) {
String libraryPath = PathUtil.getLocalPath(file);
assert libraryPath != null : name + " for " + file + " should not be null";
return KotlinJavascriptMetadataUtils.loadMetadata(libraryPath);
}
private final IdentityHashMap<KotlinJavascriptMetadata, JsModuleDescriptor<ModuleDescriptorImpl>> factoryMap = new IdentityHashMap<>();
private JsModuleDescriptor<ModuleDescriptorImpl> createModuleDescriptor(KotlinJavascriptMetadata metadata) { private JsModuleDescriptor<ModuleDescriptorImpl> createModuleDescriptor(KotlinJavascriptMetadata metadata) {
LanguageVersionSettings languageVersionSettings = CommonConfigurationKeysKt.getLanguageVersionSettings(configuration); return factoryMap.computeIfAbsent(metadata, m -> {
assert metadata.getVersion().isCompatible() || LanguageVersionSettings languageVersionSettings = CommonConfigurationKeysKt.getLanguageVersionSettings(configuration);
languageVersionSettings.isFlagEnabled(AnalysisFlags.getSkipMetadataVersionCheck()) : assert m.getVersion().isCompatible() ||
"Expected JS metadata version " + JsMetadataVersion.INSTANCE + ", but actual metadata version is " + metadata.getVersion(); languageVersionSettings.isFlagEnabled(AnalysisFlags.getSkipMetadataVersionCheck()) :
"Expected JS metadata version " + JsMetadataVersion.INSTANCE + ", but actual metadata version is " + m.getVersion();
ModuleDescriptorImpl moduleDescriptor = new ModuleDescriptorImpl( ModuleDescriptorImpl moduleDescriptor = new ModuleDescriptorImpl(
Name.special("<" + metadata.getModuleName() + ">"), storageManager, JsPlatform.INSTANCE.getBuiltIns() Name.special("<" + m.getModuleName() + ">"), storageManager, JsPlatform.INSTANCE.getBuiltIns()
); );
JsModuleDescriptor<PackageFragmentProvider> rawDescriptor = KotlinJavascriptSerializationUtil.readModule( JsModuleDescriptor<PackageFragmentProvider> rawDescriptor = KotlinJavascriptSerializationUtil.readModule(
metadata.getBody(), storageManager, moduleDescriptor, m.getBody(), storageManager, moduleDescriptor,
new CompilerDeserializationConfiguration(languageVersionSettings) new CompilerDeserializationConfiguration(languageVersionSettings)
); );
PackageFragmentProvider provider = rawDescriptor.getData(); PackageFragmentProvider provider = rawDescriptor.getData();
moduleDescriptor.initialize(provider != null ? provider : PackageFragmentProvider.Empty.INSTANCE); moduleDescriptor.initialize(provider != null ? provider : PackageFragmentProvider.Empty.INSTANCE);
return rawDescriptor.copy(moduleDescriptor); return rawDescriptor.copy(moduleDescriptor);
});
} }
private static void setDependencies(ModuleDescriptorImpl module, List<ModuleDescriptorImpl> modules) { private static void setDependencies(ModuleDescriptorImpl module, List<ModuleDescriptorImpl> modules) {
@@ -273,38 +273,40 @@ class NameSuggestion {
} }
fun mangledAndStable() = NameAndStability(getStableMangledName(baseName, encodeSignature(descriptor)), true) fun mangledAndStable() = NameAndStability(getStableMangledName(baseName, encodeSignature(descriptor)), true)
fun mangledInternal() = NameAndStability(getInternalMangledName(baseName, encodeSignature(descriptor)), true)
fun mangledPrivate() = NameAndStability(getPrivateMangledName(baseName, descriptor), false) fun mangledPrivate() = NameAndStability(getPrivateMangledName(baseName, descriptor), false)
val effectiveVisibility = descriptor.ownEffectiveVisibility val effectiveVisibility = descriptor.ownEffectiveVisibility
val containingDeclaration = descriptor.containingDeclaration val containingDeclaration = descriptor.containingDeclaration
return when (containingDeclaration) { return when (containingDeclaration) {
is PackageFragmentDescriptor -> if (effectiveVisibility.isPublicAPI) mangledAndStable() else regularAndUnstable() is PackageFragmentDescriptor -> when {
is ClassDescriptor -> { effectiveVisibility.isPublicAPI -> mangledAndStable()
effectiveVisibility == Visibilities.INTERNAL -> mangledInternal()
else -> regularAndUnstable()
}
is ClassDescriptor -> when {
// valueOf() is created in the library with a mangled name for every enum class // valueOf() is created in the library with a mangled name for every enum class
if (descriptor is FunctionDescriptor && descriptor.isEnumValueOfMethod()) return mangledAndStable() descriptor is FunctionDescriptor && descriptor.isEnumValueOfMethod() -> mangledAndStable()
// Make all public declarations stable // Make all public declarations stable
if (effectiveVisibility == Visibilities.PUBLIC) { effectiveVisibility == Visibilities.PUBLIC -> mangledAndStable()
return mangledAndStable()
}
if (descriptor is CallableMemberDescriptor && descriptor.isOverridableOrOverrides) return mangledAndStable() descriptor is CallableMemberDescriptor && descriptor.isOverridableOrOverrides -> mangledAndStable()
// Make all protected declarations of non-final public classes stable // Make all protected declarations of non-final public classes stable
if (effectiveVisibility == Visibilities.PROTECTED && effectiveVisibility == Visibilities.PROTECTED &&
!containingDeclaration.isFinalClass && !containingDeclaration.isFinalClass &&
containingDeclaration.visibility.isPublicAPI containingDeclaration.visibility.isPublicAPI -> mangledAndStable()
) {
return mangledAndStable() effectiveVisibility == Visibilities.INTERNAL -> mangledInternal()
}
// Mangle (but make unstable) all non-public API of public classes // Mangle (but make unstable) all non-public API of public classes
if (containingDeclaration.visibility.isPublicAPI && !containingDeclaration.isFinalClass) { containingDeclaration.visibility.isPublicAPI && !containingDeclaration.isFinalClass -> mangledPrivate()
return mangledPrivate()
}
regularAndUnstable() else -> regularAndUnstable()
} }
else -> { else -> {
assert(containingDeclaration is CallableMemberDescriptor) { assert(containingDeclaration is CallableMemberDescriptor) {
@@ -323,6 +325,11 @@ class NameSuggestion {
return getStableMangledName(baseName, ownerName + ":" + encodeSignature(descriptor)) return getStableMangledName(baseName, ownerName + ":" + encodeSignature(descriptor))
} }
fun getInternalMangledName(suggestedName: String, forCalculateId: String): String {
val suffix = "_${mangledId("internal:" + forCalculateId)}\$"
return suggestedName + suffix
}
@JvmStatic fun getStableMangledName(suggestedName: String, forCalculateId: String): String { @JvmStatic fun getStableMangledName(suggestedName: String, forCalculateId: String): String {
val suffix = if (forCalculateId.isEmpty()) "" else "_${mangledId(forCalculateId)}\$" val suffix = if (forCalculateId.isEmpty()) "" else "_${mangledId(forCalculateId)}\$"
return suggestedName + suffix return suggestedName + suffix
@@ -96,9 +96,10 @@ abstract class BasicBoxTest(
val generatedJsFiles = orderedModules.asReversed().mapNotNull { module -> val generatedJsFiles = orderedModules.asReversed().mapNotNull { module ->
val dependencies = module.dependencies.mapNotNull { modules[it]?.outputFileName(outputDir) + ".meta.js" } val dependencies = module.dependencies.mapNotNull { modules[it]?.outputFileName(outputDir) + ".meta.js" }
val friends = module.friends.mapNotNull { modules[it]?.outputFileName(outputDir) + ".meta.js" }
val outputFileName = module.outputFileName(outputDir) + ".js" val outputFileName = module.outputFileName(outputDir) + ".js"
generateJavaScriptFile(file.parent, module, outputFileName, dependencies, modules.size > 1, generateJavaScriptFile(file.parent, module, outputFileName, dependencies, friends, modules.size > 1,
outputPrefixFile, outputPostfixFile, mainCallParameters) outputPrefixFile, outputPostfixFile, mainCallParameters)
if (!module.name.endsWith(OLD_MODULE_SUFFIX)) outputFileName else null if (!module.name.endsWith(OLD_MODULE_SUFFIX)) outputFileName else null
@@ -222,6 +223,7 @@ abstract class BasicBoxTest(
module: TestModule, module: TestModule,
outputFileName: String, outputFileName: String,
dependencies: List<String>, dependencies: List<String>,
friends: List<String>,
multiModule: Boolean, multiModule: Boolean,
outputPrefixFile: File?, outputPrefixFile: File?,
outputPostfixFile: File?, outputPostfixFile: File?,
@@ -239,7 +241,7 @@ abstract class BasicBoxTest(
val additionalFiles = globalCommonFiles + localCommonFiles + additionalCommonFiles val additionalFiles = globalCommonFiles + localCommonFiles + additionalCommonFiles
val psiFiles = createPsiFiles(testFiles + additionalFiles) val psiFiles = createPsiFiles(testFiles + additionalFiles)
val config = createConfig(module, dependencies, multiModule, additionalMetadata = null) val config = createConfig(module, dependencies, friends, multiModule, additionalMetadata = null)
val outputFile = File(outputFileName) val outputFile = File(outputFileName)
translateFiles(psiFiles.map(TranslationUnit::SourceFile), outputFile, config, outputPrefixFile, outputPostfixFile, mainCallParameters) translateFiles(psiFiles.map(TranslationUnit::SourceFile), outputFile, config, outputPrefixFile, outputPostfixFile, mainCallParameters)
@@ -263,7 +265,7 @@ abstract class BasicBoxTest(
} }
val headerFile = File(incrementalDir, HEADER_FILE) val headerFile = File(incrementalDir, HEADER_FILE)
val recompiledConfig = createConfig(module, dependencies, multiModule, Pair(headerFile,serializedMetadata)) val recompiledConfig = createConfig(module, dependencies, friends, multiModule, Pair(headerFile,serializedMetadata))
val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js") val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js")
translateFiles(allTranslationUnits, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile, translateFiles(allTranslationUnits, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile,
@@ -357,7 +359,7 @@ abstract class BasicBoxTest(
private fun createPsiFiles(fileNames: List<String>): List<KtFile> = fileNames.map(this::createPsiFile) private fun createPsiFiles(fileNames: List<String>): List<KtFile> = fileNames.map(this::createPsiFile)
private fun createConfig( private fun createConfig(
module: TestModule, dependencies: List<String>, multiModule: Boolean, additionalMetadata: Pair<File, List<File>>? module: TestModule, dependencies: List<String>, friends: List<String>, multiModule: Boolean, additionalMetadata: Pair<File, List<File>>?
): JsConfig { ): JsConfig {
val configuration = environment.configuration.copy() val configuration = environment.configuration.copy()
@@ -368,6 +370,7 @@ abstract class BasicBoxTest(
} }
configuration.put(JSConfigurationKeys.LIBRARIES, JsConfig.JS_STDLIB + JsConfig.JS_KOTLIN_TEST + dependencies) configuration.put(JSConfigurationKeys.LIBRARIES, JsConfig.JS_STDLIB + JsConfig.JS_KOTLIN_TEST + dependencies)
configuration.put(JSConfigurationKeys.FRIEND_PATHS, friends)
configuration.put(CommonConfigurationKeys.MODULE_NAME, module.name.removeSuffix(OLD_MODULE_SUFFIX)) configuration.put(CommonConfigurationKeys.MODULE_NAME, module.name.removeSuffix(OLD_MODULE_SUFFIX))
configuration.put(JSConfigurationKeys.MODULE_KIND, module.moduleKind) configuration.put(JSConfigurationKeys.MODULE_KIND, module.moduleKind)
@@ -397,7 +400,7 @@ abstract class BasicBoxTest(
private inner class TestFileFactoryImpl : TestFileFactory<TestModule, TestFile>, Closeable { private inner class TestFileFactoryImpl : TestFileFactory<TestModule, TestFile>, Closeable {
var testPackage: String? = null var testPackage: String? = null
val tmpDir = KotlinTestUtils.tmpDir("js-tests") val tmpDir = KotlinTestUtils.tmpDir("js-tests")
val defaultModule = TestModule(TEST_MODULE, emptyList()) val defaultModule = TestModule(TEST_MODULE, emptyList(), emptyList())
override fun createFile(module: TestModule?, fileName: String, text: String, directives: Map<String, String>): TestFile? { override fun createFile(module: TestModule?, fileName: String, text: String, directives: Map<String, String>): TestFile? {
val currentModule = module ?: defaultModule val currentModule = module ?: defaultModule
@@ -433,8 +436,8 @@ abstract class BasicBoxTest(
return TestFile(temporaryFile.absolutePath, currentModule, recompile = RECOMPILE_PATTERN.matcher(text).find()) return TestFile(temporaryFile.absolutePath, currentModule, recompile = RECOMPILE_PATTERN.matcher(text).find())
} }
override fun createModule(name: String, dependencies: List<String>): TestModule? { override fun createModule(name: String, dependencies: List<String>, friends: List<String>): TestModule? {
return TestModule(name, dependencies) return TestModule(name, dependencies, friends)
} }
override fun close() { override fun close() {
@@ -450,9 +453,11 @@ abstract class BasicBoxTest(
private class TestModule( private class TestModule(
val name: String, val name: String,
dependencies: List<String> dependencies: List<String>,
friends: List<String>
) { ) {
val dependencies = dependencies.toMutableList() val dependencies = dependencies.toMutableList()
val friends = friends.toMutableList()
var moduleKind = ModuleKind.PLAIN var moduleKind = ModuleKind.PLAIN
var inliningDisabled = false var inliningDisabled = false
val files = mutableListOf<TestFile>() val files = mutableListOf<TestFile>()
@@ -12072,6 +12072,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
} }
@TestMetadata("internal.kt")
public void testInternal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internal.kt");
doTest(fileName);
}
@TestMetadata("internalOverride.kt") @TestMetadata("internalOverride.kt")
public void testInternalOverride() throws Exception { public void testInternalOverride() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internalOverride.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/mangling/internalOverride.kt");
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.exportedPackage import org.jetbrains.kotlin.js.backend.ast.metadata.exportedPackage
import org.jetbrains.kotlin.js.backend.ast.metadata.exportedTag import org.jetbrains.kotlin.js.backend.ast.metadata.exportedTag
import org.jetbrains.kotlin.js.backend.ast.metadata.staticRef import org.jetbrains.kotlin.js.backend.ast.metadata.staticRef
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isLibraryObject import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isLibraryObject
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject
@@ -151,7 +152,14 @@ internal class DeclarationExporter(val context: StaticContext) {
private fun JsExpression.exportStatement(declaration: DeclarationDescriptor) = JsExpressionStatement(this).also { private fun JsExpression.exportStatement(declaration: DeclarationDescriptor) = JsExpressionStatement(this).also {
it.exportedTag = context.getTag(declaration) it.exportedTag = context.getTag(declaration)
} }
private fun EffectiveVisibility.publicOrInternal(): Boolean {
if (publicApi) return true
if (context.config.configuration.getBoolean(JSConfigurationKeys.FRIEND_PATHS_DISABLED)) return false
return toVisibility() == Visibilities.INTERNAL
}
private fun MemberDescriptor.shouldBeExported(force: Boolean) =
force || effectiveVisibility(checkPublishedApi = true).publicOrInternal() || AnnotationsUtils.getJsNameAnnotation(this) != null
} }
private fun MemberDescriptor.shouldBeExported(force: Boolean) =
force || effectiveVisibility(checkPublishedApi = true).publicApi || AnnotationsUtils.getJsNameAnnotation(this) != null
@@ -214,6 +214,7 @@ val SIMPLE = "baz"
val SIMPLE0 = "${SIMPLE}_0" val SIMPLE0 = "${SIMPLE}_0"
val NATIVE = SIMPLE val NATIVE = SIMPLE
val STABLE = "baz_za3lpa$" val STABLE = "baz_za3lpa$"
val INTERNAL = "baz_kcn2v3$"
fun box(): String { fun box(): String {
testGroup = "Top Level" testGroup = "Top Level"
@@ -225,7 +226,7 @@ fun box(): String {
testGroup = "Public Class" testGroup = "Public Class"
test(STABLE) { PublicClass().public_baz(0) } test(STABLE) { PublicClass().public_baz(0) }
test(NATIVE) { PublicClass().public_baz("native") } test(NATIVE) { PublicClass().public_baz("native") }
test(SIMPLE0) { PublicClass().internal_baz(0) } test(INTERNAL) { PublicClass().internal_baz(0) }
test(NATIVE) { PublicClass().internal_baz("native") } test(NATIVE) { PublicClass().internal_baz("native") }
test(SIMPLE0, PublicClass().call_private_baz) test(SIMPLE0, PublicClass().call_private_baz)
test(NATIVE, PublicClass().call_private_native_baz) test(NATIVE, PublicClass().call_private_native_baz)
@@ -233,7 +234,7 @@ fun box(): String {
testGroup = "Internal Class" testGroup = "Internal Class"
test(STABLE) { InternalClass().public_baz(0) } test(STABLE) { InternalClass().public_baz(0) }
test(NATIVE) { InternalClass().public_baz("native") } test(NATIVE) { InternalClass().public_baz("native") }
test(SIMPLE0) { InternalClass().internal_baz(0) } test(INTERNAL) { InternalClass().internal_baz(0) }
test(NATIVE) { InternalClass().internal_baz("native") } test(NATIVE) { InternalClass().internal_baz("native") }
test(SIMPLE0, InternalClass().call_private_baz) test(SIMPLE0, InternalClass().call_private_baz)
test(NATIVE, InternalClass().call_private_native_baz) test(NATIVE, InternalClass().call_private_native_baz)
@@ -241,7 +242,7 @@ fun box(): String {
testGroup = "Private Class" testGroup = "Private Class"
test(STABLE) { PrivateClass().public_baz(0) } test(STABLE) { PrivateClass().public_baz(0) }
test(NATIVE) { PrivateClass().public_baz("native") } test(NATIVE) { PrivateClass().public_baz("native") }
test(SIMPLE0) { PrivateClass().internal_baz(0) } test(INTERNAL) { PrivateClass().internal_baz(0) }
test(NATIVE) { PrivateClass().internal_baz("native") } test(NATIVE) { PrivateClass().internal_baz("native") }
test(SIMPLE0, PrivateClass().call_private_baz) test(SIMPLE0, PrivateClass().call_private_baz)
test(NATIVE, PrivateClass().call_private_native_baz) test(NATIVE, PrivateClass().call_private_native_baz)
@@ -249,7 +250,7 @@ fun box(): String {
testGroup = "Open Public Class" testGroup = "Open Public Class"
test(STABLE) { OpenPublicClass().public_baz(0) } test(STABLE) { OpenPublicClass().public_baz(0) }
test(NATIVE) { OpenPublicClass().public_baz("native") } test(NATIVE) { OpenPublicClass().public_baz("native") }
testMangledPrivate { OpenPublicClass().internal_baz(0) } test(INTERNAL) { OpenPublicClass().internal_baz(0) }
test(NATIVE) { OpenPublicClass().internal_baz("native") } test(NATIVE) { OpenPublicClass().internal_baz("native") }
testMangledPrivate(OpenPublicClass().call_private_baz) testMangledPrivate(OpenPublicClass().call_private_baz)
test(NATIVE, OpenPublicClass().call_private_native_baz) test(NATIVE, OpenPublicClass().call_private_native_baz)
@@ -257,7 +258,7 @@ fun box(): String {
testGroup = "Open Internal Class" testGroup = "Open Internal Class"
test(STABLE) { OpenInternalClass().public_baz(0) } test(STABLE) { OpenInternalClass().public_baz(0) }
test(NATIVE) { OpenInternalClass().public_baz("native") } test(NATIVE) { OpenInternalClass().public_baz("native") }
test(SIMPLE0) { OpenInternalClass().internal_baz(0) } test(INTERNAL) { OpenInternalClass().internal_baz(0) }
test(NATIVE) { OpenInternalClass().internal_baz("native") } test(NATIVE) { OpenInternalClass().internal_baz("native") }
test(SIMPLE0, OpenInternalClass().call_private_baz) test(SIMPLE0, OpenInternalClass().call_private_baz)
test(NATIVE, OpenInternalClass().call_private_native_baz) test(NATIVE, OpenInternalClass().call_private_native_baz)
@@ -265,7 +266,7 @@ fun box(): String {
testGroup = "Open Private Class" testGroup = "Open Private Class"
test(STABLE) { OpenPrivateClass().public_baz(0) } test(STABLE) { OpenPrivateClass().public_baz(0) }
test(NATIVE) { OpenPrivateClass().public_baz("native") } test(NATIVE) { OpenPrivateClass().public_baz("native") }
test(SIMPLE0) { OpenPrivateClass().internal_baz(0) } test(INTERNAL) { OpenPrivateClass().internal_baz(0) }
test(NATIVE) { OpenPrivateClass().internal_baz("native") } test(NATIVE) { OpenPrivateClass().internal_baz("native") }
test(SIMPLE0, OpenPrivateClass().call_private_baz) test(SIMPLE0, OpenPrivateClass().call_private_baz)
test(NATIVE, OpenPrivateClass().call_private_native_baz) test(NATIVE, OpenPrivateClass().call_private_native_baz)
@@ -14,9 +14,11 @@ fun test2(`p 1`: Int, `.p 2`: Int) = `+`(`p 1`, `.p 2`)
fun test3(): String { fun test3(): String {
val `#` = "K" val `#` = "K"
class ` `(private val `::`: String) { class ` `(private val `::`: String) {
internal fun `@`() = `::` + `#` private fun `@`() = `::` + `#`
operator fun invoke() = `@`()
} }
return ` `("O").`@`() return ` `("O")()
} }
fun test4(): String { fun test4(): String {
@@ -124,6 +124,15 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() {
} }
} }
@Test
fun testCompilerTestAccessInternalProduction() {
val project = Project("kotlin2JsInternalTest", "2.10")
project.build("runRhino") {
assertSuccessful()
}
}
@Test @Test
fun testJsCustomSourceSet() { fun testJsCustomSourceSet() {
val project = Project("kotlin2JsProjectWithCustomSourceset", "2.10") val project = Project("kotlin2JsProjectWithCustomSourceset", "2.10")
@@ -0,0 +1,50 @@
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin2js'
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
compile "org.mozilla:rhino:1.7.7.1"
}
task runRhino(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
workingDir = "${buildDir}/classes/"
main = 'org.mozilla.javascript.tools.shell.Main'
args = ["-opt", "-1", "-f", "kotlin.js", "-f", "main/kotlin2JsInternalTest_main.js", "-f", "test/kotlin2JsInternalTest_test.js", "-f", "check.js"]
}
build.doLast {
configurations.compile.each { File file ->
copy {
includeEmptyDirs = false
from zipTree(file.absolutePath)
into "${buildDir}/classes/"
include { fileTreeElement ->
def path = fileTreeElement.path
path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
}
}
}
copy {
from "."
include "check.js"
into "${buildDir}/classes/"
}
}
runRhino.dependsOn build
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2015 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.
*/
internal val CONST = "CONST"
open class PublicClass {
internal fun foo(): String = "foo"
internal val bar: String = "bar"
open internal fun baz(): String = "PublicClass.baz()"
}
internal data class InternalDataClass(val x: Int, val y: Int)
internal fun box(): String {
return "OK"
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2015 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.
*/
class PublicClassHeir : PublicClass() {
override internal fun baz(): String = "PublicClassHeir.baz()"
}
fun <T> assertEquals(e: T, a: T) {
if (e != a) throw Exception("Expected: $e, actual: $a")
}
fun test() {
assertEquals("CONST", CONST)
assertEquals("foo", PublicClass().foo())
assertEquals("bar", PublicClass().bar)
assertEquals("PublicClass.baz()", PublicClass().baz())
assertEquals("foo", PublicClassHeir().foo())
assertEquals("bar", PublicClassHeir().bar)
assertEquals("PublicClassHeir.baz()", PublicClassHeir().baz())
val data = InternalDataClass(10, 20)
assertEquals(10, data.x)
assertEquals(20, data.y)
assertEquals("OK", box())
}
@@ -4,6 +4,12 @@ package org.jetbrains.kotlin.gradle.dsl
interface KotlinJsOptions : org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions { interface KotlinJsOptions : org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions {
/**
* Disable internal declaration export
* Default value: false
*/
var friendModulesDisabled: kotlin.Boolean
/** /**
* Whether a main function should be called * Whether a main function should be called
* Possible values: "call", "noCall" * Possible values: "call", "noCall"
@@ -24,6 +24,11 @@ internal abstract class KotlinJsOptionsBase : org.jetbrains.kotlin.gradle.dsl.Ko
get() = verboseField ?: false get() = verboseField ?: false
set(value) { verboseField = value } set(value) { verboseField = value }
private var friendModulesDisabledField: kotlin.Boolean? = null
override var friendModulesDisabled: kotlin.Boolean
get() = friendModulesDisabledField ?: false
set(value) { friendModulesDisabledField = value }
private var mainField: kotlin.String? = null private var mainField: kotlin.String? = null
override var main: kotlin.String override var main: kotlin.String
get() = mainField ?: "call" get() = mainField ?: "call"
@@ -69,6 +74,7 @@ internal abstract class KotlinJsOptionsBase : org.jetbrains.kotlin.gradle.dsl.Ko
languageVersionField?.let { args.languageVersion = it } languageVersionField?.let { args.languageVersion = it }
suppressWarningsField?.let { args.suppressWarnings = it } suppressWarningsField?.let { args.suppressWarnings = it }
verboseField?.let { args.verbose = it } verboseField?.let { args.verbose = it }
friendModulesDisabledField?.let { args.friendModulesDisabled = it }
mainField?.let { args.main = it } mainField?.let { args.main = it }
metaInfoField?.let { args.metaInfo = it } metaInfoField?.let { args.metaInfo = it }
moduleKindField?.let { args.moduleKind = it } moduleKindField?.let { args.moduleKind = it }
@@ -85,6 +91,7 @@ internal fun org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments.fil
languageVersion = "1.1" languageVersion = "1.1"
suppressWarnings = false suppressWarnings = false
verbose = false verbose = false
friendModulesDisabled = false
main = "call" main = "call"
metaInfo = true metaInfo = true
moduleKind = "plain" moduleKind = "plain"
@@ -372,6 +372,8 @@ open class Kotlin2JsCompile() : AbstractKotlinCompile<K2JSCompilerArguments>(),
null null
} }
args.friendModules = friendDependency
logger.kotlinDebug("compiling with args ${ArgumentUtils.convertArgumentsToStringList(args)}") logger.kotlinDebug("compiling with args ${ArgumentUtils.convertArgumentsToStringList(args)}")
val messageCollector = GradleMessageCollector(logger) val messageCollector = GradleMessageCollector(logger)
@@ -106,13 +106,6 @@
<plugin> <plugin>
<artifactId>maven-invoker-plugin</artifactId> <artifactId>maven-invoker-plugin</artifactId>
<version>1.9</version> <version>1.9</version>
<dependencies>
<dependency>
<groupId>org.mozilla</groupId>
<artifactId>rhino</artifactId>
<version>1.7.7.1</version>
</dependency>
</dependencies>
<configuration> <configuration>
<projectsDirectory>src/it</projectsDirectory> <projectsDirectory>src/it</projectsDirectory>
<cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo> <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
@@ -0,0 +1 @@
this['test-js-accessToInternal-tests'].org.jetbrains.test();
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>test-js-accessToInternal</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-js</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>js</goal>
</goals>
<configuration>
<output>${project.basedir}/customOutput/</output>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-js</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>unpack</id>
<phase>package</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-js</artifactId>
<version>${kotlin.version}</version>
<type>jar</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/js/</outputDirectory>
<includes>**/*.js</includes>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,31 @@
/*
* 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
internal val CONST = "CONST"
open class PublicClass {
internal fun foo(): String = "foo"
internal val bar: String = "bar"
open internal fun baz(): String = "PublicClass.baz()"
}
internal data class InternalDataClass(val x: Int, val y: Int)
internal fun box(): String {
return "OK"
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2015 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
class PublicClassHeir : PublicClass() {
override internal fun baz(): String = "PublicClassHeir.baz()"
}
fun <T> assertEquals(e: T, a: T) {
if (e != a) throw Exception("Expected: $e, actual: $a")
}
fun test() {
assertEquals("CONST", CONST)
assertEquals("foo", PublicClass().foo())
assertEquals("bar", PublicClass().bar)
assertEquals("PublicClass.baz()", PublicClass().baz())
assertEquals("foo", PublicClassHeir().foo())
assertEquals("bar", PublicClassHeir().bar)
assertEquals("PublicClassHeir.baz()", PublicClassHeir().baz())
val data = InternalDataClass(10, 20)
assertEquals(10, data.x)
assertEquals(20, data.y)
}
@@ -0,0 +1,20 @@
import java.io.*;
import javax.script.*;
File file = new File(basedir, "target/js/test-js-accessToInternal.js");
if (!file.exists() || !file.isFile()) {
throw new FileNotFoundException("Could not find generated JS : " + file);
}
File testFile = new File(basedir, "target/test-js/test-js-accessToInternal-tests.js");
if (!testFile.exists() || !testFile.isFile()) {
throw new FileNotFoundException("Could not find generated JS : " + testFile);
}
String basePath = basedir.getPath();
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader(basePath + "/target/js/kotlin.js"));
engine.eval(new FileReader(basePath + "/target/js/test-js-accessToInternal.js"));
engine.eval(new FileReader(basePath + "/target/test-js/test-js-accessToInternal-tests.js"));
engine.eval(new FileReader(basePath + "/check.js"));
@@ -1,5 +1,5 @@
import java.io.*; import java.io.*;
import org.mozilla.javascript.tools.shell.Main; import javax.script.*;
File file = new File(basedir, "target/js/test-js-moduleKind.js"); File file = new File(basedir, "target/js/test-js-moduleKind.js");
if (!file.exists() || !file.isFile()) { if (!file.exists() || !file.isFile()) {
@@ -7,9 +7,9 @@ if (!file.exists() || !file.isFile()) {
} }
String basePath = basedir.getPath(); String basePath = basedir.getPath();
Main.main(new String[] { ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
"-O", "-1",
"-f", basePath + "/amd.js", engine.eval(new FileReader(basePath + "/amd.js"));
"-f", basePath + "/target/js/kotlin.js", engine.eval(new FileReader(basePath + "/target/js/kotlin.js"));
"-f", basePath + "/target/js/test-js-moduleKind.js", engine.eval(new FileReader(basePath + "/target/js/test-js-moduleKind.js"));
"-f", basePath + "/check.js"}) engine.eval(new FileReader(basePath + "/check.js"));
@@ -34,9 +34,11 @@ import org.jetbrains.kotlin.js.JavaScript;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Map;
import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@@ -109,15 +111,11 @@ public class K2JSCompilerMojo extends KotlinCompileMojoBase<K2JSCompilerArgument
arguments.sourceMap = sourceMap; arguments.sourceMap = sourceMap;
Set<String> collector = getOutputDirectoriesCollector();
if (outputFile != null) { if (outputFile != null) {
collector.add(new File(outputFile).getParent()); ConcurrentMap<String, List<String>> collector = getOutputDirectoriesCollector();
} String key = project.getArtifactId();
if (metaInfo) { List<String> paths = collector.computeIfAbsent(key, k -> Collections.synchronizedList(new ArrayList<String>()));
String output = com.google.common.base.Objects.firstNonNull(outputFile, ""); // fqname here because of J8 compatibility issues paths.add(new File(outputFile).getParent());
String metaFile = StringsKt.substringBeforeLast(output, JavaScript.DOT_EXTENSION, output) + KotlinJavascriptMetadataUtils.META_JS_SUFFIX;
collector.add(new File(metaFile).getParent());
} }
} }
@@ -145,14 +143,16 @@ public class K2JSCompilerMojo extends KotlinCompileMojoBase<K2JSCompilerArgument
} }
} }
for (String path : getOutputDirectoriesCollector()) { for (List<String> paths : getOutputDirectoriesCollector().values()) {
File file = new File(path); for (String path : paths) {
File file = new File(path);
if (file.exists() && LibraryUtils.isKotlinJavascriptLibrary(file)) { if (file.exists() && LibraryUtils.isKotlinJavascriptLibrary(file)) {
libraries.add(file.getAbsolutePath()); libraries.add(file.getAbsolutePath());
} }
else { else {
getLog().debug("JS output directory missing: " + file); getLog().debug("JS output directory missing: " + file);
}
} }
} }
@@ -177,12 +177,12 @@ public class K2JSCompilerMojo extends KotlinCompileMojoBase<K2JSCompilerArgument
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Set<String> getOutputDirectoriesCollector() { protected ConcurrentMap<String, List<String>> getOutputDirectoriesCollector() {
lock.lock(); lock.lock();
try { try {
Set<String> collector = (Set<String>) getPluginContext().get(OUTPUT_DIRECTORIES_COLLECTOR_PROPERTY_NAME); ConcurrentMap<String, List<String>> collector = (ConcurrentMap<String, List<String>>) getPluginContext().get(OUTPUT_DIRECTORIES_COLLECTOR_PROPERTY_NAME);
if (collector == null) { if (collector == null) {
collector = new ConcurrentSkipListSet<String>(); collector = new ConcurrentSkipListMap<String, List<String>>();
getPluginContext().put(OUTPUT_DIRECTORIES_COLLECTOR_PROPERTY_NAME, collector); getPluginContext().put(OUTPUT_DIRECTORIES_COLLECTOR_PROPERTY_NAME, collector);
} }
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.maven; package org.jetbrains.kotlin.maven;
import com.intellij.openapi.util.text.StringUtil;
import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.MojoFailureException;
@@ -27,8 +28,9 @@ import org.apache.maven.project.MavenProject;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments; import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments;
import java.io.File;
import java.util.List; import java.util.List;
import java.util.Collections;
/** /**
* Converts Kotlin to JavaScript code * Converts Kotlin to JavaScript code
* *
@@ -79,6 +81,8 @@ public class KotlinTestJSCompilerMojo extends K2JSCompilerMojo {
@Override @Override
protected void configureSpecificCompilerArguments(@NotNull K2JSCompilerArguments arguments) throws MojoExecutionException { protected void configureSpecificCompilerArguments(@NotNull K2JSCompilerArguments arguments) throws MojoExecutionException {
List<String> friends = getOutputDirectoriesCollector().getOrDefault(project.getArtifactId(), Collections.emptyList());
arguments.friendModules = StringUtil.join(friends, File.pathSeparator);
output = testOutput; output = testOutput;
super.configureSpecificCompilerArguments(arguments); super.configureSpecificCompilerArguments(arguments);