Pill: Move project model out of parser file

This commit is contained in:
Yan Zhulanow
2020-09-04 05:09:44 +09:00
parent e4ce48d9ef
commit 6da03c0cda
12 changed files with 467 additions and 416 deletions
@@ -12,6 +12,10 @@ import org.gradle.api.tasks.SourceSet
import org.gradle.kotlin.dsl.extra
import org.jetbrains.kotlin.pill.artifact.ArtifactDependencyMapper
import org.jetbrains.kotlin.pill.artifact.ArtifactGenerator
import org.jetbrains.kotlin.pill.model.PDependency
import org.jetbrains.kotlin.pill.model.PLibrary
import org.jetbrains.kotlin.pill.model.POrderRoot
import org.jetbrains.kotlin.pill.model.PProject
import shadow.org.jdom2.input.SAXBuilder
import shadow.org.jdom2.*
import shadow.org.jdom2.output.Format
@@ -89,12 +93,12 @@ class JpsCompatiblePluginTasks(private val rootProject: Project, private val pla
rootProject.logger.lifecycle("Pill: Setting up project for the '${variant.name.toLowerCase()}' variant...")
val modulePrefix = System.getProperty("pill.module.prefix", "")
val parserContext = ParserContext(variant, modulePrefix)
val modelParser = ModelParser(variant, modulePrefix)
val dependencyPatcher = DependencyPatcher(rootProject)
val dependencyMappers = listOf(dependencyPatcher, ::attachPlatformSources, ::attachAsmSources)
val jpsProject = parse(rootProject, parserContext)
val jpsProject = modelParser.parse(rootProject)
.mapDependencies(dependencyMappers)
.copy(libraries = dependencyPatcher.libraries)
@@ -0,0 +1,334 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.pill
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.*
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.plugins.ide.idea.IdeaPlugin
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.internal.HasConvention
import org.gradle.api.internal.file.copy.CopySpecInternal
import org.gradle.api.internal.file.copy.SingleParentCopySpec
import org.gradle.jvm.tasks.Jar
import org.gradle.language.jvm.tasks.ProcessResources
import org.jetbrains.kotlin.pill.model.POrderRoot.*
import org.jetbrains.kotlin.pill.model.PSourceRoot.*
import org.jetbrains.kotlin.pill.PillExtensionMirror.*
import org.jetbrains.kotlin.pill.model.*
import java.io.File
typealias OutputDir = String
typealias GradleProjectPath = String
class ModelParser(private val variant: Variant, private val modulePrefix: String) {
fun parse(project: Project): PProject {
if (project != project.rootProject) {
error("$project is not a root project")
}
fun Project.matchesSelectedVariant(): Boolean {
val extension = this.findPillExtensionMirror() ?: return true
val projectVariant = extension.variant ?: Variant.BASE
return projectVariant in variant.includes
}
val (includedProjects, excludedProjects) = project.allprojects
.partition { it.plugins.hasPlugin("jps-compatible") && it.matchesSelectedVariant() }
val modules = includedProjects.flatMap { parseModules(it, excludedProjects) }
val artifacts = parseArtifacts(project)
return PProject("Kotlin", project.projectDir, modules, emptyList(), artifacts)
}
private fun parseArtifacts(rootProject: Project): Map<String, List<GradleProjectPath>> {
val artifacts = HashMap<OutputDir, List<GradleProjectPath>>()
val additionalOutputs = HashMap<OutputDir, List<OutputDir>>()
for (project in rootProject.allprojects) {
val sourceSets = project.sourceSets?.toList() ?: emptyList()
for (sourceSet in sourceSets) {
val path = makePath(project, sourceSet.name)
for (output in sourceSet.output.toList()) {
artifacts[output.absolutePath] = listOf(path)
}
val jarTask = project.tasks.findByName(sourceSet.jarTaskName) as? Jar ?: continue
val embeddedTask = findEmbeddableTask(project, sourceSet)
for (task in listOfNotNull(jarTask, embeddedTask)) {
val archiveFile = task.archiveFile.get().asFile
artifacts[archiveFile.absolutePath] = listOf(path)
val additionalOutputsForSourceSet = mutableListOf<File>()
fun process(spec: CopySpecInternal) {
spec.children.forEach { process(it) }
if (spec is SingleParentCopySpec) {
for (sourcePath in spec.sourcePaths) {
if (sourcePath is SourceSetOutput) {
additionalOutputsForSourceSet += sourcePath.classesDirs
sourcePath.resourcesDir?.let { additionalOutputsForSourceSet += it }
}
}
}
}
process(task.rootSpec)
additionalOutputs[archiveFile.absolutePath] = additionalOutputsForSourceSet.map { it.absolutePath }
}
}
}
for ((sourceSetOutputDir, additionalOutputsForSourceSet) in additionalOutputs) {
val projectPaths = artifacts[sourceSetOutputDir] ?: error("Unknown artifact $sourceSetOutputDir")
val newPaths = projectPaths + additionalOutputsForSourceSet.mapNotNull { artifacts[it] }.flatten()
artifacts[sourceSetOutputDir] = newPaths.distinct()
}
return artifacts
}
private fun findEmbeddableTask(project: Project, sourceSet: SourceSet): Jar? {
val jarName = sourceSet.jarTaskName
val embeddable = "embeddable"
val embeddedName = if (jarName == "jar") embeddable else jarName.dropLast("jar".length) + embeddable.capitalize()
return project.tasks.findByName(embeddedName) as? Jar
}
private fun makePath(project: Project, sourceSetName: String): GradleProjectPath {
return project.path + "/" + sourceSetName
}
private fun parseModules(project: Project, excludedProjects: List<Project>): List<PModule> {
val modules = mutableListOf<PModule>()
fun getModuleFile(name: String): File {
val relativePath = File(project.projectDir, "$modulePrefix$name.iml").toRelativeString(project.rootProject.projectDir)
return File(project.rootProject.projectDir, ".idea/modules/$relativePath")
}
val embeddedDependencies = project.configurations.findByName(EMBEDDED_CONFIGURATION_NAME)
?.let { parseDependencies(it) } ?: emptyList()
val sourceSets = parseSourceSets(project).sortedBy { it.forTests }
for (sourceSet in sourceSets) {
val sourceRoots = mutableListOf<PSourceRoot>()
for (dir in sourceSet.sourceDirectories) {
sourceRoots += PSourceRoot(dir, if (sourceSet.forTests) Kind.TEST else Kind.PRODUCTION)
}
for (dir in sourceSet.resourceDirectories) {
sourceRoots += PSourceRoot(dir, if (sourceSet.forTests) Kind.TEST_RESOURCES else Kind.RESOURCES)
}
if (sourceRoots.isEmpty()) {
continue
}
val productionModule = if (sourceSet.forTests) modules.firstOrNull { !it.forTests } else null
val contentRoots = sourceRoots.map { PContentRoot(it.directory, listOf(it), emptyList()) }
var orderRoots = parseDependencies(project, sourceSet)
if (productionModule != null) {
val productionModuleDependency = PDependency.Module(productionModule.name)
orderRoots = listOf(POrderRoot(productionModuleDependency, Scope.COMPILE, true)) + orderRoots
}
val name = project.pillModuleName + "." + sourceSet.name
modules += PModule(
name = modulePrefix + name,
path = makePath(project, sourceSet.name),
forTests = sourceSet.forTests,
rootDirectory = sourceRoots.first().directory,
moduleFile = getModuleFile(name),
contentRoots = contentRoots,
orderRoots = orderRoots,
kotlinOptions = sourceSet.kotlinOptions,
moduleForProductionSources = productionModule,
embeddedDependencies = embeddedDependencies
)
}
val mainModuleFileRelativePath = when (project) {
project.rootProject -> File(project.rootProject.projectDir, modulePrefix + project.rootProject.name + ".iml")
else -> getModuleFile(project.pillModuleName)
}
modules += PModule(
name = modulePrefix + project.pillModuleName,
path = project.path,
forTests = false,
rootDirectory = project.projectDir,
moduleFile = mainModuleFileRelativePath,
contentRoots = listOf(PContentRoot(project.projectDir, listOf(), getExcludedDirs(project, excludedProjects))),
orderRoots = emptyList(),
kotlinOptions = null,
moduleForProductionSources = null,
embeddedDependencies = emptyList()
)
return modules
}
private fun getExcludedDirs(project: Project, excludedProjects: List<Project>): List<File> {
fun getJavaExcludedDirs() = project.plugins.findPlugin(IdeaPlugin::class.java)
?.model?.module?.excludeDirs?.toList() ?: emptyList()
fun getPillExcludedDirs() = project.findPillExtensionMirror()?.excludedDirs ?: emptyList()
return getPillExcludedDirs() + getJavaExcludedDirs() + project.buildDir +
(if (project == project.rootProject) excludedProjects.map { it.buildDir } else emptyList())
}
private fun parseSourceSets(project: Project): List<PSourceSet> {
if (!project.plugins.hasPlugin(JavaPlugin::class.java)) {
return emptyList()
}
val kotlinTasksBySourceSet = project.tasks.names
.filter { it.startsWith("compile") && it.endsWith("Kotlin") }
.map { project.tasks.getByName(it) }
.associateBy { it.invokeInternal("getSourceSetName") }
val gradleSourceSets = project.sourceSets?.toList() ?: emptyList()
val sourceSets = mutableListOf<PSourceSet>()
for (sourceSet in gradleSourceSets) {
val kotlinCompileTask = kotlinTasksBySourceSet[sourceSet.name]
fun Any.getKotlin(): SourceDirectorySet {
val kotlinMethod = javaClass.getMethod("getKotlin")
kotlinMethod.isAccessible = true
return kotlinMethod(this) as SourceDirectorySet
}
val kotlinSourceDirectories = (sourceSet as HasConvention).convention
.plugins["kotlin"]?.getKotlin()?.srcDirs ?: emptySet()
val sourceDirectories = (sourceSet.java.sourceDirectories.files + kotlinSourceDirectories).toList()
val resourceDirectoriesFromSourceSet = sourceSet.resources.sourceDirectories.files
val resourceDirectoriesFromTask = parseResourceRootsProcessedByProcessResourcesTask(project, sourceSet)
val resourceDirectories = (resourceDirectoriesFromSourceSet + resourceDirectoriesFromTask)
.distinct().filter { it !in sourceDirectories }
sourceSets += PSourceSet(
name = sourceSet.name,
forTests = sourceSet.isTestSourceSet,
sourceDirectories = sourceDirectories,
resourceDirectories = resourceDirectories,
kotlinOptions = kotlinCompileTask?.let { getKotlinOptions(it) },
compileClasspathConfigurationName = sourceSet.compileClasspathConfigurationName,
runtimeClasspathConfigurationName = sourceSet.runtimeClasspathConfigurationName
)
}
return sourceSets
}
private fun parseResourceRootsProcessedByProcessResourcesTask(project: Project, sourceSet: SourceSet): List<File> {
val isMainSourceSet = sourceSet.name == SourceSet.MAIN_SOURCE_SET_NAME
val taskNameBase = "processResources"
val taskName = if (isMainSourceSet) taskNameBase else sourceSet.name + taskNameBase.capitalize()
val task = project.tasks.findByName(taskName) as? ProcessResources ?: return emptyList()
val roots = mutableListOf<File>()
fun collectRoots(spec: CopySpecInternal) {
if (spec is SingleParentCopySpec && spec.children.none()) {
roots += spec.sourcePaths.map { File(project.projectDir, it.toString()) }.filter { it.exists() }
return
}
spec.children.forEach(::collectRoots)
}
collectRoots(task.rootSpec)
return roots
}
private fun getKotlinOptions(kotlinCompileTask: Any): PSourceRootKotlinOptions? {
val compileArguments = run {
val method = kotlinCompileTask::class.java.getMethod("getSerializedCompilerArguments")
method.isAccessible = true
@Suppress("UNCHECKED_CAST")
method.invoke(kotlinCompileTask) as List<String>
}
fun parseBoolean(name: String) = compileArguments.contains("-$name")
fun parseString(name: String) = compileArguments.dropWhile { it != "-$name" }.drop(1).firstOrNull()
fun isOptionForScriptingCompilerPlugin(option: String): Boolean {
return option.startsWith("-Xplugin=") && option.contains("kotlin-scripting-compiler-embeddable")
}
val extraArguments = compileArguments.filter {
it.startsWith("-X") && !isOptionForScriptingCompilerPlugin(it)
}
return PSourceRootKotlinOptions(
parseBoolean("no-stdlib"),
parseBoolean("no-reflect"),
parseString("module-name"),
parseString("api-version"),
parseString("language-version"),
parseString("jvm-target"),
extraArguments
)
}
private fun parseDependencies(project: Project, sourceSet: PSourceSet): List<POrderRoot> {
val roots = mutableListOf<POrderRoot>()
fun process(name: String, scope: Scope) {
val configuration = project.configurations.findByName(name) ?: return
roots += parseDependencies(configuration).map { POrderRoot(it, scope) }
}
process(sourceSet.compileClasspathConfigurationName, Scope.PROVIDED)
process(sourceSet.runtimeClasspathConfigurationName, Scope.RUNTIME)
if (sourceSet.forTests) {
process("jpsTest", Scope.TEST)
}
return roots
}
private fun parseDependencies(configuration: Configuration): List<PDependency> {
return configuration.resolve().map { file ->
val library = PLibrary(file.name, listOf(file))
return@map PDependency.ModuleLibrary(library)
}
}
}
private val SourceSet.isTestSourceSet: Boolean
get() = name == SourceSet.TEST_SOURCE_SET_NAME
|| name.endsWith("Test")
|| name.endsWith("Tests")
private fun Any.invokeInternal(name: String, instance: Any = this): Any? {
val method = javaClass.methods.single { it.name.startsWith(name) && it.parameterTypes.isEmpty() }
method.isAccessible = true
return method.invoke(instance)
}
val Project.pillModuleName: String
get() = path.removePrefix(":").replace(':', '.')
val Project.sourceSets: SourceSetContainer?
get() {
val convention = project.convention.findPlugin(JavaPluginConvention::class.java) ?: return null
return convention.sourceSets
}
@@ -6,6 +6,8 @@
package org.jetbrains.kotlin.pill
import org.gradle.api.Project
import org.jetbrains.kotlin.pill.model.PModule
import org.jetbrains.kotlin.pill.model.PProject
import java.io.File
private val USER_HOME_DIR_PATH = System.getProperty("user.home").withSlash()
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.pill.artifact
import org.jetbrains.kotlin.pill.PDependency
import org.jetbrains.kotlin.pill.model.PDependency
interface ArtifactDependencyMapper {
fun map(dependency: PDependency): List<PDependency>
@@ -9,6 +9,8 @@ import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.kotlin.dsl.extra
import org.jetbrains.kotlin.pill.*
import org.jetbrains.kotlin.pill.model.PDependency
import org.jetbrains.kotlin.pill.model.PLibrary
import java.io.File
class ArtifactGenerator(private val dependencyMapper: ArtifactDependencyMapper) {
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.pill.model
import java.io.File
data class PLibrary(
val name: String,
val classes: List<File>,
val javadoc: List<File> = emptyList(),
val sources: List<File> = emptyList(),
val annotations: List<File> = emptyList(),
val dependencies: List<PLibrary> = emptyList(),
val originalName: String = name
) {
fun attachSource(file: File): PLibrary {
return this.copy(sources = this.sources + listOf(file))
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.pill.model
import org.jetbrains.kotlin.pill.GradleProjectPath
import java.io.File
data class PModule(
val name: String,
val path: GradleProjectPath,
val forTests: Boolean,
val rootDirectory: File,
val moduleFile: File,
val contentRoots: List<PContentRoot>,
val orderRoots: List<POrderRoot>,
val kotlinOptions: PSourceRootKotlinOptions?,
val moduleForProductionSources: PModule? = null,
val embeddedDependencies: List<PDependency>
)
data class PContentRoot(
val path: File,
val sourceRoots: List<PSourceRoot>,
val excludedDirectories: List<File>
)
data class PSourceRoot(val directory: File, val kind: Kind) {
enum class Kind { PRODUCTION, TEST, RESOURCES, TEST_RESOURCES }
}
data class PSourceRootKotlinOptions(
val noStdlib: Boolean?,
val noReflect: Boolean?,
val moduleName: String?,
val apiVersion: String?,
val languageVersion: String?,
val jvmTarget: String?,
val extraArguments: List<String>
)
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.pill.model
data class POrderRoot(
val dependency: PDependency,
val scope: Scope,
val isExported: Boolean = false,
val isProductionOnTestDependency: Boolean = false
) {
enum class Scope { COMPILE, TEST, RUNTIME, PROVIDED }
}
sealed class PDependency {
data class Module(val name: String) : PDependency()
data class Library(val name: String) : PDependency()
data class ModuleLibrary(val library: PLibrary) : PDependency()
}
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.pill.model
import org.jetbrains.kotlin.pill.GradleProjectPath
import org.jetbrains.kotlin.pill.OutputDir
import java.io.File
data class PProject(
val name: String,
val rootDirectory: File,
val modules: List<PModule>,
val libraries: List<PLibrary>,
val artifacts: Map<OutputDir, List<GradleProjectPath>>
)
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.pill.model
import java.io.File
data class PSourceSet(
val name: String,
val forTests: Boolean,
val sourceDirectories: List<File>,
val resourceDirectories: List<File>,
val kotlinOptions: PSourceRootKotlinOptions?,
val compileClasspathConfigurationName: String,
val runtimeClasspathConfigurationName: String
)
-413
View File
@@ -1,413 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.pill
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.*
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.plugins.ide.idea.IdeaPlugin
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.internal.HasConvention
import org.gradle.api.internal.file.copy.CopySpecInternal
import org.gradle.api.internal.file.copy.SingleParentCopySpec
import org.gradle.jvm.tasks.Jar
import org.gradle.language.jvm.tasks.ProcessResources
import org.jetbrains.kotlin.pill.POrderRoot.*
import org.jetbrains.kotlin.pill.PSourceRoot.*
import org.jetbrains.kotlin.pill.PillExtensionMirror.*
import java.io.File
class ParserContext(val variant: Variant, val modulePrefix: String)
typealias OutputDir = String
typealias GradleProjectPath = String
data class PProject(
val name: String,
val rootDirectory: File,
val modules: List<PModule>,
val libraries: List<PLibrary>,
val artifacts: Map<OutputDir, List<GradleProjectPath>>
)
data class PModule(
val name: String,
val path: GradleProjectPath,
val forTests: Boolean,
val rootDirectory: File,
val moduleFile: File,
val contentRoots: List<PContentRoot>,
val orderRoots: List<POrderRoot>,
val kotlinOptions: PSourceRootKotlinOptions?,
val moduleForProductionSources: PModule? = null,
val embeddedDependencies: List<PDependency>
)
data class PContentRoot(
val path: File,
val sourceRoots: List<PSourceRoot>,
val excludedDirectories: List<File>
)
data class PSourceSet(
val name: String,
val forTests: Boolean,
val sourceDirectories: List<File>,
val resourceDirectories: List<File>,
val kotlinOptions: PSourceRootKotlinOptions?,
val compileClasspathConfigurationName: String,
val runtimeClasspathConfigurationName: String
)
data class PSourceRoot(val directory: File, val kind: Kind) {
enum class Kind { PRODUCTION, TEST, RESOURCES, TEST_RESOURCES }
}
data class PSourceRootKotlinOptions(
val noStdlib: Boolean?,
val noReflect: Boolean?,
val moduleName: String?,
val apiVersion: String?,
val languageVersion: String?,
val jvmTarget: String?,
val extraArguments: List<String>
)
data class POrderRoot(
val dependency: PDependency,
val scope: Scope,
val isExported: Boolean = false,
val isProductionOnTestDependency: Boolean = false
) {
enum class Scope { COMPILE, TEST, RUNTIME, PROVIDED }
}
sealed class PDependency {
data class Module(val name: String) : PDependency()
data class Library(val name: String) : PDependency()
data class ModuleLibrary(val library: PLibrary) : PDependency()
}
data class PLibrary(
val name: String,
val classes: List<File>,
val javadoc: List<File> = emptyList(),
val sources: List<File> = emptyList(),
val annotations: List<File> = emptyList(),
val dependencies: List<PLibrary> = emptyList(),
val originalName: String = name
) {
fun attachSource(file: File): PLibrary {
return this.copy(sources = this.sources + listOf(file))
}
}
fun parse(project: Project, context: ParserContext): PProject = with(context) {
if (project != project.rootProject) {
error("$project is not a root project")
}
fun Project.matchesSelectedVariant(): Boolean {
val extension = this.findPillExtensionMirror() ?: return true
val projectVariant = extension.variant ?: Variant.BASE
return projectVariant in context.variant.includes
}
val (includedProjects, excludedProjects) = project.allprojects
.partition { it.plugins.hasPlugin("jps-compatible") && it.matchesSelectedVariant() }
val modules = includedProjects.flatMap { parseModules(it, excludedProjects) }
val artifacts = parseArtifacts(project)
return PProject("Kotlin", project.projectDir, modules, emptyList(), artifacts)
}
private fun parseArtifacts(rootProject: Project): Map<String, List<GradleProjectPath>> {
val artifacts = HashMap<OutputDir, List<GradleProjectPath>>()
val additionalOutputs = HashMap<OutputDir, List<OutputDir>>()
for (project in rootProject.allprojects) {
val sourceSets = project.sourceSets?.toList() ?: emptyList()
for (sourceSet in sourceSets) {
val path = makePath(project, sourceSet.name)
for (output in sourceSet.output.toList()) {
artifacts[output.absolutePath] = listOf(path)
}
val jarTask = project.tasks.findByName(sourceSet.jarTaskName) as? Jar ?: continue
val embeddedTask = findEmbeddableTask(project, sourceSet)
for (task in listOfNotNull(jarTask, embeddedTask)) {
val archiveFile = task.archiveFile.get().asFile
artifacts[archiveFile.absolutePath] = listOf(path)
val additionalOutputsForSourceSet = mutableListOf<File>()
fun process(spec: CopySpecInternal) {
spec.children.forEach { process(it) }
if (spec is SingleParentCopySpec) {
for (sourcePath in spec.sourcePaths) {
if (sourcePath is SourceSetOutput) {
additionalOutputsForSourceSet += sourcePath.classesDirs
sourcePath.resourcesDir?.let { additionalOutputsForSourceSet += it }
}
}
}
}
process(task.rootSpec)
additionalOutputs[archiveFile.absolutePath] = additionalOutputsForSourceSet.map { it.absolutePath }
}
}
}
for ((sourceSetOutputDir, additionalOutputsForSourceSet) in additionalOutputs) {
val projectPaths = artifacts[sourceSetOutputDir] ?: error("Unknown artifact $sourceSetOutputDir")
val newPaths = projectPaths + additionalOutputsForSourceSet.mapNotNull { artifacts[it] }.flatten()
artifacts[sourceSetOutputDir] = newPaths.distinct()
}
return artifacts
}
private fun findEmbeddableTask(project: Project, sourceSet: SourceSet): Jar? {
val jarName = sourceSet.jarTaskName
val embeddable = "embeddable"
val embeddedName = if (jarName == "jar") embeddable else jarName.dropLast("jar".length) + embeddable.capitalize()
return project.tasks.findByName(embeddedName) as? Jar
}
private fun makePath(project: Project, sourceSetName: String): GradleProjectPath {
return project.path + "/" + sourceSetName
}
private fun ParserContext.parseModules(project: Project, excludedProjects: List<Project>): List<PModule> {
val modules = mutableListOf<PModule>()
fun getModuleFile(name: String): File {
val relativePath = File(project.projectDir, "$modulePrefix$name.iml").toRelativeString(project.rootProject.projectDir)
return File(project.rootProject.projectDir, ".idea/modules/$relativePath")
}
val embeddedDependencies = project.configurations.findByName(EMBEDDED_CONFIGURATION_NAME)
?.let { parseDependencies(it) } ?: emptyList()
val sourceSets = parseSourceSets(project).sortedBy { it.forTests }
for (sourceSet in sourceSets) {
val sourceRoots = mutableListOf<PSourceRoot>()
for (dir in sourceSet.sourceDirectories) {
sourceRoots += PSourceRoot(dir, if (sourceSet.forTests) Kind.TEST else Kind.PRODUCTION)
}
for (dir in sourceSet.resourceDirectories) {
sourceRoots += PSourceRoot(dir, if (sourceSet.forTests) Kind.TEST_RESOURCES else Kind.RESOURCES)
}
if (sourceRoots.isEmpty()) {
continue
}
val productionModule = if (sourceSet.forTests) modules.firstOrNull { !it.forTests } else null
val contentRoots = sourceRoots.map { PContentRoot(it.directory, listOf(it), emptyList()) }
var orderRoots = parseDependencies(project, sourceSet)
if (productionModule != null) {
val productionModuleDependency = PDependency.Module(productionModule.name)
orderRoots = listOf(POrderRoot(productionModuleDependency, Scope.COMPILE, true)) + orderRoots
}
val name = project.pillModuleName + "." + sourceSet.name
modules += PModule(
name = modulePrefix + name,
path = makePath(project, sourceSet.name),
forTests = sourceSet.forTests,
rootDirectory = sourceRoots.first().directory,
moduleFile = getModuleFile(name),
contentRoots = contentRoots,
orderRoots = orderRoots,
kotlinOptions = sourceSet.kotlinOptions,
moduleForProductionSources = productionModule,
embeddedDependencies = embeddedDependencies
)
}
val mainModuleFileRelativePath = when (project) {
project.rootProject -> File(project.rootProject.projectDir, modulePrefix + project.rootProject.name + ".iml")
else -> getModuleFile(project.pillModuleName)
}
modules += PModule(
name = modulePrefix + project.pillModuleName,
path = project.path,
forTests = false,
rootDirectory = project.projectDir,
moduleFile = mainModuleFileRelativePath,
contentRoots = listOf(PContentRoot(project.projectDir, listOf(), getExcludedDirs(project, excludedProjects))),
orderRoots = emptyList(),
kotlinOptions = null,
moduleForProductionSources = null,
embeddedDependencies = emptyList()
)
return modules
}
private fun getExcludedDirs(project: Project, excludedProjects: List<Project>): List<File> {
fun getJavaExcludedDirs() = project.plugins.findPlugin(IdeaPlugin::class.java)
?.model?.module?.excludeDirs?.toList() ?: emptyList()
fun getPillExcludedDirs() = project.findPillExtensionMirror()?.excludedDirs ?: emptyList()
return getPillExcludedDirs() + getJavaExcludedDirs() + project.buildDir +
(if (project == project.rootProject) excludedProjects.map { it.buildDir } else emptyList())
}
private fun parseSourceSets(project: Project): List<PSourceSet> {
if (!project.plugins.hasPlugin(JavaPlugin::class.java)) {
return emptyList()
}
val kotlinTasksBySourceSet = project.tasks.names
.filter { it.startsWith("compile") && it.endsWith("Kotlin") }
.map { project.tasks.getByName(it) }
.associateBy { it.invokeInternal("getSourceSetName") }
val gradleSourceSets = project.sourceSets?.toList() ?: emptyList()
val sourceSets = mutableListOf<PSourceSet>()
for (sourceSet in gradleSourceSets) {
val kotlinCompileTask = kotlinTasksBySourceSet[sourceSet.name]
fun Any.getKotlin(): SourceDirectorySet {
val kotlinMethod = javaClass.getMethod("getKotlin")
kotlinMethod.isAccessible = true
return kotlinMethod(this) as SourceDirectorySet
}
val kotlinSourceDirectories = (sourceSet as HasConvention).convention
.plugins["kotlin"]?.getKotlin()?.srcDirs ?: emptySet()
val sourceDirectories = (sourceSet.java.sourceDirectories.files + kotlinSourceDirectories).toList()
val resourceDirectoriesFromSourceSet = sourceSet.resources.sourceDirectories.files
val resourceDirectoriesFromTask = parseResourceRootsProcessedByProcessResourcesTask(project, sourceSet)
val resourceDirectories = (resourceDirectoriesFromSourceSet + resourceDirectoriesFromTask)
.distinct().filter { it !in sourceDirectories }
sourceSets += PSourceSet(
name = sourceSet.name,
forTests = sourceSet.isTestSourceSet,
sourceDirectories = sourceDirectories,
resourceDirectories = resourceDirectories,
kotlinOptions = kotlinCompileTask?.let { getKotlinOptions(it) },
compileClasspathConfigurationName = sourceSet.compileClasspathConfigurationName,
runtimeClasspathConfigurationName = sourceSet.runtimeClasspathConfigurationName
)
}
return sourceSets
}
private fun parseResourceRootsProcessedByProcessResourcesTask(project: Project, sourceSet: SourceSet): List<File> {
val isMainSourceSet = sourceSet.name == SourceSet.MAIN_SOURCE_SET_NAME
val taskNameBase = "processResources"
val taskName = if (isMainSourceSet) taskNameBase else sourceSet.name + taskNameBase.capitalize()
val task = project.tasks.findByName(taskName) as? ProcessResources ?: return emptyList()
val roots = mutableListOf<File>()
fun collectRoots(spec: CopySpecInternal) {
if (spec is SingleParentCopySpec && spec.children.none()) {
roots += spec.sourcePaths.map { File(project.projectDir, it.toString()) }.filter { it.exists() }
return
}
spec.children.forEach(::collectRoots)
}
collectRoots(task.rootSpec)
return roots
}
private val SourceSet.isTestSourceSet: Boolean
get() = name == SourceSet.TEST_SOURCE_SET_NAME
|| name.endsWith("Test")
|| name.endsWith("Tests")
private fun getKotlinOptions(kotlinCompileTask: Any): PSourceRootKotlinOptions? {
val compileArguments = run {
val method = kotlinCompileTask::class.java.getMethod("getSerializedCompilerArguments")
method.isAccessible = true
@Suppress("UNCHECKED_CAST")
method.invoke(kotlinCompileTask) as List<String>
}
fun parseBoolean(name: String) = compileArguments.contains("-$name")
fun parseString(name: String) = compileArguments.dropWhile { it != "-$name" }.drop(1).firstOrNull()
fun isOptionForScriptingCompilerPlugin(option: String): Boolean {
return option.startsWith("-Xplugin=") && option.contains("kotlin-scripting-compiler-embeddable")
}
val extraArguments = compileArguments.filter {
it.startsWith("-X") && !isOptionForScriptingCompilerPlugin(it)
}
return PSourceRootKotlinOptions(
parseBoolean("no-stdlib"),
parseBoolean("no-reflect"),
parseString("module-name"),
parseString("api-version"),
parseString("language-version"),
parseString("jvm-target"),
extraArguments
)
}
private fun Any.invokeInternal(name: String, instance: Any = this): Any? {
val method = javaClass.methods.single { it.name.startsWith(name) && it.parameterTypes.isEmpty() }
method.isAccessible = true
return method.invoke(instance)
}
private fun parseDependencies(project: Project, sourceSet: PSourceSet): List<POrderRoot> {
val roots = mutableListOf<POrderRoot>()
fun process(name: String, scope: Scope) {
val configuration = project.configurations.findByName(name) ?: return
roots += parseDependencies(configuration).map { POrderRoot(it, scope) }
}
process(sourceSet.compileClasspathConfigurationName, Scope.PROVIDED)
process(sourceSet.runtimeClasspathConfigurationName, Scope.RUNTIME)
if (sourceSet.forTests) {
process("jpsTest", Scope.TEST)
}
return roots
}
private fun parseDependencies(configuration: Configuration): List<PDependency> {
return configuration.resolve().map { file ->
val library = PLibrary(file.name, listOf(file))
return@map PDependency.ModuleLibrary(library)
}
}
val Project.pillModuleName: String
get() = path.removePrefix(":").replace(':', '.')
val Project.sourceSets: SourceSetContainer?
get() {
val convention = project.convention.findPlugin(JavaPluginConvention::class.java) ?: return null
return convention.sourceSets
}
+1
View File
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.pill
import org.jetbrains.kotlin.pill.model.*
import java.io.File
class PFile(val path: File, private val text: String) {