From af60bcfb47588a148ac242c5979b3028360270b7 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Wed, 14 Feb 2018 23:50:45 +0300 Subject: [PATCH] Initial Pill implementation Pill is a Gradle-to-JPS model generator for compiler + compiler plugins + IDE project parts. The generated project model does not know anything about Gradle and is fully operable using only JPS. --- .gitignore | 1 + buildSrc/src/main/kotlin/pill/context.kt | 15 + buildSrc/src/main/kotlin/pill/parser.kt | 355 ++++++++++++++++++ buildSrc/src/main/kotlin/pill/pathContext.kt | 34 ++ buildSrc/src/main/kotlin/pill/plugin.kt | 133 +++++++ buildSrc/src/main/kotlin/pill/render.kt | 149 ++++++++ buildSrc/src/main/kotlin/pill/xml.kt | 50 +++ .../jps-compatible-root.properties | 1 + .../gradle-plugins/jps-compatible.properties | 1 + 9 files changed, 739 insertions(+) create mode 100644 buildSrc/src/main/kotlin/pill/context.kt create mode 100644 buildSrc/src/main/kotlin/pill/parser.kt create mode 100644 buildSrc/src/main/kotlin/pill/pathContext.kt create mode 100644 buildSrc/src/main/kotlin/pill/plugin.kt create mode 100644 buildSrc/src/main/kotlin/pill/render.kt create mode 100644 buildSrc/src/main/kotlin/pill/xml.kt create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/jps-compatible-root.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/jps-compatible.properties diff --git a/.gitignore b/.gitignore index b2984810be3..67ed416a356 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ build/ .idea/libraries/Gradle*.xml .idea/libraries/Maven*.xml .idea/modules +.idea/libraries .idea/modules.xml .idea/gradle.xml .idea/compiler.xml diff --git a/buildSrc/src/main/kotlin/pill/context.kt b/buildSrc/src/main/kotlin/pill/context.kt new file mode 100644 index 00000000000..f1ff767ebf4 --- /dev/null +++ b/buildSrc/src/main/kotlin/pill/context.kt @@ -0,0 +1,15 @@ +@file:Suppress("PackageDirectoryMismatch") +package org.jetbrains.kotlin.pill + +import org.gradle.api.artifacts.* + +class DependencyMapper( + val group: String, + val module: String, + vararg val configurations: String, + val mapping: (ResolvedDependency) -> MappedDependency? +) + +class MappedDependency(val main: PDependency, val deferred: List = emptyList()) + +class ParserContext(val dependencyMappers: List) \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/pill/parser.kt b/buildSrc/src/main/kotlin/pill/parser.kt new file mode 100644 index 00000000000..a3064e545ce --- /dev/null +++ b/buildSrc/src/main/kotlin/pill/parser.kt @@ -0,0 +1,355 @@ +@file:Suppress("PackageDirectoryMismatch") +package org.jetbrains.kotlin.pill + +import org.gradle.api.Project +import org.gradle.api.artifacts.* +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.kotlin.dsl.configure +import org.gradle.plugins.ide.idea.IdeaPlugin +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.internal.HasConvention +import org.jetbrains.kotlin.pill.POrderRoot.* +import org.jetbrains.kotlin.pill.PSourceRoot.* +import java.io.File +import java.util.LinkedList + +data class PProject( + val name: String, + val rootDirectory: File, + val modules: List, + val libraries: List +) + +data class PModule( + val name: String, + val bundleName: String, + val rootDirectory: File, + val contentRoots: List, + val orderRoots: List, + val moduleForProductionSources: PModule? = null, + val group: String? = null +) + +data class PContentRoot( + val path: File, + val forTests: Boolean, + val sourceRoots: List, + val excludedDirectories: List +) + +data class PSourceRoot( + val path: File, + val kind: Kind +) { + enum class Kind { + PRODUCTION, TEST, RESOURCES, TEST_RESOURCES; + + val isResources get() = this == RESOURCES || this == TEST_RESOURCES + } +} + +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, + val javadoc: List = emptyList(), + val sources: List = emptyList(), + val annotations: List = emptyList(), + val dependencies: List = emptyList() +) { + fun attachSource(file: File): PLibrary { + return this.copy(sources = this.sources + listOf(file)) + } +} + +fun parse(project: Project, context: ParserContext): PProject = with (context) { + val modules = project.allprojects + .filter { it.plugins.hasPlugin(JpsCompatiblePlugin::class.java) } + .flatMap { parseModules(it) } + + return PProject("Kotlin", project.rootProject.projectDir, modules, emptyList()) +} + +private val CONFIGURATION_MAPPING = mapOf( + listOf("compile") to Scope.COMPILE, + listOf("compileOnly") to Scope.PROVIDED, + listOf("runtime") to Scope.RUNTIME +) + +private val TEST_CONFIGURATION_MAPPING = mapOf( + listOf("compile", "testCompile") to Scope.COMPILE, + listOf("compileOnly", "testCompileOnly") to Scope.PROVIDED, + listOf("runtime", "testRuntime") to Scope.RUNTIME +) + +private val SOURCE_SET_MAPPING = mapOf( + "main" to Kind.PRODUCTION, + "test" to Kind.TEST +) + +private fun ParserContext.parseModules(project: Project): List { + val (productionContentRoots, testContentRoots) = parseContentRoots(project).partition { !it.forTests } + + val modules = mutableListOf() + + fun getJavaExcludedDirs() = project.plugins.findPlugin(IdeaPlugin::class.java) + ?.model?.module?.excludeDirs?.toList() ?: emptyList() + + val allExcludedDirs = getJavaExcludedDirs() + project.buildDir + + var productionSourcesModule: PModule? = null + + for ((nameSuffix, roots) in mapOf("src" to productionContentRoots, "test" to testContentRoots)) { + if (roots.isEmpty()) { + continue + } + + val mainRoot = roots.first() + + var dependencies = parseDependencies(project, mainRoot.forTests) + if (productionContentRoots.isNotEmpty() && mainRoot.forTests) { + val productionModuleDependency = PDependency.Module(project.name + ".src") + dependencies += POrderRoot(productionModuleDependency, Scope.COMPILE, true) + } + + val module = PModule( + project.name + "." + nameSuffix, + project.name, + mainRoot.path, + roots, + dependencies, + productionSourcesModule + ) + + modules += module + + if (!mainRoot.forTests) { + productionSourcesModule = module + } + } + + modules += PModule( + project.name, + project.name, + project.projectDir, + listOf(PContentRoot(project.projectDir, false, emptyList(), allExcludedDirs)), + if (modules.isEmpty()) parseDependencies(project, false) else emptyList() + ) + + return modules +} + +private fun parseContentRoots(project: Project): List { + val sourceRoots = parseSourceRoots(project).groupBy { it.kind } + fun getRoots(kind: PSourceRoot.Kind) = sourceRoots[kind] ?: emptyList() + + val productionSourceRoots = getRoots(Kind.PRODUCTION) + getRoots(Kind.RESOURCES) + val testSourceRoots = getRoots(Kind.TEST) + getRoots(Kind.TEST_RESOURCES) + + fun createContentRoots(sourceRoots: List, forTests: Boolean): List { + return sourceRoots.map { PContentRoot(it.path, forTests, listOf(it), emptyList()) } + } + + return createContentRoots(productionSourceRoots, forTests = false) + + createContentRoots(testSourceRoots, forTests = true) +} + +private fun parseSourceRoots(project: Project): List { + if (!project.plugins.hasPlugin(JavaPlugin::class.java)) { + return emptyList() + } + + val sourceRoots = mutableListOf() + + project.configure { + for ((sourceSetName, kind) in SOURCE_SET_MAPPING) { + val sourceSet = sourceSets.findByName(sourceSetName) ?: continue + + fun Any.getKotlin(): SourceDirectorySet { + val kotlinMethod = javaClass.getMethod("getKotlin") + val oldIsAccessible = kotlinMethod.isAccessible + try { + kotlinMethod.isAccessible = true + return kotlinMethod(this) as SourceDirectorySet + } finally { + kotlinMethod.isAccessible = oldIsAccessible + } + } + + val kotlinSourceDirectories = (sourceSet as HasConvention).convention + .plugins["kotlin"]?.getKotlin()?.srcDirs + ?: emptySet() + + val directories = (sourceSet.java.sourceDirectories.files + kotlinSourceDirectories).toList() + .filter { it.exists() } + .takeIf { it.isNotEmpty() } + ?: continue + + for (resourceRoot in sourceSet.resources.sourceDirectories.files) { + if (!resourceRoot.exists() || resourceRoot in directories) { + continue + } + + val resourceRootKind = when (kind) { + Kind.PRODUCTION -> Kind.RESOURCES + Kind.TEST -> Kind.TEST_RESOURCES + else -> error("Invalid source root kind $kind") + } + + sourceRoots += PSourceRoot(resourceRoot, resourceRootKind) + } + + for (directory in directories) { + sourceRoots += PSourceRoot(directory, kind) + } + } + } + + return sourceRoots +} + +private fun ParserContext.parseDependencies(project: Project, forTests: Boolean): List { + val configurationMapping = if (forTests) TEST_CONFIGURATION_MAPPING else CONFIGURATION_MAPPING + + with(project.configurations) { + val moduleRoots = mutableListOf() + val libraryRoots = mutableListOf() + val deferredRoots = mutableListOf() + + fun collectConfigurations(): List> { + val configurations = mutableListOf>() + + for ((configurationNames, scope) in configurationMapping) { + for (configurationName in configurationNames) { + val configuration = findByName(configurationName)?.also { it.resolve() } ?: continue + configurations += Pair(configuration.resolvedConfiguration, scope) + } + } + + return configurations + } + + nextDependency@ for ((configuration, scope, dependency) in collectConfigurations().collectDependencies()) { + for (mapper in dependencyMappers) { + if (dependency.moduleGroup == mapper.group + && dependency.moduleName == mapper.module + && dependency.configuration in mapper.configurations + ) { + val mappedDependency = mapper.mapping(dependency) + + if (mappedDependency != null) { + val orderRoot = POrderRoot(mappedDependency.main, scope) + if (mappedDependency.main is PDependency.Module) { + moduleRoots += orderRoot + } else { + libraryRoots += orderRoot + } + + for (deferredDep in mappedDependency.deferred) { + deferredRoots += POrderRoot(deferredDep, scope) + } + } + + continue@nextDependency + } + } + + if (dependency.configuration == "runtimeElements") { + moduleRoots += POrderRoot(PDependency.Module(dependency.moduleName + ".src"), scope) + } else if (dependency.configuration == "tests-jar") { + moduleRoots += POrderRoot( + PDependency.Module(dependency.moduleName + ".test"), + scope, + isProductionOnTestDependency = true + ) + } else { + val classes = dependency.moduleArtifacts.map { it.file } + val library = PLibrary(dependency.moduleName, classes) + libraryRoots += POrderRoot(PDependency.ModuleLibrary(library), scope) + } + } + + return removeDuplicates(moduleRoots + libraryRoots + deferredRoots) + } +} + +private fun removeDuplicates(roots: List): List { + val dependenciesByScope = roots.groupBy { it.scope }.mapValues { it.value.mapTo(mutableSetOf()) { it.dependency } } + fun depenciesFor(scope: Scope) = dependenciesByScope[scope] ?: emptySet() + + val result = mutableSetOf() + for (root in roots.distinct()) { + val scope = root.scope + val dependency = root.dependency + + if (root in result) { + continue + } + + if ((scope == Scope.PROVIDED || scope == Scope.RUNTIME) && dependency in depenciesFor(Scope.COMPILE)) { + continue + } + + if (scope == Scope.PROVIDED && dependency in depenciesFor(Scope.RUNTIME)) { + result += POrderRoot(dependency, Scope.COMPILE) + continue + } + + if (scope == Scope.RUNTIME && dependency in depenciesFor(Scope.PROVIDED)) { + result += POrderRoot(dependency, Scope.COMPILE) + continue + } + + result += root + } + + return result.toList() +} + +private data class DependencyInfo(val configuration: ResolvedConfiguration, val scope: Scope, val dependency: ResolvedDependency) + +private fun List>.collectDependencies(): List { + val dependencies = mutableListOf() + + val unprocessed = LinkedList() + val existing = mutableSetOf>() + + for ((configuration, scope) in this) { + for (dependency in configuration.firstLevelModuleDependencies) { + unprocessed += DependencyInfo(configuration, scope, dependency) + } + } + + while (unprocessed.isNotEmpty()) { + val info = unprocessed.removeAt(0) + dependencies += info + + val data = Pair(info.scope, info.dependency) + existing += data + + for (child in info.dependency.children) { + if (child.moduleGroup != "org.jetbrains.kotlin" && Pair(info.scope, child) in existing) { + continue + } + + unprocessed += DependencyInfo(info.configuration, info.scope, child) + } + } + + return dependencies +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/pill/pathContext.kt b/buildSrc/src/main/kotlin/pill/pathContext.kt new file mode 100644 index 00000000000..a9a67fbd211 --- /dev/null +++ b/buildSrc/src/main/kotlin/pill/pathContext.kt @@ -0,0 +1,34 @@ +@file:Suppress("PackageDirectoryMismatch") +package org.jetbrains.kotlin.pill + +import java.io.File + +interface PathContext { + operator fun invoke(file: File): String + + fun url(file: File): Pair { + val path = when { + file.isFile && file.extension.toLowerCase() == "jar" -> "jar://" + this(file) + "!/" + else -> "file://" + this(file) + } + + return Pair("url", path) + } +} + +class ProjectContext(val project: PProject) : PathContext { + override fun invoke(file: File): String { + return file.absolutePath + .replace(project.rootDirectory.absolutePath, "\$PROJECT_DIR\$") + } +} + +class ModuleContext(val project: PProject, val module: PModule) : PathContext { + override fun invoke(file: File): String { + if (!file.startsWith(project.rootDirectory)) { + return file.absolutePath + } + + return "\$MODULE_DIR\$/" + file.toRelativeString(module.rootDirectory) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/pill/plugin.kt b/buildSrc/src/main/kotlin/pill/plugin.kt new file mode 100644 index 00000000000..b1c87dd30ae --- /dev/null +++ b/buildSrc/src/main/kotlin/pill/plugin.kt @@ -0,0 +1,133 @@ +package org.jetbrains.kotlin.pill + +import org.gradle.api.Plugin +import org.gradle.api.Project +import java.io.File + +class JpsCompatiblePlugin : Plugin { + override fun apply(project: Project) {} +} + +class JpsCompatibleRootPlugin : Plugin { + companion object { + private fun mapper(module: String, vararg configurations: String): DependencyMapper { + return DependencyMapper("org.jetbrains.kotlin", module, *configurations) { MappedDependency(PDependency.Library(module)) } + } + + private val dependencyMappers = listOf( + mapper("protobuf-relocated", "default"), + mapper("kotlin-test-junit", "distJar", "runtimeElements"), + mapper("kotlin-script-runtime", "distJar", "runtimeElements"), + mapper("kotlin-reflect", "distJar", "runtimeElements"), + mapper("kotlin-test-jvm", "distJar", "runtimeElements"), + DependencyMapper("org.jetbrains.kotlin", "kotlin-stdlib", "distJar", "runtimeElements") { + MappedDependency( + PDependency.Library("kotlin-stdlib"), + listOf(PDependency.Library("annotations-13.0")) + ) + }, + DependencyMapper("org.jetbrains.kotlin", "kotlin-reflect-api", "runtimeElements") { + MappedDependency(PDependency.Library("kotlin-reflect")) + }, + DependencyMapper("org.jetbrains.kotlin", "kotlin-compiler-embeddable", "runtimeJar") { null }, + DependencyMapper("org.jetbrains.kotlin", "kotlin-stdlib-js", "distJar") { null }, + DependencyMapper("org.jetbrains.kotlin", "kotlin-compiler", "runtimeJar") { null }, + DependencyMapper("org.jetbrains.kotlin", "compiler", "runtimeElements") { null } + ) + + fun getProjectLibraries(project: PProject): List { + fun distJar(name: String) = File(project.rootDirectory, "dist/kotlinc/lib/$name.jar") + fun projectFile(path: String) = File(project.rootDirectory, path) + + return listOf( + PLibrary( + "kotlin-stdlib", + classes = listOf(distJar("kotlin-stdlib")), + sources = listOf(distJar("kotlin-stdlib-sources")) + ), + PLibrary( + "kotlin-reflect", + classes = listOf(distJar("kotlin-reflect")), + sources = listOf(distJar("kotlin-reflect-sources")) + ), + PLibrary( + "annotations-13.0", + classes = listOf(distJar("annotations-13.0")) + ), + PLibrary( + "kotlin-test-jvm", + classes = listOf(distJar("kotlin-test")), + sources = listOf(distJar("kotlin-test-sources")) + ), + PLibrary( + "kotlin-test-junit", + classes = listOf(distJar("kotlin-test-junit")), + sources = listOf(distJar("kotlin-test-junit-sources")) + ), + PLibrary( + "kotlin-script-runtime", + classes = listOf(distJar("kotlin-script-runtime")), + sources = listOf(distJar("kotlin-script-runtime-sources")) + ), + PLibrary( + "protobuf-relocated", + classes = listOf(projectFile("custom-dependencies/protobuf-relocated/build/libs/protobuf-java-relocated-2.6.1.jar")) + ) + ) + } + + } + + override fun apply(project: Project) { + project.tasks.create("pill") { + this.doLast { pill(project) } + } + } + + private lateinit var platformVersion: String + private lateinit var platformDir: File + + private fun pill(project: Project) { + platformVersion = project.rootProject.extensions.extraProperties.get("versions.intellijSdk").toString() + platformDir = File(project.projectDir, "buildSrc/prepare-deps/intellij-sdk/build/repo/kotlin.build.custom.deps/$platformVersion") + + val jpsProject = parse(project, ParserContext(dependencyMappers)) + .mapLibraries(attachPlatformSources(platformVersion)) + + val files = render(jpsProject, getProjectLibraries(jpsProject)) + + File(project.rootProject.projectDir, ".idea/libraries").deleteRecursively() + + for (file in files) { + val stubFile = file.path + stubFile.parentFile.mkdirs() + stubFile.writeText(file.text) + } + } + + fun attachPlatformSources(platformVersion: String) = fun(library: PLibrary): PLibrary { + val platformSourcesJar = File(platformDir, "sources/ideaIC-$platformVersion-sources.jar") + + if (library.classes.any { it.startsWith(platformDir) }) { + return library.attachSource(platformSourcesJar) + } + + return library + } + + private fun PProject.mapLibraries(vararg mappers: (PLibrary) -> PLibrary): PProject { + fun mapLibrary(root: POrderRoot): POrderRoot { + val dependency = root.dependency + + if (dependency is PDependency.ModuleLibrary) { + val newLibrary = mappers.fold(dependency.library) { lib, mapper -> mapper(lib) } + return root.copy(dependency = dependency.copy(library = newLibrary)) + } + + return root + } + + val modules = this.modules.map { it.copy(orderRoots = it.orderRoots.map(::mapLibrary)) } + return this.copy(modules = modules) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/pill/render.kt b/buildSrc/src/main/kotlin/pill/render.kt new file mode 100644 index 00000000000..1afb62498bc --- /dev/null +++ b/buildSrc/src/main/kotlin/pill/render.kt @@ -0,0 +1,149 @@ +@file:Suppress("PackageDirectoryMismatch") +package org.jetbrains.kotlin.pill + +import java.io.File + +class PFile(val path: File, val text: String) +fun PFile(path: File, xml: xml) = PFile(path, xml.toString()) + +fun render(project: PProject, extraLibraries: List = emptyList()): List { + val files = mutableListOf() + + files += renderModulesFile(project) + project.modules.forEach { files += renderModule(project, it) } + (project.libraries + extraLibraries).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) + + if (module.group != null) { + xml("module", "fileurl" to "file://$moduleFilePath", "filepath" to moduleFilePath, "group" to module.group) + } else { + 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) + } + + xml("component", "name" to "NewModuleRootManager", "inherit-compiler-output" to "true") { + xml("exclude-output") + + val pathContext = ModuleContext(project, module) + + for (contentRoot in module.contentRoots) { + xml("content", pathContext.url(contentRoot.path)) { + for (sourceRoot in contentRoot.sourceRoots) { + var args = arrayOf(pathContext.url(sourceRoot.path)) + + 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") + + for (orderRoot in module.orderRoots) { + val dependency = orderRoot.dependency + + var args = when (dependency) { + is PDependency.ModuleLibrary -> arrayOf( + "type" to "module-library" + ) + is PDependency.Module -> arrayOf( + "type" to "module", + "module-name" to dependency.name + ) + is PDependency.Library -> arrayOf( + "type" to "library", + "name" to dependency.name, + "level" to "project" + ) + else -> error("Unsupported dependency type: $dependency") + } + + if (dependency is PDependency.Module && orderRoot.isProductionOnTestDependency) { + args += ("production-on-test" to "") + } + + args += ("scope" to orderRoot.scope.toString()) + if (orderRoot.isExported) { + args += ("exported" to "") + } + + xml("orderEntry", *args) { + 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): xml { + 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 \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/pill/xml.kt b/buildSrc/src/main/kotlin/pill/xml.kt new file mode 100644 index 00000000000..42af40c767a --- /dev/null +++ b/buildSrc/src/main/kotlin/pill/xml.kt @@ -0,0 +1,50 @@ +@file:Suppress("PackageDirectoryMismatch") +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 + +class xml(val name: String, vararg val args: Pair, block: xml.() -> Unit = {}) { + private companion object { + fun makeXml(name: String, vararg args: Pair, block: xml.() -> Unit = {}): xml { + return xml(name, *args, block = block) + } + } + + private val children = mutableListOf() + + init { + @Suppress("UNUSED_EXPRESSION") + block() + } + + fun xml(name: String, vararg args: Pair, block: xml.() -> Unit = {}) { + children += makeXml(name, *args, block = block) + } + + fun add(xml: xml) { + children += xml + } + + private fun toElement(): Element { + val element = Element(name) + + for (arg in args) { + element.setAttribute(arg.first, arg.second.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) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/jps-compatible-root.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/jps-compatible-root.properties new file mode 100644 index 00000000000..df550eb444b --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/jps-compatible-root.properties @@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlin.pill.JpsCompatibleRootPlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/jps-compatible.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/jps-compatible.properties new file mode 100644 index 00000000000..eb4dd1f83b2 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/jps-compatible.properties @@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlin.pill.JpsCompatiblePlugin \ No newline at end of file