Pill: Move out Pill tasks from buildSrc

This commit is contained in:
Yan Zhulanow
2020-01-31 03:41:45 +09:00
parent 6f1b3e0f17
commit a2be789d37
31 changed files with 235 additions and 84 deletions
@@ -0,0 +1,49 @@
import org.jetbrains.kotlin.pill.PillExtension
import java.lang.reflect.Modifier
import java.net.URLClassLoader
plugins {
kotlin("jvm")
id("jps-compatible")
}
pill {
variant = PillExtension.Variant.FULL
}
dependencies {
compileOnly(kotlin("stdlib", embeddedKotlinVersion))
compileOnly(gradleApi())
compileOnly(gradleKotlinDsl())
compileOnly("com.github.jengelman.gradle.plugins:shadow:${rootProject.extra["versions.shadow"]}")
}
sourceSets {
"main" { projectDefault() }
"test" { }
}
fun runPillTask(taskName: String) {
val jarFile = configurations.archives.artifacts.single { it.type == "jar" }.file
val cl = URLClassLoader(arrayOf(jarFile.toURI().toURL()), (object {}).javaClass.classLoader)
val pillImporterClass = Class.forName("org.jetbrains.kotlin.pill.PillImporter", true, cl)
val runMethod = pillImporterClass.declaredMethods.single { it.name == "run" }
require(Modifier.isStatic(runMethod.modifiers))
val platformDir = IntellijRootUtils.getIntellijRootDir(project)
val resourcesDir = File(project.projectDir, "resources")
runMethod.invoke(null, project.rootProject, taskName, platformDir, resourcesDir, EmbeddedComponents.CONFIGURATION_NAME)
}
val jar: Jar by tasks
val pill by tasks.creating {
dependsOn(jar)
doLast { runPillTask("pill") }
}
val unpill by tasks.creating {
dependsOn(jar)
doLast { runPillTask("unpill") }
}
@@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[PILL] All Compiler Tests" type="CompoundRunConfigurationType" factoryName="Compound Run Configuration">
<toRun type="JUnit" name="[Pill] Compiler Tests" />
<toRun type="JUnit" name="[Pill] JS Backend Tests" />
<toRun type="JUnit" name="[Pill] Java 8 Tests" />
<method />
</configuration>
</component>
@@ -0,0 +1,37 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[Pill] All IDEA Plugin Tests" type="JUnit" factoryName="JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea">
<pattern>
<option name="PATTERN" value="org.jetbrains.kotlin.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<module name="idea.test" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="org.jetbrains.kotlin" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea -XX:+HeapDumpOnOutOfMemoryError -Xmx1250m -XX:+UseCodeCacheFlushing -XX:ReservedCodeCacheSize=128m -Djna.nosys=true -Duse.jps=true -Didea.home.path=$IDEA_HOME_PATH$" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="" />
<option name="TRANSPORT" value="0" />
<option name="LOCAL" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="Profile " />
<RunnerSettings RunnerId="Run" />
<ConfigurationWrapper RunnerId="Debug" />
<ConfigurationWrapper RunnerId="Run" />
<method />
</configuration>
</component>
@@ -0,0 +1,35 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[Pill] Compiler Tests" type="JUnit" factoryName="JUnit">
<module name="compiler.test" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.jetbrains.kotlin.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="org.jetbrains.kotlin" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea -XX:+HeapDumpOnOutOfMemoryError -Xmx1024m -XX:+UseCodeCacheFlushing -XX:ReservedCodeCacheSize=128m -Djna.nosys=true -Duse.jps=true -Didea.home.path=$IDEA_HOME_PATH$" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="" />
<option name="TRANSPORT" value="0" />
<option name="LOCAL" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="Profile " />
<RunnerSettings RunnerId="Run" />
<ConfigurationWrapper RunnerId="Debug" />
<ConfigurationWrapper RunnerId="Run" />
<method />
</configuration>
</component>
@@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[Pill] Generate All Tests" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.jetbrains.kotlin.pill.generateAllTests.Main" />
<module name="pill.generate-all-tests.test" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
<method />
</configuration>
</component>
@@ -0,0 +1,41 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[Pill] IDEA" type="Application" factoryName="Application" singleton="true">
<log_file alias="idea.log" path="$PROJECT_DIR$/ideaSDK/system-idea/log/idea.log" />
<option name="MAIN_CLASS_NAME" value="com.intellij.idea.Main" />
<module name="idea-runner.main" />
<option name="VM_PARAMETERS" value="-Xmx1250m -XX:ReservedCodeCacheSize=240m -XX:+HeapDumpOnOutOfMemoryError -ea -Didea.is.internal=true -Didea.debug.mode=true -Didea.system.path=$PROJECT_DIR$/local/ideaSandbox -Didea.config.path=$PROJECT_DIR$/local/ideaSandbox/config -Dapple.laf.useScreenMenuBar=true -Dapple.awt.graphics.UseQuartz=true -Dsun.io.useCanonCaches=false -Dplugin.path=$PROJECT_DIR$/out/artifacts/Kotlin $ADDITIONAL_IDEA_ARGS$" />
<option name="WORKING_DIRECTORY" value="file://$IDEA_HOME_PATH$" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="" />
<option name="TRANSPORT" value="0" />
<option name="LOCAL" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="JavaRebel">
<option name="bootstrapPath" />
<option name="jrebelArgs" value="" />
<option name="loggingEnabled" value="false" />
<option name="useBootstrapDefaults" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="JavaRebel Debug">
<option name="bootstrapPath" />
<option name="debugPort" value="" />
<option name="jrebelArgs" value="" />
<option name="loggingEnabled" value="false" />
<option name="transport" value="0" />
<option name="useBootstrapDefaults" value="true" />
<option name="DEBUG_PORT" value="" />
<option name="TRANSPORT" value="0" />
<option name="LOCAL" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="Profile " />
<RunnerSettings RunnerId="Run" />
<ConfigurationWrapper RunnerId="Debug" />
<ConfigurationWrapper RunnerId="Profile " />
<ConfigurationWrapper RunnerId="Run" />
<method>
<option name="BuildArtifacts" enabled="true">
<artifact name="KotlinPlugin" />
</option>
</method>
</configuration>
</component>
@@ -0,0 +1,41 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[Pill] IDEA (No ProcessCanceledException)" type="Application" factoryName="Application" singleton="true">
<log_file alias="idea.log" path="$PROJECT_DIR$/ideaSDK/system-idea/log/idea.log" />
<option name="MAIN_CLASS_NAME" value="com.intellij.idea.Main" />
<module name="idea-runner.main" />
<option name="VM_PARAMETERS" value="-Xmx1250m -XX:ReservedCodeCacheSize=240m -XX:+HeapDumpOnOutOfMemoryError -ea -Didea.is.internal=true -Didea.debug.mode=true -Didea.system.path=$PROJECT_DIR$/local/ideaSandbox -Didea.config.path=$PROJECT_DIR$/local/ideaSandbox/config -Dapple.laf.useScreenMenuBar=true -Dapple.awt.graphics.UseQuartz=true -Dsun.io.useCanonCaches=false -Dplugin.path=$PROJECT_DIR$/out/artifacts/Kotlin -Didea.ProcessCanceledException=disabled $ADDITIONAL_IDEA_ARGS$" />
<option name="WORKING_DIRECTORY" value="file://$IDEA_HOME_PATH$" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="" />
<option name="TRANSPORT" value="0" />
<option name="LOCAL" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="JavaRebel">
<option name="bootstrapPath" />
<option name="jrebelArgs" value="" />
<option name="loggingEnabled" value="false" />
<option name="useBootstrapDefaults" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="JavaRebel Debug">
<option name="bootstrapPath" />
<option name="debugPort" value="" />
<option name="jrebelArgs" value="" />
<option name="loggingEnabled" value="false" />
<option name="transport" value="0" />
<option name="useBootstrapDefaults" value="true" />
<option name="DEBUG_PORT" value="" />
<option name="TRANSPORT" value="0" />
<option name="LOCAL" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="Profile " />
<RunnerSettings RunnerId="Run" />
<ConfigurationWrapper RunnerId="Debug" />
<ConfigurationWrapper RunnerId="Profile " />
<ConfigurationWrapper RunnerId="Run" />
<method>
<option name="BuildArtifacts" enabled="true">
<artifact name="KotlinPlugin" />
</option>
</method>
</configuration>
</component>
@@ -0,0 +1,41 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[Pill] IDEA Ultimate" type="Application" factoryName="Application" singleton="true">
<log_file alias="idea.log" path="$PROJECT_DIR$/ideaSDK/system-idea/log/idea.log" />
<option name="MAIN_CLASS_NAME" value="com.intellij.idea.Main" />
<module name="kotlin-ultimate.ultimate.ultimate-runner.main" />
<option name="VM_PARAMETERS" value="-Xmx1250m -XX:ReservedCodeCacheSize=240m -XX:+HeapDumpOnOutOfMemoryError -ea -Didea.is.internal=true -Didea.debug.mode=true -Didea.system.path=$PROJECT_DIR$/local/ideaSandbox -Didea.config.path=$PROJECT_DIR$/local/ideaSandbox/config -Dapple.laf.useScreenMenuBar=true -Dapple.awt.graphics.UseQuartz=true -Dsun.io.useCanonCaches=false -Dplugin.path=$PROJECT_DIR$/out/artifacts/Kotlin $ADDITIONAL_IDEA_ARGS$" />
<option name="WORKING_DIRECTORY" value="file://$IDEA_HOME_PATH$" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="" />
<option name="TRANSPORT" value="0" />
<option name="LOCAL" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="JavaRebel">
<option name="bootstrapPath" />
<option name="jrebelArgs" value="" />
<option name="loggingEnabled" value="false" />
<option name="useBootstrapDefaults" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="JavaRebel Debug">
<option name="bootstrapPath" />
<option name="debugPort" value="" />
<option name="jrebelArgs" value="" />
<option name="loggingEnabled" value="false" />
<option name="transport" value="0" />
<option name="useBootstrapDefaults" value="true" />
<option name="DEBUG_PORT" value="" />
<option name="TRANSPORT" value="0" />
<option name="LOCAL" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="Profile " />
<RunnerSettings RunnerId="Run" />
<ConfigurationWrapper RunnerId="Debug" />
<ConfigurationWrapper RunnerId="Profile " />
<ConfigurationWrapper RunnerId="Run" />
<method>
<option name="BuildArtifacts" enabled="true">
<artifact name="KotlinPlugin" />
</option>
</method>
</configuration>
</component>
@@ -0,0 +1,28 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[Pill] JS Backend Tests" type="JUnit" factoryName="JUnit">
<module name="js.js.tests.test" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="org.jetbrains.kotlin.js.test" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea -XX:+HeapDumpOnOutOfMemoryError -Xmx1250m -XX:+UseCodeCacheFlushing -Djna.nosys=true -Duse.jps=true -Dkotlin.js.skipMinificationTest=false" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="" />
<option name="TRANSPORT" value="0" />
<option name="LOCAL" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="Run" />
<ConfigurationWrapper RunnerId="Debug" />
<ConfigurationWrapper RunnerId="Run" />
<method />
</configuration>
</component>
@@ -0,0 +1,35 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[Pill] JVM Backend Tests" type="JUnit" factoryName="JUnit">
<module name="compiler.test" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.jetbrains.kotlin.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="org.jetbrains.kotlin.codegen" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea -XX:+HeapDumpOnOutOfMemoryError -Xmx1024m -XX:+UseCodeCacheFlushing -XX:ReservedCodeCacheSize=128m -Djna.nosys=true -Duse.jps=true -Didea.home.path=$IDEA_HOME_PATH$" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="" />
<option name="TRANSPORT" value="0" />
<option name="LOCAL" value="true" />
</RunnerSettings>
<RunnerSettings RunnerId="Profile " />
<RunnerSettings RunnerId="Run" />
<ConfigurationWrapper RunnerId="Debug" />
<ConfigurationWrapper RunnerId="Run" />
<method />
</configuration>
</component>
@@ -0,0 +1,26 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[Pill] Java 8 Tests" type="JUnit" factoryName="JUnit">
<module name="compiler.tests-java8.test" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.jetbrains.kotlin.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="ALTERNATIVE_JRE_PATH" value="1.8" />
<option name="PACKAGE_NAME" value="org.jetbrains.kotlin" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea -XX:+HeapDumpOnOutOfMemoryError -XX:+UseCodeCacheFlushing -XX:ReservedCodeCacheSize=128m -Djna.nosys=true -Duse.jps=true -Dkotlin.colors.enabled=false -Didea.home.path=$IDEA_HOME_PATH$" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<method />
</configuration>
</component>
@@ -0,0 +1,21 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[Pill] Refresh PILL Model" type="GradleRunConfiguration" factoryName="Gradle" singleton="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="pill" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<method />
</configuration>
</component>
@@ -0,0 +1,408 @@
/*
* 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.plugins.BasePluginConvention
import org.gradle.kotlin.dsl.extra
import shadow.org.jdom2.input.SAXBuilder
import shadow.org.jdom2.*
import shadow.org.jdom2.output.Format
import shadow.org.jdom2.output.XMLOutputter
import java.io.File
import java.util.*
import kotlin.collections.HashMap
const val EMBEDDED_CONFIGURATION_NAME = "embedded"
class JpsCompatiblePluginTasks(private val rootProject: Project, private val platformDir: File, private val resourcesDir: File) {
companion object {
private val DIST_LIBRARIES = listOf(
":kotlin-annotations-jvm",
":kotlin-stdlib",
":kotlin-stdlib-jdk7",
":kotlin-stdlib-jdk8",
":kotlin-reflect",
":kotlin-test:kotlin-test-jvm",
":kotlin-test:kotlin-test-junit",
":kotlin-script-runtime",
":kotlin-serialization"
)
private val IGNORED_LIBRARIES = listOf(
// Libraries
":kotlin-stdlib-common",
":kotlin-reflect-api",
":kotlin-test:kotlin-test-common",
":kotlin-test:kotlin-test-annotations-common",
// Other
":kotlin-compiler",
":kotlin-daemon-embeddable",
":kotlin-compiler-embeddable",
":kotlin-android-extensions",
":kotlin-scripting-compiler-embeddable",
":kotlin-scripting-compiler-impl-embeddable",
":kotlin-scripting-jvm-host-embeddable"
)
private val MAPPED_LIBRARIES = mapOf(
":kotlin-reflect-api/java9" to ":kotlin-reflect/main"
)
private val LIB_DIRECTORIES = listOf("dependencies", "dist")
}
private lateinit var projectDir: File
private lateinit var platformVersion: String
private lateinit var platformBaseNumber: String
private lateinit var intellijCoreDir: File
private var isAndroidStudioPlatform: Boolean = false
private fun initEnvironment(project: Project) {
projectDir = project.projectDir
platformVersion = project.extensions.extraProperties.get("versions.intellijSdk").toString()
platformBaseNumber = platformVersion.substringBefore(".", "").takeIf { it.isNotEmpty() }
?: platformVersion.substringBefore("-", "").takeIf { it.isNotEmpty() }
?: error("Invalid platform version: $platformVersion")
intellijCoreDir = File(platformDir.parentFile.parentFile.parentFile, "intellij-core")
isAndroidStudioPlatform = project.extensions.extraProperties.has("versions.androidStudioRelease")
}
fun pill() {
initEnvironment(rootProject)
val variantOptionValue = System.getProperty("pill.variant", "base").toUpperCase()
val variant = PillExtensionMirror.Variant.values().firstOrNull { it.name == variantOptionValue }
?: run {
rootProject.logger.error("Invalid variant name: $variantOptionValue")
return
}
rootProject.logger.lifecycle("Pill: Setting up project for the '${variant.name.toLowerCase()}' variant...")
if (variant == PillExtensionMirror.Variant.NONE || variant == PillExtensionMirror.Variant.DEFAULT) {
rootProject.logger.error("'none' and 'default' should not be passed as a Pill variant property value")
return
}
val parserContext = ParserContext(variant)
val dependencyPatcher = DependencyPatcher(rootProject)
val dependencyMappers = listOf(dependencyPatcher, ::attachPlatformSources, ::attachAsmSources)
val jpsProject = parse(rootProject, parserContext)
.mapDependencies(dependencyMappers)
.copy(libraries = dependencyPatcher.libraries)
val files = render(jpsProject)
removeExistingIdeaLibrariesAndModules()
removeJpsAndPillRunConfigurations()
removeAllArtifactConfigurations()
val artifactDependencyMapper = object : OpaqueDependencyMapper {
override fun map(dependency: PDependency): List<PDependency> {
return jpsProject.mapDependency(dependency, dependencyMappers)
}
}
generateKotlinPluginArtifactFile(rootProject, artifactDependencyMapper).write()
copyRunConfigurations()
setOptionsForDefaultJunitRunConfiguration(rootProject)
files.forEach { it.write() }
}
fun unpill() {
initEnvironment(rootProject)
removeExistingIdeaLibrariesAndModules()
removeJpsAndPillRunConfigurations()
removeAllArtifactConfigurations()
}
private fun removeExistingIdeaLibrariesAndModules() {
File(projectDir, ".idea/libraries").deleteRecursively()
File(projectDir, ".idea/modules").deleteRecursively()
}
private fun removeJpsAndPillRunConfigurations() {
File(projectDir, ".idea/runConfigurations")
.walk()
.filter { (it.name.startsWith("JPS_") || it.name.startsWith("Pill_")) && it.extension.toLowerCase() == "xml" }
.forEach { it.delete() }
}
private fun removeAllArtifactConfigurations() {
File(projectDir, ".idea/artifacts")
.walk()
.filter { it.extension.toLowerCase() == "xml" }
.forEach { it.delete() }
}
private fun copyRunConfigurations() {
val runConfigurationsDir = File(resourcesDir, "runConfigurations")
val targetDir = File(projectDir, ".idea/runConfigurations")
val platformDirProjectRelative = "\$PROJECT_DIR\$/" + platformDir.toRelativeString(projectDir)
val additionalIdeaArgs = if (isAndroidStudioPlatform) "-Didea.platform.prefix=AndroidStudio" else ""
targetDir.mkdirs()
fun substitute(text: String): String {
return text
.replace("\$IDEA_HOME_PATH\$", platformDirProjectRelative)
.replace("\$ADDITIONAL_IDEA_ARGS\$", additionalIdeaArgs)
}
(runConfigurationsDir.listFiles() ?: emptyArray())
.filter { it.extension == "xml" }
.map { it.name to substitute(it.readText()) }
.forEach { File(targetDir, it.first).writeText(it.second) }
}
/*
This sets a proper (project root) working directory and a "idea.home.path" property to the default JUnit configuration,
so one does not need to make these changes manually.
*/
private fun setOptionsForDefaultJunitRunConfiguration(project: Project) {
val workspaceFile = File(projectDir, ".idea/workspace.xml")
if (!workspaceFile.exists()) {
project.logger.warn("${workspaceFile.name} does not exist, JUnit default run configuration was not modified")
return
}
val document = SAXBuilder().build(workspaceFile)
val rootElement = document.rootElement
fun Element.getOrCreateChild(name: String, vararg attributes: Pair<String, String>): Element {
for (child in getChildren(name)) {
if (attributes.all { (attribute, value) -> child.getAttributeValue(attribute) == value }) {
return child
}
}
return Element(name).apply {
for ((attributeName, value) in attributes) {
setAttribute(attributeName, value)
}
this@getOrCreateChild.addContent(this@apply)
}
}
val platformDirProjectRelative = "\$PROJECT_DIR\$/" + platformDir.toRelativeString(projectDir)
val runManagerComponent = rootElement.getOrCreateChild("component", "name" to "RunManager")
val junitConfiguration = runManagerComponent.getOrCreateChild(
"configuration",
"default" to "true",
"type" to "JUnit",
"factoryName" to "JUnit"
)
val kotlinJunitConfiguration = runManagerComponent.getOrCreateChild(
"configuration",
"default" to "true",
"type" to "KotlinJUnit",
"factoryName" to "Kotlin JUnit"
)
fun Element.applyJUnitTemplate() {
getOrCreateChild("option", "name" to "WORKING_DIRECTORY").setAttribute("value", "file://\$PROJECT_DIR\$")
getOrCreateChild("option", "name" to "VM_PARAMETERS").also { vmParams ->
var options = vmParams.getAttributeValue("value", "")
.split(' ')
.map { it.trim() }
.filter { it.isNotEmpty() }
fun addOrReplaceOptionValue(name: String, value: Any?) {
val optionsWithoutNewValue = options.filter { !it.startsWith("-D$name=") }
options = if (value == null) optionsWithoutNewValue else (optionsWithoutNewValue + listOf("-D$name=$value"))
}
if (options.none { it == "-ea" }) {
options = options + "-ea"
}
addOrReplaceOptionValue("idea.home.path", platformDirProjectRelative)
addOrReplaceOptionValue("ideaSdk.androidPlugin.path", "$platformDirProjectRelative/plugins/android/lib")
addOrReplaceOptionValue("use.jps", "true")
addOrReplaceOptionValue("kotlinVersion", project.rootProject.extra["kotlinVersion"].toString())
val isAndroidStudioBunch = project.findProperty("versions.androidStudioRelease") != null
addOrReplaceOptionValue("idea.platform.prefix", if (isAndroidStudioBunch) "AndroidStudio" else null)
val androidJarPath = project.configurations.findByName("androidJar")?.singleFile
val androidSdkPath = project.configurations.findByName("androidSdk")?.singleFile
if (androidJarPath != null && androidSdkPath != null) {
addOrReplaceOptionValue("android.sdk", "\$PROJECT_DIR\$/" + androidSdkPath.toRelativeString(projectDir))
addOrReplaceOptionValue("android.jar", "\$PROJECT_DIR\$/" + androidJarPath.toRelativeString(projectDir))
}
vmParams.setAttribute("value", options.joinToString(" "))
}
}
junitConfiguration.applyJUnitTemplate()
kotlinJunitConfiguration.applyJUnitTemplate()
val output = XMLOutputter().also {
@Suppress("UsePropertyAccessSyntax")
it.format = Format.getPrettyFormat().apply {
setEscapeStrategy { c -> Verifier.isHighSurrogate(c) || c == '"' }
setIndent(" ")
setTextMode(Format.TextMode.TRIM)
setOmitEncoding(false)
setOmitDeclaration(false)
}
}
val postProcessedXml = output.outputString(document)
.replace("&#x22;", "&quot;")
.replace("&#xA;", "&#10;")
.replace("&#xC;", "&#13;")
workspaceFile.writeText(postProcessedXml)
}
private class DependencyPatcher(private val rootProject: Project) : Function2<PProject, PDependency, List<PDependency>> {
private val mappings: Map<String, Optional<PLibrary>> = run {
val distLibDir = File(rootProject.extra["distLibDir"].toString())
val result = HashMap<String, Optional<PLibrary>>()
fun List<File>.filterExisting() = filter { it.exists() }
for (path in DIST_LIBRARIES) {
val project = rootProject.findProject(path) ?: error("Project not found")
val archiveName = project.convention.findPlugin(BasePluginConvention::class.java)!!.archivesBaseName
val classesJars = listOf(File(distLibDir, "$archiveName.jar")).filterExisting()
val sourcesJars = listOf(File(distLibDir, "$archiveName-sources.jar")).filterExisting()
result["$path/main"] = Optional.of(PLibrary(archiveName, classesJars, sourcesJars, originalName = path))
}
for (path in IGNORED_LIBRARIES) {
result["$path/main"] = Optional.empty<PLibrary>()
}
for ((old, new) in MAPPED_LIBRARIES) {
result[old] = result[new] ?: error("Mapped library $old -> $new not found")
}
return@run result
}
val libraries: List<PLibrary> = mappings.values.filter { it.isPresent }.map { it.get() }
override fun invoke(project: PProject, dependency: PDependency): List<PDependency> {
if (dependency !is PDependency.ModuleLibrary) {
return listOf(dependency)
}
val root = dependency.library.classes.singleOrNull() ?: return listOf(dependency)
val paths = project.artifacts[root.absolutePath]
if (paths == null) {
val projectDir = rootProject.projectDir
if (projectDir.isParent(root) && LIB_DIRECTORIES.none { File(projectDir, it).isParent(root) }) {
rootProject.logger.warn("Paths not found for root: ${root.absolutePath}")
return emptyList()
}
return listOf(dependency)
}
val result = mutableListOf<PDependency>()
for (path in paths) {
val module = project.modules.find { it.path == path }
if (module != null) {
result += PDependency.Module(module.name)
continue
}
val maybeLibrary = mappings[path]
if (maybeLibrary == null) {
rootProject.logger.warn("Library not found for root: ${root.absolutePath} ($path)")
continue
}
if (maybeLibrary.isPresent) {
result += PDependency.Library(maybeLibrary.get().name)
}
}
return result
}
private fun File.isParent(child: File): Boolean {
var parent = child.parentFile ?: return false
while (true) {
if (parent == this) {
return true
}
parent = parent.parentFile ?: return false
}
}
}
private fun attachPlatformSources(@Suppress("UNUSED_PARAMETER") project: PProject, dependency: PDependency): List<PDependency> {
if (dependency is PDependency.ModuleLibrary) {
val library = dependency.library
val platformSourcesJar = File(platformDir, "../../../sources/intellij-$platformVersion-sources.jar")
if (library.classes.any { it.startsWith(platformDir) || it.startsWith(intellijCoreDir) }) {
return listOf(dependency.copy(library = library.attachSource(platformSourcesJar)))
}
}
return listOf(dependency)
}
private fun attachAsmSources(@Suppress("UNUSED_PARAMETER") project: PProject, dependency: PDependency): List<PDependency> {
if (dependency is PDependency.ModuleLibrary) {
val library = dependency.library
val asmSourcesJar = File(platformDir, "../asm-shaded-sources/asm-src-$platformBaseNumber.jar")
val asmAllJar = File(platformDir, "lib/asm-all.jar")
if (library.classes.any { it == asmAllJar }) {
return listOf(dependency.copy(library = library.attachSource(asmSourcesJar)))
}
}
return listOf(dependency)
}
private fun PProject.mapDependencies(mappers: List<(PProject, PDependency) -> List<PDependency>>): PProject {
fun mapRoot(root: POrderRoot): List<POrderRoot> {
val dependencies = mapDependency(root.dependency, mappers)
return dependencies.map { root.copy(dependency = it) }
}
val modules = this.modules.map { module ->
val newOrderRoots = module.orderRoots.flatMap(::mapRoot).distinct()
module.copy(orderRoots = newOrderRoots)
}
return this.copy(modules = modules)
}
private fun PProject.mapDependency(
dependency: PDependency,
mappers: List<(PProject, PDependency) -> List<PDependency>>
): List<PDependency> {
var dependencies = listOf(dependency)
for (mapper in mappers) {
val newDependencies = mutableListOf<PDependency>()
for (dep in dependencies) {
newDependencies += mapper(this, dep)
}
dependencies = newDependencies
}
return dependencies
}
}
@@ -0,0 +1,57 @@
/*
* 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 java.io.File
private val USER_HOME_DIR_PATH = System.getProperty("user.home").withSlash()
private fun replacePrefix(path: String, prefix: String, variableName: String): String {
if (path.startsWith(prefix)) {
return "$" + variableName + "$/" + path.drop(prefix.length)
}
return path
}
private fun simplifyUserHomeDirPath(path: String): String {
return replacePrefix(path, USER_HOME_DIR_PATH, "USER_HOME")
}
interface PathContext {
operator fun invoke(file: File): String
fun url(file: File): Pair<String, String> {
val path = when {
file.isFile && file.extension.toLowerCase() == "jar" -> "jar://" + this(file) + "!/"
else -> "file://" + this(file)
}
return Pair("url", path)
}
}
class ProjectContext private constructor(private val projectDir: File) : PathContext {
constructor(project: PProject) : this(project.rootDirectory)
constructor(project: Project) : this(project.projectDir)
override fun invoke(file: File): String {
return simplifyUserHomeDirPath(replacePrefix(file.absolutePath, projectDir.absolutePath.withSlash(), "PROJECT_DIR"))
}
}
class ModuleContext(private val project: PProject, val module: PModule) : PathContext {
override fun invoke(file: File): String {
if (!file.startsWith(project.rootDirectory)) {
return simplifyUserHomeDirPath(file.absolutePath)
}
return "\$MODULE_DIR\$/" + file.toRelativeString(module.moduleFile.parentFile)
}
}
fun String.withSlash() = if (this.endsWith("/")) this else ("$this/")
@@ -0,0 +1,51 @@
/*
* 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 java.io.File
import org.gradle.api.Project
open class PillExtensionMirror(variant: String, val excludedDirs: List<File>) {
val variant = Variant.valueOf(variant)
enum class Variant {
// Default variant (./gradlew pill)
BASE() {
override val includes = setOf(BASE)
},
// Full variant (./gradlew pill -Dpill.variant=full)
FULL() {
override val includes = setOf(BASE, FULL)
},
// Do not import the project to JPS model, but set some options for it
NONE() {
override val includes = emptySet<Variant>()
},
// 'BASE' if the "jps-compatible" plugin is applied, 'NONE' otherwise
DEFAULT() {
override val includes = emptySet<Variant>()
};
abstract val includes: Set<Variant>
}
}
fun Project.findPillExtensionMirror(): PillExtensionMirror? {
val ext = extensions.findByName("pill") ?: return null
@Suppress("UNCHECKED_CAST")
val serialized = ext::class.java.getMethod("serialize").invoke(ext) as Map<String, Any>
val variant = serialized["variant"] as String
@Suppress("UNCHECKED_CAST")
val excludedDirs = serialized["excludedDirs"] as List<File>
val constructor = PillExtensionMirror::class.java.declaredConstructors.single()
return constructor.newInstance(variant, excludedDirs) as PillExtensionMirror
}
@@ -0,0 +1,30 @@
/*
* 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 java.io.File
object EmbeddedComponents {
lateinit var CONFIGURATION_NAME: String
}
@Suppress("unused")
object PillImporter {
private val TASKS = mapOf(
"pill" to JpsCompatiblePluginTasks::pill,
"unpill" to JpsCompatiblePluginTasks::unpill
)
@JvmStatic
fun run(rootProject: Project, taskName: String, platformDir: File, resourcesDir: File, embeddedConfigurationName: String) {
EmbeddedComponents.CONFIGURATION_NAME = embeddedConfigurationName
val tasks = JpsCompatiblePluginTasks(rootProject, platformDir, resourcesDir)
val task = TASKS[taskName] ?: error("Unknown task $taskName, available tasks: " + TASKS.keys.joinToString())
task(tasks)
}
}
+63
View File
@@ -0,0 +1,63 @@
/*
* 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 shadow.org.jdom2.Document
import shadow.org.jdom2.Element
import shadow.org.jdom2.output.Format
import shadow.org.jdom2.output.XMLOutputter
fun xml(name: String, vararg args: Pair<String, Any>, block: XmlNode.() -> Unit = {}): XmlNode {
return XmlNode(name, args.asList(), block)
}
class XmlNode(val name: String, private val args: List<Pair<String, Any>>, block: XmlNode.() -> Unit = {}) {
private val children = mutableListOf<XmlNode>()
private var value: Any? = null
init {
@Suppress("UNUSED_EXPRESSION")
block()
}
fun xml(name: String, vararg args: Pair<String, Any>, block: XmlNode.() -> Unit = {}) {
children += XmlNode(name, args.asList(), block = block)
}
fun add(xml: XmlNode) {
children += xml
}
fun raw(text: String) {
value = text
}
private fun toElement(): Element {
val element = Element(name)
for (arg in args) {
element.setAttribute(arg.first, arg.second.toString())
}
require(value == null || children.isEmpty())
value?.let { value ->
element.addContent(value.toString())
}
for (child in children) {
element.addContent(child.toElement())
}
return element
}
override fun toString(): String {
val document = Document().also { it.rootElement = toElement() }
val output = XMLOutputter().also { it.format = Format.getPrettyFormat() }
return output.outputString(document)
}
}
@@ -0,0 +1,174 @@
/*
* 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.kotlin.dsl.extra
import org.jetbrains.kotlin.pill.ArtifactElement.*
import java.io.File
class PArtifact(val artifactName: String, private val outputDir: File, private val contents: Root) {
fun render(context: PathContext) = xml("component", "name" to "ArtifactManager") {
xml("artifact", "name" to artifactName) {
xml("output-path") {
raw(context(outputDir))
}
add(contents.renderRecursively(context))
}
}
}
interface OpaqueDependencyMapper {
fun map(dependency: PDependency): List<PDependency>
}
sealed class ArtifactElement {
private val myChildren = mutableListOf<ArtifactElement>()
private val children get() = myChildren
fun add(child: ArtifactElement) {
myChildren += child
}
fun add(children: List<ArtifactElement>) {
myChildren += children
}
abstract fun render(context: PathContext): XmlNode
fun renderRecursively(context: PathContext): XmlNode {
return render(context).apply {
children.forEach { add(it.renderRecursively(context)) }
}
}
class Root : ArtifactElement() {
override fun render(context: PathContext) = xml("root", "id" to "root")
}
data class Directory(val name: String) : ArtifactElement() {
override fun render(context: PathContext) = xml("element", "id" to "directory", "name" to name)
}
data class Archive(val name: String) : ArtifactElement() {
override fun render(context: PathContext) = xml("element", "id" to "archive", "name" to name)
}
data class ModuleOutput(val moduleName: String) : ArtifactElement() {
override fun render(context: PathContext) = xml("element", "id" to "module-output", "name" to moduleName)
}
data class FileCopy(val source: File, val outputFileName: String? = null) : ArtifactElement() {
override fun render(context: PathContext): XmlNode {
val args = mutableListOf("id" to "file-copy", "path" to context(source))
if (outputFileName != null) {
args += "output-file-name" to outputFileName
}
return xml("element", *args.toTypedArray())
}
}
data class DirectoryCopy(val source: File) : ArtifactElement() {
override fun render(context: PathContext) = xml("element", "id" to "dir-copy", "path" to context(source))
}
data class ProjectLibrary(val name: String) : ArtifactElement() {
override fun render(context: PathContext) = xml("element", "id" to "library", "level" to "project", "name" to name)
}
data class ExtractedDirectory(val archive: File, val pathInJar: String = "/") : ArtifactElement() {
override fun render(context: PathContext) =
xml("element", "id" to "extracted-dir", "path" to context(archive), "path-in-jar" to pathInJar)
}
}
fun generateKotlinPluginArtifactFile(rootProject: Project, dependencyMapper: OpaqueDependencyMapper): PFile {
val root = Root()
fun Project.getProject(name: String) = findProject(name) ?: error("Cannot find project $name")
val prepareIdeaPluginProject = rootProject.getProject(":prepare:idea-plugin")
root.add(Directory("kotlinc").apply {
val kotlincDirectory = rootProject.extra["distKotlinHomeDir"].toString()
add(DirectoryCopy(File(kotlincDirectory)))
})
root.add(Directory("lib").apply {
val librariesConfiguration = prepareIdeaPluginProject.configurations.getByName("libraries")
add(getArtifactElements(librariesConfiguration, dependencyMapper, false))
add(Directory("jps").apply {
val prepareJpsPluginProject = rootProject.getProject(":kotlin-jps-plugin")
add(Archive(prepareJpsPluginProject.name + ".jar").apply {
val jpsPluginConfiguration = prepareIdeaPluginProject.configurations.getByName("jpsPlugin")
add(getArtifactElements(jpsPluginConfiguration, dependencyMapper, true))
})
})
add(Archive("kotlin-plugin.jar").apply {
add(FileCopy(File(rootProject.projectDir, "resources/kotlinManifest.properties")))
val embeddedConfiguration = prepareIdeaPluginProject.configurations.getByName(EMBEDDED_CONFIGURATION_NAME)
add(getArtifactElements(embeddedConfiguration, dependencyMapper, true))
})
})
val artifact = PArtifact("KotlinPlugin", File(rootProject.projectDir, "out/artifacts/Kotlin"), root)
return PFile(
File(rootProject.projectDir, ".idea/artifacts/${artifact.artifactName}.xml"),
artifact.render(ProjectContext(rootProject))
)
}
private fun getArtifactElements(
configuration: Configuration,
dependencyMapper: OpaqueDependencyMapper,
extractDependencies: Boolean
): List<ArtifactElement> {
val dependencies = parseDependencies(configuration, dependencyMapper)
val artifacts = mutableListOf<ArtifactElement>()
for (dependency in dependencies) {
when (dependency) {
is PDependency.Module -> {
val moduleOutput = ModuleOutput(dependency.name)
if (extractDependencies) {
artifacts += moduleOutput
} else {
artifacts += Archive(dependency.name + ".jar").apply {
add(moduleOutput)
}
}
}
is PDependency.Library -> artifacts += ProjectLibrary(dependency.name)
is PDependency.ModuleLibrary -> {
val files = dependency.library.classes
if (extractDependencies) {
files.mapTo(artifacts) { ExtractedDirectory(it) }
} else {
files.mapTo(artifacts) { FileCopy(it) }
}
}
}
}
return artifacts
}
private fun parseDependencies(configuration: Configuration, dependencyMapper: OpaqueDependencyMapper): List<PDependency> {
val dependencies = mutableListOf<PDependency>()
for (file in configuration.resolve()) {
val library = PLibrary(file.name, listOf(file))
dependencies += dependencyMapper.map(PDependency.ModuleLibrary(library))
}
return dependencies
}
+403
View File
@@ -0,0 +1,403 @@
/*
* 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.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)
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
)
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.takeUnless { it == Variant.DEFAULT } ?: 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 parseModules(project: Project, excludedProjects: List<Project>): List<PModule> {
val modules = mutableListOf<PModule>()
fun getModuleFile(name: String): File {
val relativePath = File(project.projectDir, "$name.iml").toRelativeString(project.rootProject.projectDir)
return File(project.rootProject.projectDir, ".idea/modules/$relativePath")
}
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 = 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
)
}
val mainModuleFileRelativePath = when (project) {
project.rootProject -> File(project.rootProject.projectDir, project.rootProject.name + ".iml")
else -> getModuleFile(project.pillModuleName)
}
modules += PModule(
name = 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
)
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")
}
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
for (file in configuration.resolve()) {
val library = PLibrary(file.name, listOf(file))
val dependency = PDependency.ModuleLibrary(library)
roots += POrderRoot(dependency, scope)
}
}
process(sourceSet.compileClasspathConfigurationName, Scope.PROVIDED)
process(sourceSet.runtimeClasspathConfigurationName, Scope.RUNTIME)
if (sourceSet.forTests) {
process("jpsTest", Scope.TEST)
}
return roots
}
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
}
+201
View File
@@ -0,0 +1,201 @@
/*
* 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 java.io.File
class PFile(val path: File, private val text: String) {
constructor(path: File, xml: XmlNode) : this(path, xml.toString())
fun write() {
path.parentFile.mkdirs()
path.writeText(text)
}
}
fun render(project: PProject): List<PFile> {
val files = mutableListOf<PFile>()
files += renderModulesFile(project)
project.modules.forEach { files += renderModule(project, it) }
project.libraries.forEach { files += renderLibrary(project, it) }
return files
}
private fun renderModulesFile(project: PProject) = PFile(
File(project.rootDirectory, ".idea/modules.xml"),
xml("project", "version" to 4) {
xml("component", "name" to "ProjectModuleManager") {
xml("modules") {
val pathContext = ProjectContext(project)
for (module in project.modules) {
val moduleFilePath = pathContext(module.moduleFile)
xml("module", "fileurl" to "file://$moduleFilePath", "filepath" to moduleFilePath)
}
}
}
}
)
private fun renderModule(project: PProject, module: PModule) = PFile(
module.moduleFile,
xml(
"module",
"type" to "JAVA_MODULE",
"version" to 4
) {
val moduleForProductionSources = module.moduleForProductionSources
if (moduleForProductionSources != null) {
xml("component", "name" to "TestModuleProperties", "production-module" to moduleForProductionSources.name)
}
val kotlinCompileOptions = module.kotlinOptions
val pathContext = ModuleContext(project, module)
val platformVersion = (kotlinCompileOptions?.jvmTarget ?: "1.8")
val classesDirectory = File(project.rootDirectory, "out/production/${module.name}")
if (kotlinCompileOptions != null) {
xml("component", "name" to "FacetManager") {
xml("facet", "type" to "kotlin-language", "name" to "Kotlin") {
xml("configuration", "version" to 3, "platform" to "JVM $platformVersion", "useProjectSettings" to "false") {
xml("compilerSettings") {
xml(
"option",
"name" to "additionalArguments",
"value" to kotlinCompileOptions.extraArguments.joinToString(" ")
)
}
xml("compilerArguments") {
xml("option", "name" to "destination", "value" to pathContext(classesDirectory))
fun Any?.option(name: String) {
if (this != null) xml("option", "name" to name, "value" to this.toString())
}
kotlinCompileOptions.noStdlib.option("noStdlib")
kotlinCompileOptions.noReflect.option("noReflect")
module.name.option("moduleName")
xml("option", "name" to "jvmTarget", "value" to platformVersion)
kotlinCompileOptions.languageVersion.option("languageVersion")
kotlinCompileOptions.apiVersion.option("apiVersion")
xml("option", "name" to "pluginOptions") { xml("array") }
xml("option", "name" to "pluginClasspaths") { xml("array") }
}
}
}
}
}
xml(
"component",
"name" to "NewModuleRootManager",
"LANGUAGE_LEVEL" to "JDK_${platformVersion.replace('.', '_')}",
"inherit-compiler-output" to "true"
) {
xml("exclude-output")
for (contentRoot in module.contentRoots.filter { it.path.exists() }) {
xml("content", pathContext.url(contentRoot.path)) {
for (sourceRoot in contentRoot.sourceRoots) {
var args = arrayOf(pathContext.url(sourceRoot.directory))
args += when (sourceRoot.kind) {
PSourceRoot.Kind.PRODUCTION -> ("isTestSource" to "false")
PSourceRoot.Kind.TEST -> ("isTestSource" to "true")
PSourceRoot.Kind.RESOURCES -> ("type" to "java-resource")
PSourceRoot.Kind.TEST_RESOURCES -> ("type" to "java-test-resource")
}
xml("sourceFolder", *args)
}
for (excludedDir in contentRoot.excludedDirectories) {
xml("excludeFolder", pathContext.url(excludedDir))
}
}
}
xml("orderEntry", "type" to "inheritedJdk")
xml("orderEntry", "type" to "sourceFolder", "forTests" to "false")
for (orderRoot in module.orderRoots) {
val dependency = orderRoot.dependency
val args = when (dependency) {
is PDependency.ModuleLibrary -> mutableListOf(
"type" to "module-library"
)
is PDependency.Module -> mutableListOf(
"type" to "module",
"module-name" to dependency.name
)
is PDependency.Library -> mutableListOf(
"type" to "library",
"name" to dependency.name,
"level" to "project"
)
}
if (orderRoot.scope != POrderRoot.Scope.COMPILE) {
args.add(1, "scope" to orderRoot.scope.toString())
}
if (dependency is PDependency.Module && orderRoot.isProductionOnTestDependency) {
args += ("production-on-test" to "")
}
if (orderRoot.isExported) {
args += ("exported" to "")
}
xml("orderEntry", *args.toTypedArray()) {
if (dependency is PDependency.ModuleLibrary) {
add(renderLibraryToXml(dependency.library, pathContext, named = false))
}
}
}
}
}
)
private fun renderLibrary(project: PProject, library: PLibrary): PFile {
val pathContext = ProjectContext(project)
// TODO find how IDEA escapes library names
val escapedName = library.renderName().replace(" ", "_").replace(".", "_").replace("-", "_")
return PFile(
File(project.rootDirectory, ".idea/libraries/$escapedName.xml"),
xml("component", "name" to "libraryTable") {
add(renderLibraryToXml(library, pathContext))
})
}
private fun renderLibraryToXml(library: PLibrary, pathContext: PathContext, named: Boolean = true): XmlNode {
val args = if (named) arrayOf("name" to library.renderName()) else emptyArray()
return xml("library", *args) {
xml("CLASSES") {
library.classes.forEach { xml("root", pathContext.url(it)) }
}
xml("JAVADOC") {
library.javadoc.forEach { xml("root", pathContext.url(it)) }
}
xml("SOURCES") {
library.sources.forEach { xml("root", pathContext.url(it)) }
}
}
}
fun PLibrary.renderName() = name.takeIf { it != "unspecified" } ?: classes.first().nameWithoutExtension