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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<PDependency> = emptyList())
|
||||
|
||||
class ParserContext(val dependencyMappers: List<DependencyMapper>)
|
||||
@@ -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<PModule>,
|
||||
val libraries: List<PLibrary>
|
||||
)
|
||||
|
||||
data class PModule(
|
||||
val name: String,
|
||||
val bundleName: String,
|
||||
val rootDirectory: File,
|
||||
val contentRoots: List<PContentRoot>,
|
||||
val orderRoots: List<POrderRoot>,
|
||||
val moduleForProductionSources: PModule? = null,
|
||||
val group: String? = null
|
||||
)
|
||||
|
||||
data class PContentRoot(
|
||||
val path: File,
|
||||
val forTests: Boolean,
|
||||
val sourceRoots: List<PSourceRoot>,
|
||||
val excludedDirectories: List<File>
|
||||
)
|
||||
|
||||
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<File>,
|
||||
val javadoc: List<File> = emptyList(),
|
||||
val sources: List<File> = emptyList(),
|
||||
val annotations: List<File> = emptyList(),
|
||||
val dependencies: List<PLibrary> = 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<PModule> {
|
||||
val (productionContentRoots, testContentRoots) = parseContentRoots(project).partition { !it.forTests }
|
||||
|
||||
val modules = mutableListOf<PModule>()
|
||||
|
||||
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<PContentRoot> {
|
||||
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<PSourceRoot>, forTests: Boolean): List<PContentRoot> {
|
||||
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<PSourceRoot> {
|
||||
if (!project.plugins.hasPlugin(JavaPlugin::class.java)) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val sourceRoots = mutableListOf<PSourceRoot>()
|
||||
|
||||
project.configure<JavaPluginConvention> {
|
||||
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<POrderRoot> {
|
||||
val configurationMapping = if (forTests) TEST_CONFIGURATION_MAPPING else CONFIGURATION_MAPPING
|
||||
|
||||
with(project.configurations) {
|
||||
val moduleRoots = mutableListOf<POrderRoot>()
|
||||
val libraryRoots = mutableListOf<POrderRoot>()
|
||||
val deferredRoots = mutableListOf<POrderRoot>()
|
||||
|
||||
fun collectConfigurations(): List<Pair<ResolvedConfiguration, Scope>> {
|
||||
val configurations = mutableListOf<Pair<ResolvedConfiguration, Scope>>()
|
||||
|
||||
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<POrderRoot>): List<POrderRoot> {
|
||||
val dependenciesByScope = roots.groupBy { it.scope }.mapValues { it.value.mapTo(mutableSetOf()) { it.dependency } }
|
||||
fun depenciesFor(scope: Scope) = dependenciesByScope[scope] ?: emptySet<PDependency>()
|
||||
|
||||
val result = mutableSetOf<POrderRoot>()
|
||||
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<Pair<ResolvedConfiguration, Scope>>.collectDependencies(): List<DependencyInfo> {
|
||||
val dependencies = mutableListOf<DependencyInfo>()
|
||||
|
||||
val unprocessed = LinkedList<DependencyInfo>()
|
||||
val existing = mutableSetOf<Pair<Scope, ResolvedDependency>>()
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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<String, String> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<Project> {
|
||||
override fun apply(project: Project) {}
|
||||
}
|
||||
|
||||
class JpsCompatibleRootPlugin : Plugin<Project> {
|
||||
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<PLibrary> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<PLibrary> = emptyList()): List<PFile> {
|
||||
val files = mutableListOf<PFile>()
|
||||
|
||||
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
|
||||
@@ -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<String, Any>, block: xml.() -> Unit = {}) {
|
||||
private companion object {
|
||||
fun makeXml(name: String, vararg args: Pair<String, Any>, block: xml.() -> Unit = {}): xml {
|
||||
return xml(name, *args, block = block)
|
||||
}
|
||||
}
|
||||
|
||||
private val children = mutableListOf<xml>()
|
||||
|
||||
init {
|
||||
@Suppress("UNUSED_EXPRESSION")
|
||||
block()
|
||||
}
|
||||
|
||||
fun xml(name: String, vararg args: Pair<String, Any>, 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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
implementation-class=org.jetbrains.kotlin.pill.JpsCompatibleRootPlugin
|
||||
@@ -0,0 +1 @@
|
||||
implementation-class=org.jetbrains.kotlin.pill.JpsCompatiblePlugin
|
||||
Reference in New Issue
Block a user