Modules: Support production-on-test dependency

This feature is supported in IDEA project configuration
and can be used in Maven/Gradle-based projects

 #KT-20112 Fixed
This commit is contained in:
Alexey Sedunov
2017-09-12 20:17:06 +03:00
parent 0269c3309a
commit f63e33b23d
3 changed files with 242 additions and 10 deletions
@@ -21,6 +21,7 @@ import com.intellij.openapi.module.impl.scopes.LibraryScopeBase
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.*
import com.intellij.openapi.roots.impl.ModuleOrderEntryImpl
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.util.ModificationTracker
@@ -35,6 +36,7 @@ import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.TrackableModuleInfo
import org.jetbrains.kotlin.caches.resolve.LibraryModuleInfo
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.KotlinPluginUtil
import org.jetbrains.kotlin.idea.framework.getLibraryPlatform
import org.jetbrains.kotlin.idea.project.KotlinModuleModificationTracker
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
@@ -58,8 +60,8 @@ interface IdeaModuleInfo : ModuleInfo {
override fun dependencies(): List<IdeaModuleInfo>
}
private fun orderEntryToModuleInfo(project: Project, orderEntry: OrderEntry, productionOnly: Boolean): List<IdeaModuleInfo> {
fun Module.toInfos() = correspondingModuleInfos().filter { !productionOnly || it is ModuleProductionSourceInfo }
private fun orderEntryToModuleInfo(project: Project, orderEntry: OrderEntry, forProduction: Boolean): List<IdeaModuleInfo> {
fun Module.toInfos() = correspondingModuleInfos().filter { !forProduction || it is ModuleProductionSourceInfo }
if (!orderEntry.isValid) return emptyList()
@@ -68,7 +70,13 @@ private fun orderEntryToModuleInfo(project: Project, orderEntry: OrderEntry, pro
orderEntry.getOwnerModule().toInfos()
}
is ModuleOrderEntry -> {
orderEntry.module?.toInfos().orEmpty()
val module = orderEntry.module ?: return emptyList()
if (forProduction && orderEntry is ModuleOrderEntryImpl && orderEntry.isProductionOnTestDependency) {
listOfNotNull(module.testSourceInfo())
}
else {
module.toInfos()
}
}
is LibraryOrderEntry -> {
val library = orderEntry.library ?: return listOf()
@@ -88,16 +96,25 @@ fun <T> Module.cached(provider: CachedValueProvider<T>): T {
return CachedValuesManager.getManager(project).getCachedValue(this, provider)
}
private fun ideaModelDependencies(module: Module, productionOnly: Boolean): List<IdeaModuleInfo> {
private fun OrderEntry.acceptAsDependency(forProduction: Boolean): Boolean {
return this !is ExportableOrderEntry
|| !forProduction
// this is needed for Maven/Gradle projects with "production-on-test" dependency
|| this is ModuleOrderEntryImpl && isProductionOnTestDependency
|| scope.isForProductionCompile
}
private fun ideaModelDependencies(module: Module, forProduction: Boolean): List<IdeaModuleInfo> {
//NOTE: lib dependencies can be processed several times during recursive traversal
val result = LinkedHashSet<IdeaModuleInfo>()
val dependencyEnumerator = ModuleRootManager.getInstance(module).orderEntries().compileOnly().recursively().exportedOnly()
if (productionOnly) {
if (forProduction && !(KotlinPluginUtil.isMavenModule(module) || KotlinPluginUtil.isGradleModule(module))) {
dependencyEnumerator.productionOnly()
}
dependencyEnumerator.forEach {
orderEntry ->
result.addAll(orderEntryToModuleInfo(module.project, orderEntry!!, productionOnly))
dependencyEnumerator.forEach { orderEntry ->
if (orderEntry.acceptAsDependency(forProduction)) {
result.addAll(orderEntryToModuleInfo(module.project, orderEntry!!, forProduction))
}
true
}
return result.toList()
@@ -125,7 +142,7 @@ data class ModuleProductionSourceInfo internal constructor(override val module:
override fun dependencies() = module.cached(CachedValueProvider {
CachedValueProvider.Result(
ideaModelDependencies(module, productionOnly = true),
ideaModelDependencies(module, forProduction = true),
ProjectRootModificationTracker.getInstance(module.project))
})
}
@@ -140,7 +157,7 @@ data class ModuleTestSourceInfo internal constructor(override val module: Module
override fun dependencies() = module.cached(CachedValueProvider {
CachedValueProvider.Result(
ideaModelDependencies(module, productionOnly = false),
ideaModelDependencies(module, forProduction = false),
ProjectRootModificationTracker.getInstance(module.project))
})
@@ -23,12 +23,16 @@ import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import junit.framework.TestCase
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
import org.jetbrains.kotlin.idea.facet.KotlinFacet
import org.jetbrains.kotlin.idea.framework.CommonLibraryKind
import org.jetbrains.kotlin.idea.framework.JSLibraryKind
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
import org.jetbrains.kotlin.psi.KtFile
import org.junit.Assert
import java.io.File
@@ -1787,6 +1791,217 @@ class KotlinMavenImporterTest : MavenImportingTestCase() {
}
}
fun testProductionOnTestDependency() {
createProjectSubDirs(
"module-with-java/src/main/java",
"module-with-java/src/test/java",
"module-with-kotlin/src/main/kotlin",
"module-with-kotlin/src/test/kotlin"
)
val dummyFile = createProjectSubFile(
"module-with-kotlin/src/main/kotlin/foo/dummy.kt",
"""
package foo
fun dummy() {
}
""".trimIndent()
)
val pomA = createModulePom(
"module-with-java",
"""
<parent>
<groupId>test-group</groupId>
<artifactId>mvnktest</artifactId>
<version>0.0.0.0-SNAPSHOT</version>
</parent>
<artifactId>module-with-java</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
""".trimIndent()
)
val pomB = createModulePom(
"module-with-kotlin",
"""
<parent>
<groupId>test-group</groupId>
<artifactId>mvnktest</artifactId>
<version>0.0.0.0-SNAPSHOT</version>
</parent>
<artifactId>module-with-kotlin</artifactId>
<properties>
<kotlin.version>1.1.4</kotlin.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
</properties>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${"$"}{kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-runtime</artifactId>
<version>${"$"}{kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${"$"}{kotlin.version}</version>
</dependency>
<dependency>
<groupId>test-group</groupId>
<artifactId>module-with-java</artifactId>
</dependency>
<dependency>
<groupId>test-group</groupId>
<artifactId>module-with-java</artifactId>
<type>test-jar</type>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${"$"}{kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals> <goal>compile</goal> </goals>
<configuration>
<sourceDirs>
<sourceDir>${"$"}{project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${"$"}{project.basedir}/src/main/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<goals> <goal>test-compile</goal> </goals>
<configuration>
<sourceDirs>
<sourceDir>${"$"}{project.basedir}/src/test/kotlin</sourceDir>
<sourceDir>${"$"}{project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-testCompile as it is treated specially by maven -->
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals> <goal>compile</goal> </goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals> <goal>testCompile</goal> </goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
""".trimIndent()
)
val pomMain = createModulePom(
"",
"""
<groupId>test-group</groupId>
<artifactId>mvnktest</artifactId>
<version>0.0.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<kotlin.version>1.1.4</kotlin.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
</properties>
<modules>
<module>module-with-java</module>
<module>module-with-kotlin</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>test-group</groupId>
<artifactId>module-with-kotlin</artifactId>
<version>${"$"}{project.version}</version>
</dependency>
<dependency>
<groupId>test-group</groupId>
<artifactId>module-with-java</artifactId>
<version>${"$"}{project.version}</version>
</dependency>
<dependency>
<groupId>test-group</groupId>
<artifactId>module-with-java</artifactId>
<version>${"$"}{project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
""".trimIndent()
)
importProjects(pomMain, pomA, pomB)
assertModules("module-with-kotlin", "module-with-java", "mvnktest")
val dependencies = (dummyFile.toPsiFile(myProject) as KtFile).analyzeAndGetResult().moduleDescriptor.allDependencyModules
TestCase.assertTrue(dependencies.any { it.name.asString() == "<production sources for module module-with-java>" })
TestCase.assertTrue(dependencies.any { it.name.asString() == "<test sources for module module-with-java>" })
}
private fun assertImporterStatePresent() {
assertNotNull("Kotlin importer component is not present", myTestFixture.module.getComponent(KotlinImporterComponent::class.java))
}