All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
name.remal.gradle_plugins.plugins.ide.idea.IdeaSettingsExtension.kt Maven / Gradle / Ivy
package name.remal.gradle_plugins.plugins.ide.idea
import name.remal.*
import name.remal.gradle_plugins.dsl.Extension
import name.remal.gradle_plugins.dsl.extensions.atTheEndOfAfterEvaluationAllProjectsOrNow
import name.remal.gradle_plugins.dsl.extensions.getOrNull
import name.remal.gradle_plugins.dsl.extensions.isPluginApplied
import name.remal.gradle_plugins.dsl.utils.XML_PRETTY_OUTPUTTER
import name.remal.gradle_plugins.dsl.utils.getGradleLogger
import name.remal.gradle_plugins.dsl.utils.getPluginIdForLogging
import name.remal.gradle_plugins.plugins.ci.CommonCIPlugin
import org.gradle.api.Action
import org.gradle.api.Project
import org.jdom2.Document
import org.jdom2.Element
import org.jdom2.input.SAXBuilder
import java.io.File
const val WORKSPACE_IDEA_DIR_RELATIVE_PATH = "workspace.xml"
@Extension
class IDEASettingsExtension(private val project: Project) : IDEASettings {
companion object {
private val logger = getGradleLogger(IDEASettingsExtension::class.java)
private fun newSAXBuilder() = SAXBuilder().apply {
setNoValidatingXMLReaderFactory()
setNoOpEntityResolver()
}
}
private val allprojectsDirs: Set by lazy {
project.rootProject.allprojects.mapTo(HashSet(), Project::getProjectDir)
.also { logger.debug("allprojectsDirs = {}", it) }
}
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
private fun hasCorrespondingIDEAModule(configFile: File, projectDir: File): Boolean {
if (!configFile.isFile) {
logger.debug("{} doesn't exist", configFile)
return false
}
try {
logger.debug("Processing {}", configFile)
val projectSubstitutions = globalSubstitutions + mapOf("\$PROJECT_DIR\$" to projectDir.invariantSeparatorsPath)
sequenceOf(newSAXBuilder().build(configFile).rootElement)
.filter { IDEA_PROJECT_VERSION == it.getAttributeValue("version") }
.flatMap { it.getChildren("component").asSequence() }
.filter { it.getAttributeValue("name") == "ProjectModuleManager" }
.flatMap { it.getChildren("modules").asSequence() }
.flatMap { it.getChildren("module").asSequence() }
.mapNotNull { it.getAttributeValue("filepath")?.replace(projectSubstitutions)?.let(project::file) }
.filter(File::isFile)
.forEach forEachModule@{ moduleConfigFile ->
try {
logger.debug("Processing {}", moduleConfigFile)
val moduleSubstitutions = projectSubstitutions + mapOf(
"\$MODULE_IML_DIR\$" to moduleConfigFile.parentFile.invariantSeparatorsPath,
"\$MODULE_DIR\$" to moduleConfigFile.parentFile.invariantSeparatorsPath
)
val moduleProjectDir = newSAXBuilder().build(moduleConfigFile).rootElement.getAttributeValue("external.linked.project.path")?.replace(moduleSubstitutions)?.let(project::file)
?: moduleConfigFile.parentFile.nullIf { name != ".idea" }?.parentFile?.let(project::file)
?: return@forEachModule
logger.debug("{}: moduleProjectDir = {}", moduleConfigFile, moduleProjectDir)
if (moduleProjectDir in allprojectsDirs) {
return true
}
} catch (e: Exception) {
logger.warn(e)
return@forEachModule
}
}
} catch (e: Exception) {
logger.warn(e)
}
return false
}
private val ideaDirs: List by lazy {
buildList {
project.projectDir.forSelfAndEachParent forEachDir@{ projectDir ->
val ideaDir = File(projectDir, ".idea")
if (hasCorrespondingIDEAModule(File(ideaDir, "modules.xml"), projectDir)) {
add(ideaDir)
}
}
}
.also { logger.debug("ideaDirs = {}", it) }
}
private val iprFiles: List by lazy {
buildList {
project.projectDir.forSelfAndEachParent forEachDir@{ projectDir ->
projectDir.listFiles { file -> file.extension == "ipr" }?.forEach { iprFile ->
if (hasCorrespondingIDEAModule(iprFile, projectDir)) {
add(iprFile)
}
}
}
}
.also { logger.debug("iprFiles = {}", it) }
}
private val iwsFiles: List by lazy {
iprFiles.map { it.resolveSibling(it.nameWithoutExtension + ".iws") }
.filter(File::isFile)
.also { logger.debug("iwsFiles = {}", it) }
}
private data class ComponentLocation(
val componentName: String,
val ideaDirRelativePath: String?
)
private val configurers = concurrentMapOf>>()
init {
project.atTheEndOfAfterEvaluationAllProjectsOrNow(Int.MAX_VALUE) atTheEndOfAfterEvaluation@{ project ->
if (project.isPluginApplied(CommonCIPlugin::class.java)) {
if (logger.isDebugEnabled) {
logger.debug("IDEA extended configuration has been skipped, as {} plugin is applied", getPluginIdForLogging(CommonCIPlugin::class.java))
}
return@atTheEndOfAfterEvaluation
}
project.allprojects { proj ->
val ideaSettings = proj.getOrNull(IDEASettings::class.java) as? IdeaSettingsDelegateToRoot
val configurers = ideaSettings?.configurers
configurers?.forEach {
configureIDEAComponent(it.componentName, it.ideaDirRelativePath, it.action)
}
}
val thread = Thread {
logger.debug("Configurers count: {}", configurers.size)
configurers.forEach { (componentName, ideaDirRelativePath), actions ->
val canBeCreated = ideaDirRelativePath != null && !ideaDirRelativePath.endsWith("/")
ideaDirs.forEach forEachIdeaDir@{ ideaDir ->
try {
var isFound = false
logger.debug("Scanning {}", ideaDir)
ideaDir.walk()
.filter { it.extension == "xml" && it.isFile }
.onEach { logger.debug("Processing {}", it) }
.forEach forEachFile@{ file ->
val document = newSAXBuilder().build(file)
val documentStrOrig = document.asString()
sequenceOf(document.rootElement)
.filter(Element::isIdeaProjectElement)
.flatMap { it.getChildren("component").asSequence() }
.plus(
sequenceOf(document.rootElement).filter { it.name == "component" }
)
.filter { it.getAttributeValue("name") == componentName }
.toList().asSequence()
.onEach { isFound = true }
.onEach { logger.debug("Processing component {}: {}", file, componentName) }
.forEach forEachElement@{ componentElement ->
actions.forEach {
it.execute(componentElement)
if (componentElement.parent == null) return@forEachElement
}
}
if (document.asString() != documentStrOrig) {
logger.debug("Writing {}", file)
file.outputStream().use { XML_PRETTY_OUTPUTTER.output(document, it) }
}
}
if (!isFound && canBeCreated && ideaDirRelativePath != null) {
val ideaDirFile = ideaDir.resolve(ideaDirRelativePath.trim('/'))
logger.debug("Creating component {}: {}", ideaDirFile, componentName)
val document: Document = if (ideaDirFile.exists()) {
newSAXBuilder().build(ideaDirFile)
} else {
Document()
}
if (!document.hasRootElement()) {
document.rootElement = Element("project").setAttribute("version", IDEA_PROJECT_VERSION)
} else if (!document.rootElement.isIdeaProjectElement()) {
return@forEachIdeaDir
}
document.rootElement.addContent(
Element("component").setAttribute("name", componentName).also { componentElement ->
actions.forEach { it.execute(componentElement) }
}
)
logger.debug("Writing {}", ideaDirFile)
ideaDirFile.createParentDirectories().outputStream().use { XML_PRETTY_OUTPUTTER.output(document, it) }
}
} catch (e: Exception) {
logger.warn(e)
return@forEachIdeaDir
}
}
(if (ideaDirRelativePath == WORKSPACE_IDEA_DIR_RELATIVE_PATH) iwsFiles else iprFiles).forEach forEachIdeaFile@{ ideaFile ->
try {
logger.debug("Processing {}", ideaFile)
val document = newSAXBuilder().build(ideaFile)
val documentStrOrig = document.asString()
var isFound = false
sequenceOf(document.rootElement)
.filter(Element::isIdeaProjectElement)
.flatMap { it.getChildren("component").asSequence() }
.filter { it.getAttributeValue("name") == componentName }
.toList().asSequence()
.onEach { isFound = true }
.onEach { logger.debug("Processing component {}: {}", ideaFile, componentName) }
.forEach forEachElement@{ componentElement ->
actions.forEach {
it.execute(componentElement)
if (componentElement.parent == null) return@forEachElement
}
}
if (!isFound && canBeCreated) {
logger.debug("Creating component {}: {}", ideaFile, componentName)
document.rootElement.addContent(
Element("component").setAttribute("name", componentName).also { componentElement ->
actions.forEach { it.execute(componentElement) }
}
)
}
if (document.asString() != documentStrOrig) {
logger.debug("Writing {}", ideaFile)
ideaFile.outputStream().use { XML_PRETTY_OUTPUTTER.output(document, it) }
}
} catch (e: Exception) {
logger.warn(e)
return@forEachIdeaFile
}
}
}
}
thread.start()
project.gradle.buildFinished {
if (thread.isAlive) {
thread.join(10_000)
}
}
}
}
override fun configureIDEAComponent(componentName: String, ideaDirRelativePath: String?, action: Action) {
if (ideaDirRelativePath != null) {
logger.debug("Adding configurer for {} (.idea/{})", componentName, ideaDirRelativePath)
} else {
logger.debug("Adding configurer for {}", componentName)
}
val locationConfigurers = configurers.computeIfAbsent(ComponentLocation(componentName, ideaDirRelativePath), { mutableListOf>().asSynchronized() })
locationConfigurers.add(action)
}
}
private val globalSubstitutions = mapOf(
"\$USER_HOME\$" to USER_HOME_DIR.invariantSeparatorsPath,
"\$MAVEN_REPOSITORY\$" to File(USER_HOME_DIR, ".m2/repository").invariantSeparatorsPath
)
private const val IDEA_PROJECT_VERSION: String = "4"
private fun Element.isIdeaProjectElement() = name == "project" && getAttributeValue("version") == IDEA_PROJECT_VERSION