Fix memory leak in gradle import
#KT-30076 Fixed
This commit is contained in:
+6
-9
@@ -29,10 +29,7 @@ import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.tooling.model.idea.IdeaModule
|
||||
import org.jetbrains.kotlin.gradle.CompilerArgumentsBySourceSet
|
||||
import org.jetbrains.kotlin.gradle.KotlinGradleModel
|
||||
import org.jetbrains.kotlin.gradle.KotlinGradleModelBuilder
|
||||
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel
|
||||
import org.jetbrains.kotlin.gradle.*
|
||||
import org.jetbrains.kotlin.idea.inspections.gradle.getDependencyModules
|
||||
import org.jetbrains.kotlin.idea.util.CopyableDataNodeUserDataProperty
|
||||
import org.jetbrains.kotlin.idea.util.DataNodeUserDataProperty
|
||||
@@ -203,8 +200,8 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension()
|
||||
if (mppModel != null) {
|
||||
mppModel.targets.forEach { target ->
|
||||
KotlinStatisticsTrigger.trigger(
|
||||
KotlinEventTrigger.KotlinGradleTargetTrigger,
|
||||
"MPP.${target.platform.id + (target.presetName?.let { ".$it"} ?: "")}"
|
||||
KotlinEventTrigger.KotlinGradleTargetTrigger,
|
||||
"MPP.${target.platform.id + (target.presetName?.let { ".$it" } ?: "")}"
|
||||
)
|
||||
}
|
||||
return super.populateModuleDependencies(gradleModule, ideModule, ideProject)
|
||||
@@ -222,13 +219,13 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension()
|
||||
|
||||
ideModule.isResolved = true
|
||||
ideModule.hasKotlinPlugin = gradleModel.hasKotlinPlugin
|
||||
ideModule.compilerArgumentsBySourceSet = gradleModel.compilerArgumentsBySourceSet
|
||||
ideModule.compilerArgumentsBySourceSet = gradleModel.compilerArgumentsBySourceSet.deepCopy()
|
||||
ideModule.coroutines = gradleModel.coroutines
|
||||
ideModule.platformPluginId = gradleModel.platformPluginId
|
||||
|
||||
KotlinStatisticsTrigger.trigger(
|
||||
KotlinEventTrigger.KotlinGradleTargetTrigger,
|
||||
gradleModel.kotlinTarget ?: "unknown"
|
||||
KotlinEventTrigger.KotlinGradleTargetTrigger,
|
||||
gradleModel.kotlinTarget ?: "unknown"
|
||||
)
|
||||
|
||||
addImplementedModuleNames(gradleModule, ideModule, ideProject, gradleModel)
|
||||
|
||||
+9
-7
@@ -613,7 +613,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() {
|
||||
resolverCtx: ProjectResolverContext
|
||||
): KotlinSourceSetInfo? {
|
||||
if (sourceSet.platform.isNotSupported()) return null
|
||||
return KotlinSourceSetInfo(sourceSet).also { info ->
|
||||
return KotlinSourceSetInfo(KotlinSourceSetImpl(sourceSet)).also { info ->
|
||||
val languageSettings = sourceSet.languageSettings
|
||||
info.moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx)
|
||||
info.gradleModuleId = getModuleId(resolverCtx, gradleModule)
|
||||
@@ -643,16 +643,18 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() {
|
||||
resolverCtx: ProjectResolverContext
|
||||
): KotlinSourceSetInfo? {
|
||||
if (compilation.platform.isNotSupported()) return null
|
||||
return KotlinSourceSetInfo(compilation).also { sourceSetInfo ->
|
||||
return KotlinSourceSetInfo(KotlinCompilationImpl(compilation)).also { sourceSetInfo ->
|
||||
sourceSetInfo.moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx)
|
||||
sourceSetInfo.gradleModuleId = getModuleId(resolverCtx, gradleModule)
|
||||
sourceSetInfo.platform = compilation.platform
|
||||
sourceSetInfo.isTestModule = compilation.isTestModule
|
||||
sourceSetInfo.compilerArguments = createCompilerArguments(compilation.arguments.currentArguments, compilation.platform).also {
|
||||
it.multiPlatform = true
|
||||
}
|
||||
sourceSetInfo.compilerArguments =
|
||||
createCompilerArguments(compilation.arguments.currentArguments, compilation.platform).also {
|
||||
it.multiPlatform = true
|
||||
}
|
||||
sourceSetInfo.dependencyClasspath = compilation.dependencyClasspath
|
||||
sourceSetInfo.defaultCompilerArguments = createCompilerArguments(compilation.arguments.defaultArguments, compilation.platform)
|
||||
sourceSetInfo.defaultCompilerArguments =
|
||||
createCompilerArguments(compilation.arguments.defaultArguments, compilation.platform)
|
||||
sourceSetInfo.addSourceSets(compilation.sourceSets, compilation.fullName(), gradleModule, resolverCtx)
|
||||
}
|
||||
}
|
||||
@@ -679,7 +681,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() {
|
||||
}
|
||||
|
||||
private fun KotlinModule.fullName(simpleName: String = name) = when (this) {
|
||||
is KotlinCompilation -> compilationFullName(simpleName, target.disambiguationClassifier)
|
||||
is KotlinCompilation -> compilationFullName(simpleName, disambiguationClassifier)
|
||||
else -> simpleName
|
||||
}
|
||||
|
||||
|
||||
+71
-29
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.codeInsight.gradle;
|
||||
import com.intellij.openapi.application.AccessToken;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder;
|
||||
import com.intellij.openapi.externalSystem.model.DataNode;
|
||||
import com.intellij.openapi.externalSystem.model.ProjectSystemId;
|
||||
@@ -49,9 +50,13 @@ import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.*;
|
||||
import org.apache.log4j.Level;
|
||||
|
||||
// part of com.intellij.openapi.externalSystem.test.ExternalSystemImportingTestCase
|
||||
public abstract class ExternalSystemImportingTestCase extends ExternalSystemTestCase {
|
||||
|
||||
private Logger logger;
|
||||
|
||||
@Override
|
||||
protected Module getModule(String name) {
|
||||
AccessToken accessToken = ApplicationManager.getApplication().acquireReadActionLock();
|
||||
@@ -74,16 +79,26 @@ public abstract class ExternalSystemImportingTestCase extends ExternalSystemTest
|
||||
doImportProject();
|
||||
}
|
||||
|
||||
public void ensureIsNotGradleProxyObject(Object o, Map<Object, Object> referencingObjects) {
|
||||
if (o instanceof Proxy) {
|
||||
StringBuilder errMessage = new StringBuilder();
|
||||
errMessage.append(String.format("Object [%s] seems to be a referenced gradle tooling api object. (it may lead to memory leaks during import) Referencing path: ", o));
|
||||
while (o != null) {
|
||||
errMessage.append(String.format("[%s] type: %s <-\r\n", o, o.getClass().toString()));
|
||||
o = referencingObjects.get(o);
|
||||
}
|
||||
//TODO fail(errMessage.toString());
|
||||
public boolean ensureIsNotGradleProxyObject(
|
||||
Object o,
|
||||
Map<Object, Object> referencingObjects,
|
||||
Map<Object, String> referencingFieldNames
|
||||
) {
|
||||
if (!(o instanceof Proxy)) {
|
||||
return true;
|
||||
}
|
||||
StringBuilder errMessage = new StringBuilder();
|
||||
errMessage.append(String.format(
|
||||
"Object [%s] seems to be a referenced gradle tooling api object. (it may lead to memory leaks during import) Referencing path: ",
|
||||
o));
|
||||
while (o != null) {
|
||||
errMessage.append(String.format("[%s].[%s] type: %s <-\r\n", o, referencingFieldNames.get(o), o.getClass().toString()));
|
||||
o = referencingObjects.get(o);
|
||||
}
|
||||
System.err.println(errMessage.toString());
|
||||
//TODO uncomment after fixing IDEA-207782
|
||||
//fail(errMessage.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldBeProcessed(Object toProcess, Set<Object> processed) {
|
||||
@@ -91,56 +106,83 @@ public abstract class ExternalSystemImportingTestCase extends ExternalSystemTest
|
||||
}
|
||||
|
||||
public void inspectForGradleMemoryLeaks(DataNode<ProjectData> externalProject) {
|
||||
// Static logger initialisation should not be used because logger factory may be changed on test.setUp
|
||||
if (logger == null) {
|
||||
logger = Logger.getInstance(this.getClass().getName());
|
||||
logger.setLevel(Level.INFO);//not required
|
||||
}
|
||||
long start = System.currentTimeMillis();
|
||||
Set<Object> processed = new HashSet<>();
|
||||
Queue<Object> toProcess = new LinkedList<>();
|
||||
Map<Object, Object> referencingObjects = new HashMap<>();
|
||||
Map<Object, String> referencingFieldNames = new HashMap<>();
|
||||
Set<Field> modifiedFields = new HashSet<>();
|
||||
toProcess.add(externalProject);
|
||||
try {
|
||||
while (! toProcess.isEmpty()) {
|
||||
while (!toProcess.isEmpty()) {
|
||||
Object nextObject = toProcess.poll();
|
||||
processed.add(nextObject);
|
||||
ensureIsNotGradleProxyObject(nextObject, referencingObjects);
|
||||
if (!ensureIsNotGradleProxyObject(nextObject, referencingObjects, referencingFieldNames)) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (Field field : nextObject.getClass().getDeclaredFields()) {
|
||||
try {
|
||||
final Object fieldValue;
|
||||
boolean isAccessible = field.isAccessible();
|
||||
try {
|
||||
|
||||
if (!field.isAccessible()) {
|
||||
field.setAccessible(true);
|
||||
fieldValue = field.get(nextObject);
|
||||
} finally {
|
||||
field.setAccessible(isAccessible);
|
||||
modifiedFields.add(field);
|
||||
}
|
||||
if (fieldValue == null) {
|
||||
final Object fieldValue = field.get(nextObject);
|
||||
if (fieldValue == null || fieldValue.getClass().isPrimitive()) {
|
||||
continue;
|
||||
}
|
||||
if (fieldValue instanceof Collection) {
|
||||
for (Object o : (Collection)fieldValue) {
|
||||
saveToProcessIfRequired(processed, toProcess, referencingObjects, nextObject, o);
|
||||
for (Object o : (Collection) fieldValue) {
|
||||
saveToProcessIfRequired(processed, toProcess, referencingObjects, referencingFieldNames, nextObject, o,
|
||||
field.getName());
|
||||
}
|
||||
} else if (fieldValue.getClass().isArray()) {
|
||||
}
|
||||
else if (fieldValue.getClass().isArray()) {
|
||||
for (int i = 0; i < Array.getLength(fieldValue); i++) {
|
||||
Object o = Array.get(fieldValue, i);
|
||||
saveToProcessIfRequired(processed, toProcess, referencingObjects, nextObject, o);
|
||||
saveToProcessIfRequired(processed, toProcess, referencingObjects, referencingFieldNames, nextObject, o,
|
||||
field.getName());
|
||||
}
|
||||
} else {
|
||||
saveToProcessIfRequired(processed, toProcess, referencingObjects, nextObject, fieldValue);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
else {
|
||||
saveToProcessIfRequired(processed, toProcess, referencingObjects, referencingFieldNames, nextObject, fieldValue,
|
||||
field.getName());
|
||||
}
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} finally {
|
||||
System.out.println("Processed size = " + processed.size() + " Duration: " + (System.currentTimeMillis() - start));
|
||||
}
|
||||
finally {
|
||||
for (Field f : modifiedFields) {
|
||||
f.setAccessible(false);
|
||||
}
|
||||
logger.info(
|
||||
String.format("Memory leak tracker has finished. Number of processed objects = %s. Duration = %s ms.", processed.size(),
|
||||
(System.currentTimeMillis() - start)));
|
||||
}
|
||||
}
|
||||
|
||||
private void saveToProcessIfRequired(Set<Object> processed, Queue<Object> toProcess, Map<Object, Object> referrers, Object referringObject, Object o) {
|
||||
private void saveToProcessIfRequired(
|
||||
Set<Object> processed,
|
||||
Queue<Object> toProcess,
|
||||
Map<Object, Object> referrers,
|
||||
Map<Object, String> referencingFieldNames,
|
||||
Object referringObject,
|
||||
Object o,
|
||||
String fieldName
|
||||
) {
|
||||
if (shouldBeProcessed(o, processed)) {
|
||||
toProcess.add(o);
|
||||
referencingFieldNames.put(o, fieldName);
|
||||
referrers.put(o, referringObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,14 +34,30 @@ interface ArgsInfo : Serializable {
|
||||
val dependencyClasspath: List<String>
|
||||
}
|
||||
|
||||
class ArgsInfoImpl(
|
||||
data class ArgsInfoImpl(
|
||||
override val currentArguments: List<String>,
|
||||
override val defaultArguments: List<String>,
|
||||
override val dependencyClasspath: List<String>
|
||||
) : ArgsInfo
|
||||
) : ArgsInfo {
|
||||
|
||||
constructor(argsInfo: ArgsInfo) : this(
|
||||
ArrayList(argsInfo.currentArguments),
|
||||
ArrayList(argsInfo.defaultArguments),
|
||||
ArrayList(argsInfo.dependencyClasspath)
|
||||
)
|
||||
}
|
||||
|
||||
typealias CompilerArgumentsBySourceSet = Map<String, ArgsInfo>
|
||||
|
||||
/**
|
||||
* Creates deep copy in order to avoid holding links to Proxy objects created by gradle tooling api
|
||||
*/
|
||||
fun CompilerArgumentsBySourceSet.deepCopy(): CompilerArgumentsBySourceSet {
|
||||
val result = HashMap<String, ArgsInfo>()
|
||||
this.forEach { key, value -> result[key] = ArgsInfoImpl(value) }
|
||||
return result
|
||||
}
|
||||
|
||||
interface KotlinGradleModel : Serializable {
|
||||
val hasKotlinPlugin: Boolean
|
||||
val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet
|
||||
@@ -51,7 +67,7 @@ interface KotlinGradleModel : Serializable {
|
||||
val kotlinTarget: String?
|
||||
}
|
||||
|
||||
class KotlinGradleModelImpl(
|
||||
data class KotlinGradleModelImpl(
|
||||
override val hasKotlinPlugin: Boolean,
|
||||
override val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet,
|
||||
override val coroutines: String?,
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
package org.jetbrains.kotlin.gradle
|
||||
|
||||
import org.jetbrains.plugins.gradle.model.ExternalDependency
|
||||
import org.jetbrains.plugins.gradle.model.ModelFactory
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
|
||||
typealias KotlinDependency = ExternalDependency
|
||||
|
||||
fun KotlinDependency.deepCopy(): KotlinDependency = ModelFactory.createCopy(this)
|
||||
|
||||
interface KotlinModule : Serializable {
|
||||
val name: String
|
||||
val platform: KotlinPlatform
|
||||
@@ -55,10 +58,10 @@ interface KotlinCompilationArguments : Serializable {
|
||||
|
||||
interface KotlinCompilation : KotlinModule {
|
||||
val sourceSets: Collection<KotlinSourceSet>
|
||||
val target: KotlinTarget
|
||||
val output: KotlinCompilationOutput
|
||||
val arguments: KotlinCompilationArguments
|
||||
val dependencyClasspath: List<String>
|
||||
val disambiguationClassifier: String?
|
||||
|
||||
companion object {
|
||||
const val MAIN_COMPILATION_NAME = "main"
|
||||
|
||||
@@ -194,7 +194,10 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService {
|
||||
}
|
||||
val jar = buildTargetJar(gradleTarget, project)
|
||||
val target = KotlinTargetImpl(gradleTarget.name, targetPresetName, disambiguationClassifier, platform, compilations, jar)
|
||||
compilations.forEach { it.target = target }
|
||||
compilations.forEach {
|
||||
it.disambiguationClassifier = target.disambiguationClassifier
|
||||
it.platform = target.platform
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +185,10 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService {
|
||||
}
|
||||
val jar = buildTargetJar(gradleTarget, project)
|
||||
val target = KotlinTargetImpl(gradleTarget.name, null, disambiguationClassifier, platform, compilations, jar)
|
||||
compilations.forEach { it.target = target }
|
||||
compilations.forEach {
|
||||
it.disambiguationClassifier = target.disambiguationClassifier
|
||||
it.platform = target.platform
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.gradle
|
||||
|
||||
import java.io.File
|
||||
|
||||
class KotlinSourceSetImpl(
|
||||
data class KotlinSourceSetImpl(
|
||||
override val name: String,
|
||||
override val languageSettings: KotlinLanguageSettings,
|
||||
override val sourceDirs: Set<File>,
|
||||
@@ -15,6 +15,16 @@ class KotlinSourceSetImpl(
|
||||
override val dependencies: Set<KotlinDependency>,
|
||||
override val dependsOnSourceSets: Set<String>
|
||||
) : KotlinSourceSet {
|
||||
|
||||
constructor(kotlinSourceSet: KotlinSourceSet) : this(
|
||||
kotlinSourceSet.name,
|
||||
KotlinLanguageSettingsImpl(kotlinSourceSet.languageSettings),
|
||||
HashSet(kotlinSourceSet.sourceDirs),
|
||||
HashSet(kotlinSourceSet.resourceDirs),
|
||||
kotlinSourceSet.dependencies.map { it.deepCopy() }.toSet(),
|
||||
HashSet(kotlinSourceSet.dependsOnSourceSets)
|
||||
)
|
||||
|
||||
override var platform: KotlinPlatform = KotlinPlatform.COMMON
|
||||
internal set
|
||||
|
||||
@@ -24,7 +34,7 @@ class KotlinSourceSetImpl(
|
||||
override fun toString() = name
|
||||
}
|
||||
|
||||
class KotlinLanguageSettingsImpl(
|
||||
data class KotlinLanguageSettingsImpl(
|
||||
override val languageVersion: String?,
|
||||
override val apiVersion: String?,
|
||||
override val isProgressiveMode: Boolean,
|
||||
@@ -32,20 +42,38 @@ class KotlinLanguageSettingsImpl(
|
||||
override val experimentalAnnotationsInUse: Set<String>,
|
||||
override val compilerPluginArguments: List<String>,
|
||||
override val compilerPluginClasspath: Set<File>
|
||||
) : KotlinLanguageSettings
|
||||
) : KotlinLanguageSettings {
|
||||
constructor(settings: KotlinLanguageSettings) : this(
|
||||
settings.languageVersion,
|
||||
settings.apiVersion,
|
||||
settings.isProgressiveMode,
|
||||
settings.enabledLanguageFeatures,
|
||||
settings.experimentalAnnotationsInUse,
|
||||
settings.compilerPluginArguments,
|
||||
settings.compilerPluginClasspath
|
||||
)
|
||||
}
|
||||
|
||||
class KotlinCompilationOutputImpl(
|
||||
data class KotlinCompilationOutputImpl(
|
||||
override val classesDirs: Set<File>,
|
||||
override val effectiveClassesDir: File?,
|
||||
override val resourcesDir: File?
|
||||
) : KotlinCompilationOutput
|
||||
) : KotlinCompilationOutput {
|
||||
constructor(output: KotlinCompilationOutput) : this(
|
||||
HashSet(output.classesDirs),
|
||||
output.effectiveClassesDir,
|
||||
output.resourcesDir
|
||||
)
|
||||
}
|
||||
|
||||
class KotlinCompilationArgumentsImpl(
|
||||
data class KotlinCompilationArgumentsImpl(
|
||||
override val defaultArguments: List<String>,
|
||||
override val currentArguments: List<String>
|
||||
) : KotlinCompilationArguments
|
||||
) : KotlinCompilationArguments {
|
||||
constructor(arguments: KotlinCompilationArguments) : this(ArrayList(arguments.defaultArguments), ArrayList(arguments.currentArguments))
|
||||
}
|
||||
|
||||
class KotlinCompilationImpl(
|
||||
data class KotlinCompilationImpl(
|
||||
override val name: String,
|
||||
override val sourceSets: Collection<KotlinSourceSet>,
|
||||
override val dependencies: Set<KotlinDependency>,
|
||||
@@ -53,11 +81,25 @@ class KotlinCompilationImpl(
|
||||
override val arguments: KotlinCompilationArguments,
|
||||
override val dependencyClasspath: List<String>
|
||||
) : KotlinCompilation {
|
||||
override lateinit var target: KotlinTarget
|
||||
|
||||
// create deep copy
|
||||
constructor(kotlinCompilation: KotlinCompilation) : this(
|
||||
kotlinCompilation.name,
|
||||
kotlinCompilation.sourceSets.map { KotlinSourceSetImpl(it) }.toList(),
|
||||
kotlinCompilation.dependencies.map { it.deepCopy() }.toSet(),
|
||||
KotlinCompilationOutputImpl(kotlinCompilation.output),
|
||||
KotlinCompilationArgumentsImpl(kotlinCompilation.arguments),
|
||||
ArrayList(kotlinCompilation.dependencyClasspath)
|
||||
) {
|
||||
disambiguationClassifier = kotlinCompilation.disambiguationClassifier
|
||||
platform = kotlinCompilation.platform
|
||||
}
|
||||
|
||||
override var disambiguationClassifier: String? = null
|
||||
internal set
|
||||
override lateinit var platform: KotlinPlatform
|
||||
internal set
|
||||
|
||||
override val platform: KotlinPlatform
|
||||
get() = target.platform
|
||||
|
||||
override val isTestModule: Boolean
|
||||
get() = name == KotlinCompilation.TEST_COMPILATION_NAME
|
||||
@@ -66,11 +108,11 @@ class KotlinCompilationImpl(
|
||||
override fun toString() = name
|
||||
}
|
||||
|
||||
class KotlinTargetJarImpl(
|
||||
data class KotlinTargetJarImpl(
|
||||
override val archiveFile: File?
|
||||
) : KotlinTargetJar
|
||||
|
||||
class KotlinTargetImpl(
|
||||
data class KotlinTargetImpl(
|
||||
override val name: String,
|
||||
override val presetName: String?,
|
||||
override val disambiguationClassifier: String?,
|
||||
@@ -81,11 +123,11 @@ class KotlinTargetImpl(
|
||||
override fun toString() = name
|
||||
}
|
||||
|
||||
class ExtraFeaturesImpl(
|
||||
data class ExtraFeaturesImpl(
|
||||
override val coroutinesState: String?
|
||||
) : ExtraFeatures
|
||||
|
||||
class KotlinMPPGradleModelImpl(
|
||||
data class KotlinMPPGradleModelImpl(
|
||||
override val sourceSets: Map<String, KotlinSourceSet>,
|
||||
override val targets: Collection<KotlinTarget>,
|
||||
override val extraFeatures: ExtraFeatures,
|
||||
|
||||
Reference in New Issue
Block a user