diff --git a/.gitignore b/.gitignore index dcf96bc1012..7922daf78ef 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,5 @@ build/ .idea/compiler.xml .idea/inspectionProfiles/profiles_settings.xml .idea/.name +.idea/artifacts/dist_auto_reference_dont_use.xml kotlin-ultimate/ diff --git a/build.gradle.kts b/build.gradle.kts index bd69ef05448..7c4c7892341 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,7 @@ import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import proguard.gradle.ProGuardTask import org.gradle.kotlin.dsl.* +import org.jetbrains.kotlin.buildUtils.idea.* import org.jetbrains.gradle.ext.ActionDelegationConfig.TestRunner.* buildscript { @@ -853,6 +854,10 @@ if (isJpsBuildEnabled && System.getProperty("idea.active") != null) { rootProject.idea { project { settings { + ideArtifacts { + generateIdeArtifacts(rootProject, this@ideArtifacts) + } + compiler { processHeapSize = 2000 addNotNullAssertions = true diff --git a/buildSrc/src/main/kotlin/idea/DistCopyDetailsMock.kt b/buildSrc/src/main/kotlin/idea/DistCopyDetailsMock.kt new file mode 100755 index 00000000000..1efd0f341ca --- /dev/null +++ b/buildSrc/src/main/kotlin/idea/DistCopyDetailsMock.kt @@ -0,0 +1,136 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package idea + +import groovy.lang.Closure +import org.gradle.api.Action +import org.gradle.api.Transformer +import org.gradle.api.file.ContentFilterable +import org.gradle.api.file.DuplicatesStrategy +import org.gradle.api.file.FileCopyDetails +import org.gradle.api.file.RelativePath +import java.io.File +import java.io.FilterReader +import java.io.InputStream +import java.io.OutputStream + +class DistCopyDetailsMock(val context: DistModelBuildContext, srcName: String) : FileCopyDetails { + private var relativePath = RelativePath(true, srcName) + lateinit var lastAction: Action + + class E : Exception() { + // skip stack trace filling + override fun fillInStackTrace(): Throwable = this + } + + fun logUnsupported(methodName: String): Nothing { + context.logUnsupported("COPY ACTION FileCopyDetails mock: $methodName", lastAction) + throw E() + } + + override fun setDuplicatesStrategy(strategy: DuplicatesStrategy) { + logUnsupported("setDuplicatesStrategy") + } + + override fun getSourcePath(): String { + logUnsupported("getSourcePath") + } + + override fun getName(): String { + logUnsupported("getName") + } + + override fun getSize(): Long { + logUnsupported("getSize") + } + + override fun getRelativePath(): RelativePath = relativePath + + override fun getRelativeSourcePath(): RelativePath { + logUnsupported("getRelativeSourcePath") + } + + override fun expand(properties: MutableMap): ContentFilterable { + logUnsupported("expand") + } + + override fun getMode(): Int { + logUnsupported("getMode") + } + + override fun getSourceName(): String { + logUnsupported("getSourceName") + } + + override fun filter(properties: MutableMap, filterType: Class): ContentFilterable { + logUnsupported("filter") + } + + override fun filter(filterType: Class): ContentFilterable { + logUnsupported("filter") + } + + override fun filter(closure: Closure<*>): ContentFilterable { + logUnsupported("filter") + } + + override fun filter(transformer: Transformer): ContentFilterable { + logUnsupported("filter") + } + + override fun getFile(): File { + logUnsupported("getFile") + } + + override fun setMode(mode: Int) { + logUnsupported("setMode") + } + + override fun copyTo(output: OutputStream) { + logUnsupported("copyTo") + } + + override fun copyTo(target: File): Boolean { + logUnsupported("copyTo") + } + + override fun open(): InputStream { + logUnsupported("open") + } + + override fun setRelativePath(path: RelativePath) { + relativePath = path + } + + override fun getPath(): String { + logUnsupported("getPath") + } + + override fun isDirectory(): Boolean { + logUnsupported("isDirectory") + } + + override fun getDuplicatesStrategy(): DuplicatesStrategy { + logUnsupported("getDuplicatesStrategy") + } + + override fun setName(name: String) { + logUnsupported("setName") + } + + override fun getLastModified(): Long { + logUnsupported("getLastModified") + } + + override fun setPath(path: String) { + logUnsupported("setPath") + } + + override fun exclude() { + logUnsupported("exclude") + } + +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/idea/DistModel.kt b/buildSrc/src/main/kotlin/idea/DistModel.kt new file mode 100755 index 00000000000..192ed302dac --- /dev/null +++ b/buildSrc/src/main/kotlin/idea/DistModel.kt @@ -0,0 +1,96 @@ +package org.jetbrains.kotlin.buildUtils.idea + +import org.gradle.api.Action +import org.gradle.api.file.FileCopyDetails +import java.io.File +import java.io.PrintWriter + + +class DistVFile( + val parent: DistVFile?, + val name: String, + val file: File = File(parent!!.file, name) +) { + val child = mutableMapOf() + + val contents = mutableSetOf() + + override fun toString(): String = name + + val hasContents: Boolean = file.exists() || contents.isNotEmpty() + + fun relativePath(path: String): DistVFile { + val pathComponents = path.split(File.separatorChar) + return pathComponents.fold(this) { parent: DistVFile, childName: String -> + try { + parent.getOrCreateChild(childName) + } catch (t: Throwable) { + throw Exception("Error while processing path `$path`, components: `$pathComponents`, element: `$childName`", t) + } + } + } + + fun getOrCreateChild(name: String): DistVFile = child.getOrPut(name) { + DistVFile(this, name) + } + + fun addContents(contents: DistContentElement) { + this.contents.add(contents) + } + + fun removeAll(matcher: (String) -> Boolean) { + child.keys.filter(matcher).forEach { + child.remove(it) + } + } + + fun printTree(p: PrintWriter, depth: String = "") { + p.println("$depth${file.path} ${if (file.exists()) "EXISTED" else ""}:") + contents.forEach { + p.println("$depth $it") + } + child.values.forEach { + it.printTree(p, "$depth ") + } + } +} + +sealed class DistContentElement() + +class DistCopy( + target: DistVFile, + val src: DistVFile, + val customTargetName: String? = null, + val copyActions: Collection> = listOf() +) : DistContentElement() { + init { + target.addContents(this) + } + + override fun toString(): String = + "COPY OF ${src.file}" + + if (customTargetName != null) " -> $customTargetName" else "" +} + +class DistModuleOutput(parent: DistVFile, val projectId: String) : DistContentElement() { + init { + parent.addContents(this) + } + + override fun toString(): String = "COMPILE OUTPUT $projectId" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DistModuleOutput + + if (projectId != other.projectId) return false + + return true + } + + override fun hashCode(): Int { + return projectId.hashCode() + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/idea/DistModelBuildContext.kt b/buildSrc/src/main/kotlin/idea/DistModelBuildContext.kt new file mode 100755 index 00000000000..8acf7bf4f2d --- /dev/null +++ b/buildSrc/src/main/kotlin/idea/DistModelBuildContext.kt @@ -0,0 +1,76 @@ +package idea + +import org.gradle.api.Action +import org.gradle.api.file.FileCopyDetails +import org.jetbrains.kotlin.buildUtils.idea.DistVFile +import org.jetbrains.kotlin.buildUtils.idea.logger + +/** + * Used for logging and nesting properties + */ +class DistModelBuildContext( + val parent: DistModelBuildContext?, + val kind: String, + val title: String, + val report: Appendable? = parent?.report, + val shade: Boolean = parent?.shade ?: false +) { + val logEnabled = false + val allCopyActions = mutableSetOf>() + + var destination: DistVFile? = parent?.destination // todo: don't nest destination between tasks visiting + val logPrefix: String = if (parent != null) "${parent.logPrefix}-" else "" + + init { + report?.appendln(toString()) + if (parent != null) { + allCopyActions.addAll(parent.allCopyActions) + } + } + + fun log(kind: String, title: String = "", print: Boolean = false) { + if (logEnabled) { + report?.appendln("$logPrefix- $kind $title") + if (print) { + logger.error("$kind $title, while visiting:") + var p = this + while (p.parent != null) { + logger.error(" - ${p.kind} ${p.title}") + p = p.parent!! + } + } + } + } + + fun logUnsupported(kind: String, obj: Any? = null) { + val objInfo = if (obj != null) { + val javaClass = obj.javaClass + val superclass = javaClass.superclass as Class<*> + "$obj [$javaClass extends $superclass implements ${javaClass.interfaces.map { it.canonicalName }}]" + } else "" + + log("UNSUPPORTED $kind", objInfo, true) + } + + override fun toString() = "$logPrefix $kind $title" + + inline fun child( + kind: String, + title: String = "", + shade: Boolean = false, + body: (DistModelBuildContext) -> Unit = {} + ): DistModelBuildContext { + val result = DistModelBuildContext(this, kind, title, shade = shade) + body(result) + return result + } + + fun addCopyActions(allCopyActions: Collection>) { + allCopyActions.forEach { + log("COPY ACTION", "$it") + } + + this.allCopyActions.addAll(allCopyActions) + } +} + diff --git a/buildSrc/src/main/kotlin/idea/DistModelBuilder.kt b/buildSrc/src/main/kotlin/idea/DistModelBuilder.kt new file mode 100755 index 00000000000..32dd982683b --- /dev/null +++ b/buildSrc/src/main/kotlin/idea/DistModelBuilder.kt @@ -0,0 +1,314 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.buildUtils.idea + +import IntelliJInstrumentCodeTask +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import idea.DistCopyDetailsMock +import idea.DistModelBuildContext +import org.codehaus.groovy.runtime.GStringImpl +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.FileCollection +import org.gradle.api.file.FileVisitDetails +import org.gradle.api.file.FileVisitor +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.internal.file.* +import org.gradle.api.internal.file.archive.ZipFileTree +import org.gradle.api.internal.file.collections.* +import org.gradle.api.internal.file.copy.CopySpecInternal +import org.gradle.api.internal.file.copy.DefaultCopySpec +import org.gradle.api.internal.file.copy.DestinationRootCopySpec +import org.gradle.api.internal.file.copy.SingleParentCopySpec +import org.gradle.api.tasks.AbstractCopyTask +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.SourceSetOutput +import org.gradle.api.tasks.Sync +import org.gradle.api.tasks.bundling.AbstractArchiveTask +import org.gradle.api.tasks.compile.AbstractCompile +import org.gradle.internal.file.PathToFileResolver +import org.gradle.jvm.tasks.Jar +import java.io.File +import java.io.PrintWriter +import java.util.concurrent.Callable + +open class DistModelBuilder(val rootProject: Project, pw: PrintWriter) { + val rootCtx = DistModelBuildContext(null, "ROOT", "dist", pw) + val visited = mutableMapOf() + val vfsRoot = DistVFile(null, "", File("")) + val refs = mutableSetOf() + + fun visitInstrumentTask(it: IntelliJInstrumentCodeTask): DistModelBuildContext = visited.getOrPut(it) { + val ctx = rootCtx.child("INSTRUMENT", it.path) + ctx.setDest(it.output!!.path) + processSourcePath(it.originalClassesDirs, ctx) + val dest = ctx.destination + if (dest != null) { + DistModuleOutput(dest, it.project.path) + } + + ctx + } + + fun visitCompileTask(it: AbstractCompile): DistModelBuildContext = visited.getOrPut(it) { + val ctx = rootCtx.child("COMPILE", it.path) + ctx.setDest(it.destinationDir.path) + val dest = ctx.destination + if (dest != null) DistModuleOutput(dest, it.project.path) + else ctx.logUnsupported("Cannot add contents: destination is unknown", it) + + ctx + } + + fun visitCopyTask( + copy: AbstractCopyTask, + shade: Boolean = false + ): DistModelBuildContext = visited.getOrPut(copy) { + val context = rootCtx.child("COPY", copy.path, shade) + + + val rootSpec = copy.rootSpec + + when (copy) { + is Copy -> context.setDest(copy.destinationDir.path) + is Sync -> context.setDest(copy.destinationDir.path) + is AbstractArchiveTask -> context.setDest(copy.archivePath.path) + } + + when (copy) { + is ShadowJar -> copy.configurations.forEach { + processSourcePath(it, context) + } + } + + processCopySpec(rootSpec, context) + + + context + } + + fun processCopySpec(spec: CopySpecInternal, ctx: DistModelBuildContext) { + spec.children.forEach { + when (it) { + is DestinationRootCopySpec -> ctx.child("DESTINATION ROOT COPY SPEC") { newCtx -> + newCtx.setDest(getRelativePath(it.destinationDir.path)) + processCopySpec(it, newCtx) + } + is DefaultCopySpec -> ctx.child("DEFAULT COPY SPEC") { newCtx -> + val buildRootResolver = it.buildRootResolver() + ctx.addCopyActions(buildRootResolver.allCopyActions) + newCtx.setDest(buildRootResolver.destPath.getFile(ctx.destination!!.file).path) + processCopySpec(it, newCtx) + it.includes + + newCtx.child("SINGE PARENT COPY SPEC") { child -> + it.sourcePaths.forEach { + processSourcePath(it, child) + } + } + } + is SingleParentCopySpec -> ctx.child("OTHER SINGE PARENT COPY SPEC") { child -> + it.sourcePaths.forEach { + processSourcePath(it, child) + } + } + is CopySpecInternal -> processCopySpec(it, ctx) + else -> ctx.logUnsupported("CopySpec", spec) + } + } + } + + fun processSourcePath(sourcePath: Any?, ctx: DistModelBuildContext) { + when { + sourcePath == null -> Unit + sourcePath is Jar -> ctx.child("JAR") { child -> + child.addCopyOf(sourcePath.archivePath.path) + } + sourcePath is SourceSetOutput -> ctx.child("COMPILE") { child -> + sourcePath.classesDirs.files.forEach { + child.addCopyOf(it.path) + } + } + sourcePath is Configuration -> { + ctx.child("CONFIGURATION") { child -> + sourcePath.resolve().forEach { + child.addCopyOf(it.path) + } + } + } + sourcePath is SourceDirectorySet -> { + ctx.child("SOURCES") { child -> + sourcePath.srcDirs.forEach { + child.addCopyOf(it.path) + } + } + } + sourcePath is MinimalFileSet -> ctx.child("MINIMAL FILE SET (${sourcePath.javaClass.simpleName})") { child -> + sourcePath.files.forEach { + processSourcePath(it, child) + } + } + sourcePath is MinimalFileTree -> ctx.child("MINIMAL FILE TREE (${sourcePath.javaClass.simpleName})") { child -> + sourcePath.visit(object : FileVisitor { + override fun visitDir(dirDetails: FileVisitDetails) { + processSourcePath(dirDetails.file, child) + } + + override fun visitFile(fileDetails: FileVisitDetails) { + processSourcePath(fileDetails.file, child) + } + }) + } + sourcePath is FileTreeAdapter && sourcePath.tree is MapFileTree -> ctx.child("FILE TREE ADAPTER OF MAP FILE TREE (${sourcePath.javaClass.simpleName})") { child -> + sourcePath.visitContents(object : FileCollectionResolveContext { + override fun add(element: Any): FileCollectionResolveContext { + processSourcePath(element, child) + return this + } + + override fun newContext(): ResolvableFileCollectionResolveContext { + error("not supported") + } + + override fun push(fileResolver: PathToFileResolver): FileCollectionResolveContext { + return this + } + }) + } + sourcePath is CompositeFileCollection -> ctx.child("COMPOSITE FILE COLLECTION") { child -> + sourcePath.visitRootElements(object : FileCollectionVisitor { + override fun visitDirectoryTree(directoryTree: DirectoryFileTree) { + child.child("DIR TREE") { + it.addCopyOf(directoryTree.dir.path) + } + } + + override fun visitTree(fileTree: FileTreeInternal) { + child.child("TREE") { + processSourcePath(fileTree, it) + } + } + + override fun visitCollection(fileCollection: FileCollectionInternal) { + processSourcePath(fileCollection, child) + } + }) + } + sourcePath is FileTreeAdapter && sourcePath.tree is ZipFileTree -> ctx.child("ZIP FILE TREE ADAPTER") { child -> + val tree = sourcePath.tree + val field = tree.javaClass.declaredFields.find { it.name == "zipFile" }!! + field.isAccessible = true + val zipFile = field.get(tree) as File + + child.addCopyOf(zipFile.path) + } + sourcePath is FileTreeInternal -> ctx.child("FILE TREE INTERNAL") { child -> + // todo: preserve or warn about filtering + sourcePath.visitTreeOrBackingFile(object : FileVisitor { + override fun visitFile(fileDetails: FileVisitDetails) { + child.addCopyOf(fileDetails.file.path) + } + + override fun visitDir(dirDetails: FileVisitDetails) { + child.addCopyOf(dirDetails.file.path) + } + }) + } + sourcePath is FileCollection -> ctx.child("OTHER FILE COLLECTION (${sourcePath.javaClass})") { child -> + try { + sourcePath.files.forEach { + child.addCopyOf(it.path) + } + } catch (t: Throwable) { + child.logUnsupported("FILE COLLECTION (${t.message})", sourcePath) + } + } + sourcePath is String || sourcePath is GStringImpl -> ctx.child("STRING") { child -> + child.addCopyOf(sourcePath.toString()) + } + sourcePath is Callable<*> -> ctx.child("CALLABLE") { child -> + processSourcePath(sourcePath.call(), child) + } + sourcePath is Collection<*> -> ctx.child("COLLECTION") { child -> + sourcePath.forEach { + processSourcePath(it, child) + } + } + sourcePath is Copy -> ctx.child("COPY OUTPUT") { child -> + val src = visitCopyTask(sourcePath).destination + if (src != null) child.addCopyOf(src) + // else it is added to `it`, because destination is inhereted by context + } + sourcePath is File -> ctx.child("FILE ${sourcePath.path}") { child -> + child.addCopyOf(sourcePath.path) + } + else -> ctx.logUnsupported("SOURCE PATH", sourcePath) + } + } + + inline fun DistModelBuildContext.addCopyOf( + src: String, + body: (src: DistVFile, target: DistVFile) -> Unit = { _, _ -> Unit } + ) { + addCopyOf(requirePath(src), body) + } + + fun DistModelBuildContext.transformName(srcName: String): String? { + val detailsMock = DistCopyDetailsMock(this, srcName) + allCopyActions.forEach { + detailsMock.lastAction = it + try { + it.execute(detailsMock) + } catch (t: DistCopyDetailsMock.E) { + // skip + } + } + val name1 = detailsMock.relativePath.lastName + return if (name1.endsWith(".jar")) transformJarName(name1) else name1 + } + + // todo: investigate why allCopyActions not working + open fun transformJarName(name: String): String = name + + inline fun DistModelBuildContext.addCopyOf( + src: DistVFile, + body: (src: DistVFile, target: DistVFile) -> Unit = { _, _ -> Unit } + ) { + + val destination = destination + if (destination != null) { + body(src, destination) + val customTargetName = transformName(src.name) + DistCopy(destination, src, customTargetName) + log("+DistCopy", "${getRelativePath(src.file.path)} -> ${getRelativePath(destination.file.path)}/$customTargetName") + } else logUnsupported("Cannot add copy of `$src`: destination is unknown") + } + + fun DistModelBuildContext.setDest(path: String) { + destination = vfsRoot.relativePath(path) + log("INTO", getRelativePath(path)) + } + + fun checkRefs() { + refs.forEach { + if (!it.hasContents && it.contents.isEmpty() && it.file.path.contains("${File.pathSeparator}build${File.pathSeparator}")) { + logger.error("UNRESOLVED ${it.file}") + it.contents.forEach { + logger.error("+ ${it}") + } + } + } + } + + fun getRelativePath(path: String) = path.replace(rootProject.projectDir.path, "$") + + fun requirePath(targetPath: String): DistVFile { + val target = vfsRoot.relativePath(targetPath) + if (!File(targetPath).exists()) refs.add(target) + return target + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/idea/DistModelFlattener.kt b/buildSrc/src/main/kotlin/idea/DistModelFlattener.kt new file mode 100755 index 00000000000..07f5c271101 --- /dev/null +++ b/buildSrc/src/main/kotlin/idea/DistModelFlattener.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.buildUtils.idea + +class DistModelFlattener() { + val stack = mutableSetOf() + val common = mutableSetOf() + + fun DistVFile.flatten(): DistVFile { + val new = DistVFile(parent, name, file) + copyFlattenedContentsTo(new) + return new + } + + private fun DistVFile.copyFlattenedContentsTo(new: DistVFile, inJar: Boolean = false) { + if (!stack.add(this)) { + return + } + + try { + contents.forEach { + if (!shouldSkip(new, it)) { + when (it) { + is DistCopy -> { + val srcName = it.customTargetName ?: it.src.name + if (it.src.file.exists()) { + DistCopy(new, it.src, srcName) + } + + if (!inJar && srcName.endsWith(".jar")) { + val newChild = new.getOrCreateChild(srcName) + it.src.copyFlattenedContentsTo(newChild, inJar = true) + } else { + it.src.copyFlattenedContentsTo(new, inJar) + } + } + is DistModuleOutput -> DistModuleOutput(new, it.projectId) + } + } + } + + child.values.forEach { oldChild -> + if (inJar) { + val newChild = + if (oldChild.name.endsWith(".jar")) new + else new.getOrCreateChild(oldChild.name) + oldChild.copyFlattenedContentsTo(newChild, inJar = true) + } else { + val newChild = new.getOrCreateChild(oldChild.name) + oldChild.copyFlattenedContentsTo(newChild) + } + } + } finally { + stack.remove(this) + } + } + + private fun shouldSkip( + new: DistVFile, + content: DistContentElement + ) = + new.name == "kotlin-jps-plugin.jar" && content is DistCopy && content.customTargetName == "kotlin-compiler-runner.jar" +} + diff --git a/buildSrc/src/main/kotlin/idea/DistModelIdeaArtifactBuilder.kt b/buildSrc/src/main/kotlin/idea/DistModelIdeaArtifactBuilder.kt new file mode 100755 index 00000000000..76df5b75ac4 --- /dev/null +++ b/buildSrc/src/main/kotlin/idea/DistModelIdeaArtifactBuilder.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.buildUtils.idea + +import org.gradle.api.Project +import org.gradle.plugins.ide.idea.model.IdeaModel +import org.jetbrains.gradle.ext.ArtifactType +import org.jetbrains.gradle.ext.RecursiveArtifact + +class DistModelIdeaArtifactBuilder(val rootProject: Project) { + fun RecursiveArtifact.addFiles(vFile: DistVFile, inJar: Boolean = false) { + val files = mutableSetOf() + + vFile.contents.forEach { + when (it) { + is DistCopy -> { + val file = it.src.file + when { + inJar && file.name.endsWith(".jar") -> extractedDirectory(file.path) + file.isDirectory -> { + files.add(file.name) + directoryContent(file.path) + } + else -> { + files.add(file.name) + file(file.path) + } + } + } + is DistModuleOutput -> { + val name = it.ideaModuleName + + if (name.result != null) moduleOutput(name.result + "_main") + else logger.warn("Cannot find idea module name for project `${it.projectId}`: ${name.error}") + } + } + } + + vFile.child.values.forEach { + if (it.name !in files) { + when { + it.name.endsWith(".jar") -> archive(it.name).addFiles(it, true) + else -> directory(it.name).addFiles(it, inJar) + } + } + } + } + + class Result(val result: T? = null, val error: String? = null) + + val DistModuleOutput.ideaModuleName: Result + get() { + val findProject = rootProject.findProject(projectId) ?: return Result(error = "cannot find gradle project $projectId") + val idea = findProject.extensions?.findByName("idea") as? IdeaModel ?: return Result(error = "cannot find idea model for gradle project $projectId") + val name = idea.module?.name ?: return Result(error = "idea model for project $projectId has no module name") + return Result(name) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/idea/generateIdeArtifacts.kt b/buildSrc/src/main/kotlin/idea/generateIdeArtifacts.kt new file mode 100755 index 00000000000..b3c1979098c --- /dev/null +++ b/buildSrc/src/main/kotlin/idea/generateIdeArtifacts.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.buildUtils.idea + +import IntelliJInstrumentCodeTask +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.tasks.AbstractCopyTask +import org.gradle.api.tasks.compile.AbstractCompile +import org.jetbrains.gradle.ext.TopLevelArtifact +import org.slf4j.LoggerFactory +import java.io.File + +/** + * Temporary solution for configuring IDEA artifacts based on Gradle copy tasks configurations. + * This should be replaced with DSL that produces both Gradle copy tasks and IDEA artifacts configuration. + * + * TODO: remove this package when DSL described above will be implemented + */ +fun generateIdeArtifacts(rootProject: Project, artifactsFactory: NamedDomainObjectContainer) { + val reportsDir = File(path(rootProject.buildDir.path, "reports", "idea-artifacts-cfg")) + reportsDir.mkdirs() + val projectDir = rootProject.projectDir + + File(reportsDir, "01-visitor.report.txt").printWriter().use { visitorReport -> + val modelBuilder = object : DistModelBuilder(rootProject, visitorReport) { + // todo: investigate why allCopyActions not working + override fun transformJarName(name: String): String { + val name1 = name.replace(Regex("-${java.util.regex.Pattern.quote(rootProject.version.toString())}"), "") + + val name2 = when (name1) { + "kotlin-runtime-common.jar" -> "kotlin-runtime.jar" + "kotlin-compiler-before-proguard.jar" -> "kotlin-compiler.jar" + "kotlin-main-kts-before-proguard.jar" -> "kotlin-main-kts.jar" + "kotlin-allopen-compiler-plugin.jar" -> "allopen-compiler-plugin.jar" + "kotlin-noarg-compiler-plugin.jar" -> "noarg-compiler-plugin.jar" + "kotlin-sam-with-receiver-compiler-plugin.jar" -> "sam-with-receiver-compiler-plugin.jar" + "kotlin-android-extensions-runtime.jar" -> "android-extensions-runtime.jar" + else -> name1 + } + + val name3 = name2.removePrefix("dist-") + + return name3 + } + } + + fun visitAllTasks(project: Project) { + project.tasks.forEach { + try { + when { + it is AbstractCopyTask -> modelBuilder.visitCopyTask(it) + it is AbstractCompile -> modelBuilder.visitCompileTask(it) + it is IntelliJInstrumentCodeTask -> modelBuilder.visitInstrumentTask(it) + it.name == "stripMetadata" -> { + modelBuilder.rootCtx.log( + "STRIP METADATA", + "${it.inputs.files.singleFile} -> ${it.outputs.files.singleFile}" + ) + + DistCopy( + modelBuilder.requirePath(it.outputs.files.singleFile.path), + modelBuilder.requirePath(it.inputs.files.singleFile.path) + ) + } + } + } catch (t: Throwable) { + logger.error("Error while visiting `$it`", t) + } + } + + project.subprojects.forEach { + visitAllTasks(it) + } + } + + visitAllTasks(rootProject) + + // proguard + DistCopy( + target = modelBuilder.requirePath( + path( + projectDir.path, + "libraries", + "reflect", + "build", + "libs", + "kotlin-reflect-proguard.jar" + ) + ), + src = modelBuilder.requirePath(path(projectDir.path, "libraries", "reflect", "build", "libs", "kotlin-reflect-shadow.jar")) + ) + + File(reportsDir, "02-vfs.txt").printWriter().use { + modelBuilder.vfsRoot.printTree(it) + } + modelBuilder.checkRefs() + + with(DistModelFlattener()) { + with(DistModelIdeaArtifactBuilder(rootProject)) { + File(reportsDir, "03-flattened-vfs.txt").printWriter().use { report -> + fun getFlattenned(vfsPath: String): DistVFile = + modelBuilder.vfsRoot.relativePath(path(projectDir.path, vfsPath)) + .flatten() + + val all = getFlattenned("dist") + all.child["artifacts"] + ?.removeAll { it != "ideaPlugin" } + all.child["artifacts"] + ?.child?.get("ideaPlugin") + ?.child?.get("Kotlin") + ?.removeAll { it != "kotlinc" && it != "lib" } + all.removeAll { it.endsWith(".zip") } + all.printTree(report) + + val dist = artifactsFactory.create("dist_auto_reference_dont_use") + dist.addFiles(all) + } + } + } + } +} + +private fun path(vararg components: String) = components.joinToString(File.separator) + +internal val logger = LoggerFactory.getLogger("ide-artifacts") + diff --git a/buildSrc/src/main/kotlin/ideaExtKotlinDsl.kt b/buildSrc/src/main/kotlin/ideaExtKotlinDsl.kt index c5f0909ab4c..185d8a758cb 100644 --- a/buildSrc/src/main/kotlin/ideaExtKotlinDsl.kt +++ b/buildSrc/src/main/kotlin/ideaExtKotlinDsl.kt @@ -1,3 +1,4 @@ +import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.plugins.ExtensionAware import org.gradle.kotlin.dsl.configure import org.gradle.plugins.ide.idea.model.IdeaProject @@ -28,3 +29,6 @@ fun DefaultRunConfigurationContainer.junit(name: String, block: JUnit.() -> Unit fun DefaultRunConfigurationContainer.application(name: String, block: Application.() -> Unit) = create(name, Application::class.java, block) + +fun ProjectSettings.ideArtifacts(block: NamedDomainObjectContainer.() -> Unit) = + (this@ideArtifacts as ExtensionAware).extensions.configure("ideArtifacts", block)