diff --git a/.gitignore b/.gitignore index b853c5f3658..e7b4249ebf3 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ build/ !**/testData/**/*.iml .idea/libraries/Gradle*.xml .idea/libraries/Maven*.xml +.idea/artifacts .idea/modules .idea/runConfigurations/JPS_*.xml .idea/libraries diff --git a/buildSrc/src/main/kotlin/pill/kotlinPluginArtifact.kt b/buildSrc/src/main/kotlin/pill/kotlinPluginArtifact.kt new file mode 100644 index 00000000000..bb87dfe2206 --- /dev/null +++ b/buildSrc/src/main/kotlin/pill/kotlinPluginArtifact.kt @@ -0,0 +1,183 @@ +@file:Suppress("PackageDirectoryMismatch") + +package org.jetbrains.kotlin.pill + +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.internal.file.copy.SingleParentCopySpec +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.Copy +import org.gradle.jvm.tasks.Jar +import org.gradle.kotlin.dsl.extra +import org.jetbrains.kotlin.pill.ArtifactElement.* +import org.jetbrains.kotlin.pill.POrderRoot.* +import java.io.File + +class PArtifact(val artifactName: String, val outputDir: File, private val contents: ArtifactElement.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)) + } + } +} + +sealed class ArtifactElement { + private val myChildren = mutableListOf() + val children get() = myChildren + + fun add(child: ArtifactElement) { + myChildren += child + } + + abstract fun render(context: PathContext): xml + + fun renderRecursively(context: PathContext): xml { + return render(context).apply { + children.forEach { add(it.renderRecursively(context)) } + } + } + + fun getDirectory(path: String): ArtifactElement { + if (path.isEmpty()) { + return this + } + + var current: ArtifactElement = this + for (segment in path.split("/")) { + val existing = current.children.firstOrNull { it is Directory && it.name == segment } + if (existing != null) { + current = existing + continue + } + + current = Directory(segment).also { current.add(it) } + } + + return current + } + + 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) : ArtifactElement() { + override fun render(context: PathContext) = xml("element", "id" to "file-copy", "path" to context(source)) + } + + 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): PFile { + val mainIdeaPluginTask = rootProject.tasks.getByName("ideaPlugin") as Copy + val gradleArtifactDir = mainIdeaPluginTask.destinationDir + + val ideaPluginTasks = mainIdeaPluginTask.taskDependencies + .getDependencies(mainIdeaPluginTask) + .filter { it.name == "ideaPlugin" } + .filterIsInstance() + + val root = Root() + + // Copy kotlinc directory + root.add(DirectoryCopy(File(rootProject.extra["distKotlinHomeDir"].toString()))) + + for (task in ideaPluginTasks) { + val spec = task.rootSpec.children.filterIsInstance().singleOrNull() + ?: error("Copy spec is not unique in ${rootProject.name}. Available specs: ${task.rootSpec.children}") + + val sourcePaths = spec.sourcePaths + for (sourcePath in sourcePaths) { + if (sourcePath is ShadowJar) { + if (sourcePath.project.path == ":prepare:idea-plugin") { + val kotlinPluginJar = Archive(sourcePath.archiveName).also { root.getDirectory("lib").add(it) } + + kotlinPluginJar.add(FileCopy(File(rootProject.projectDir, "resources/kotlinManifest.properties"))) + + for (jarFile in sourcePath.project.configurations.getByName("packedJars").resolve()) { + kotlinPluginJar.add(ExtractedDirectory(jarFile)) + } + + @Suppress("UNCHECKED_CAST") + for (projectPath in sourcePath.project.extra["projectsToShadow"] as List) { + val jpsModuleName = rootProject.findProject(projectPath)!!.name + ".src" + kotlinPluginJar.add(ModuleOutput(jpsModuleName)) + } + + continue + } + } + + when (sourcePath) { + is Jar -> { + val targetDir = ("lib/" + task.destinationDir.toRelativeString(gradleArtifactDir)).withoutSlash() + + val archiveForJar = Archive(sourcePath.project.name + ".jar").apply { + if (task.project.plugins.hasPlugin(JavaPlugin::class.java)) { + add(ModuleOutput(sourcePath.project.name + ".src")) + } + root.getDirectory(targetDir).add(this) + } + + val fatJarContentsConfiguration = sourcePath.project.configurations + .findByName("fatJarContents")?.resolvedConfiguration + + if (fatJarContentsConfiguration != null) { + for ((_, _, dependency) in listOf(fatJarContentsConfiguration to Scope.COMPILE).collectDependencies()) { + if (dependency.configuration == "runtimeElements") { + archiveForJar.add(ModuleOutput(dependency.moduleName + ".src")) + } else if (dependency.configuration == "tests-jar" || dependency.configuration == "jpsTest") { + error("Test configurations are not allowed here") + } else { + for (file in dependency.moduleArtifacts.map { it.file }) { + archiveForJar.add(ExtractedDirectory(file)) + } + } + } + } + } + is Configuration -> { + require(sourcePath.name == "sideJars") { "Configurations other than 'sideJars' are not supported" } + for (file in sourcePath.resolve()) { + root.getDirectory("lib").add(FileCopy(file)) + } + } + else -> error("${task.name} Unexpected task type ${task.javaClass.name}") + } + } + } + + 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)) + ) +} + diff --git a/buildSrc/src/main/kotlin/pill/parser.kt b/buildSrc/src/main/kotlin/pill/parser.kt index f6dfe607023..6ed3046bf19 100644 --- a/buildSrc/src/main/kotlin/pill/parser.kt +++ b/buildSrc/src/main/kotlin/pill/parser.kt @@ -345,9 +345,9 @@ private fun removeDuplicates(roots: List): List { return result.toList() } -private data class DependencyInfo(val configuration: ResolvedConfiguration, val scope: Scope, val dependency: ResolvedDependency) +data class DependencyInfo(val configuration: ResolvedConfiguration, val scope: Scope, val dependency: ResolvedDependency) -private fun List>.collectDependencies(): List { +fun List>.collectDependencies(): List { val dependencies = mutableListOf() val unprocessed = LinkedList() diff --git a/buildSrc/src/main/kotlin/pill/pathContext.kt b/buildSrc/src/main/kotlin/pill/pathContext.kt index be1557a44b7..92b8abca9cf 100644 --- a/buildSrc/src/main/kotlin/pill/pathContext.kt +++ b/buildSrc/src/main/kotlin/pill/pathContext.kt @@ -1,6 +1,7 @@ @file:Suppress("PackageDirectoryMismatch") package org.jetbrains.kotlin.pill +import org.gradle.api.Project import java.io.File interface PathContext { @@ -16,10 +17,12 @@ interface PathContext { } } -class ProjectContext(val project: PProject) : PathContext { +class ProjectContext private constructor(val projectDir: File) : PathContext { + constructor(project: PProject) : this(project.rootDirectory) + constructor(project: Project) : this(project.projectDir) + override fun invoke(file: File): String { - return file.absolutePath - .replace(project.rootDirectory.absolutePath, "\$PROJECT_DIR\$") + return file.absolutePath.replace(projectDir.absolutePath, "\$PROJECT_DIR\$") } } @@ -29,11 +32,12 @@ class ModuleContext(val project: PProject, val module: PModule) : PathContext { return file.absolutePath } - fun String.withSlash() = if (this.endsWith("/")) this else (this + "/") - return "\$MODULE_DIR\$/" + project.rootDirectory.toRelativeString(module.moduleFile.parentFile).withSlash() + module.rootDirectory.toRelativeString(project.rootDirectory).withSlash() + file.toRelativeString(module.rootDirectory) } -} \ No newline at end of file +} + +fun String.withSlash() = if (this.endsWith("/")) this else (this + "/") +fun String.withoutSlash() = this.trimEnd('/') \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/pill/plugin.kt b/buildSrc/src/main/kotlin/pill/plugin.kt index 2962f239aa9..2291a9d5a56 100644 --- a/buildSrc/src/main/kotlin/pill/plugin.kt +++ b/buildSrc/src/main/kotlin/pill/plugin.kt @@ -4,14 +4,9 @@ import org.gradle.api.Plugin import org.gradle.api.Project import shadow.org.jdom2.input.SAXBuilder import shadow.org.jdom2.* -import shadow.org.jdom2.output.DOMOutputter import shadow.org.jdom2.output.Format import shadow.org.jdom2.output.XMLOutputter -import javax.xml.transform.TransformerFactory -import javax.xml.transform.dom.* import java.io.File -import java.io.FileOutputStream -import javax.xml.transform.stream.StreamResult class JpsCompatiblePlugin : Plugin { override fun apply(project: Project) { @@ -104,6 +99,7 @@ class JpsCompatibleRootPlugin : Plugin { } } + private lateinit var projectDir: File private lateinit var platformVersion: String private lateinit var platformBaseNumber: String @@ -123,6 +119,8 @@ class JpsCompatibleRootPlugin : Plugin { val jpsProject = parse(project, ParserContext(dependencyMappers)) .mapLibraries(this::attachPlatformSources, this::attachAsmSources) + generateKotlinPluginArtifactFile(project).write() + val files = render(jpsProject, getProjectLibraries(jpsProject)) removeExistingIdeaLibrariesAndModules() @@ -131,11 +129,7 @@ class JpsCompatibleRootPlugin : Plugin { copyRunConfigurations() setOptionsForDefaultJunitRunConfiguration(project) - for (file in files) { - val stubFile = file.path - stubFile.parentFile.mkdirs() - stubFile.writeText(file.text) - } + files.forEach { it.write() } } private fun unpill(project: Project) { diff --git a/buildSrc/src/main/kotlin/pill/render.kt b/buildSrc/src/main/kotlin/pill/render.kt index 1afb62498bc..10a50e49058 100644 --- a/buildSrc/src/main/kotlin/pill/render.kt +++ b/buildSrc/src/main/kotlin/pill/render.kt @@ -3,7 +3,13 @@ package org.jetbrains.kotlin.pill import java.io.File -class PFile(val path: File, val text: String) +class PFile(val path: File, val text: String) { + fun write() { + path.parentFile.mkdirs() + path.writeText(text) + } +} + fun PFile(path: File, xml: xml) = PFile(path, xml.toString()) fun render(project: PProject, extraLibraries: List = emptyList()): List { diff --git a/buildSrc/src/main/kotlin/pill/runConfigurations/JPS_IDEA.xml b/buildSrc/src/main/kotlin/pill/runConfigurations/JPS_IDEA.xml index a73f26ae3cb..cc5d12bdd88 100644 --- a/buildSrc/src/main/kotlin/pill/runConfigurations/JPS_IDEA.xml +++ b/buildSrc/src/main/kotlin/pill/runConfigurations/JPS_IDEA.xml @@ -3,7 +3,7 @@