[dev launcher] provide a way to locate resource files in module sources in dev launcher (RDCT-1405)

To determine which modules should be compiled in IntellijDevLauncher, we need to load product-modules.xml and plugin.xml files. Currently, they are loaded via RuntimeModuleRepository from output directories, so obsolete variant may be loaded or file may not be found at all if modules containing these files aren't compiled yet. This change introduces the ModuleResourceFileFinder class, which can locate resource files in source directories instead. It doesn't use existing functionality for that to avoid adding additional modules to the system classloader. Also, it parses necessary *.iml files only to speed up the process.

GitOrigin-RevId: d55083ba879a3ae8c7985ba6e5f0211ad3062959
This commit is contained in:
Nikolay Chashnikov
2024-05-30 02:53:28 +00:00
committed by intellij-monorepo-bot
parent 019cdc28dd
commit 2192a0d35b
15 changed files with 207 additions and 0 deletions
+1
View File
@@ -942,6 +942,7 @@
<module fileurl="file://$PROJECT_DIR$/tools/apiDump/intellij.tools.apiDump.iml" filepath="$PROJECT_DIR$/tools/apiDump/intellij.tools.apiDump.iml" />
<module fileurl="file://$PROJECT_DIR$/tools/apiDump/testData/intellij.tools.apiDump.testData.iml" filepath="$PROJECT_DIR$/tools/apiDump/testData/intellij.tools.apiDump.testData.iml" />
<module fileurl="file://$PROJECT_DIR$/tools/devLauncher/intellij.tools.devLauncher.iml" filepath="$PROJECT_DIR$/tools/devLauncher/intellij.tools.devLauncher.iml" />
<module fileurl="file://$PROJECT_DIR$/tools/devLauncher/tests/intellij.tools.devLauncher.tests.iml" filepath="$PROJECT_DIR$/tools/devLauncher/tests/intellij.tools.devLauncher.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/tools/intellij.tools.ide.metrics.benchmark/intellij.tools.ide.metrics.benchmark.iml" filepath="$PROJECT_DIR$/tools/intellij.tools.ide.metrics.benchmark/intellij.tools.ide.metrics.benchmark.iml" />
<module fileurl="file://$PROJECT_DIR$/tools/intellij.tools.ide.metrics.collector/intellij.tools.ide.metrics.collector.iml" filepath="$PROJECT_DIR$/tools/intellij.tools.ide.metrics.collector/intellij.tools.ide.metrics.collector.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/performanceTesting/commands-model/intellij.tools.ide.performanceTesting.commands.iml" filepath="$PROJECT_DIR$/plugins/performanceTesting/commands-model/intellij.tools.ide.performanceTesting.commands.iml" />
+1
View File
@@ -208,6 +208,7 @@
<orderEntry type="module" module-name="intellij.platform.uast.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.runtime.repository.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.runtime.product.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.tools.devLauncher.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.html.tools.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.lombok" scope="TEST" />
<orderEntry type="module" module-name="intellij.xml.xmlbeans" scope="TEST" />
@@ -0,0 +1,102 @@
package com.intellij.tools.devLauncher
import java.nio.file.Path
import javax.xml.stream.XMLInputFactory
import javax.xml.stream.XMLStreamConstants
import kotlin.io.path.*
/**
* Provides a way to locate a resource file under source roots by its relative path in an IntelliJ project.
*/
class ModuleResourceFileFinder(private val projectDir: Path) {
private val moduleFiles: Map<String, Path>
init {
val modulesXmlFile = projectDir.resolve(".idea/modules.xml")
require(modulesXmlFile.exists()) { ".idea/modules.xml not found in $projectDir"}
modulesXmlFile.inputStream().buffered().use { input ->
val moduleFilesMap = LinkedHashMap<String, Path>()
val reader = XMLInputFactory.newDefaultFactory().createXMLStreamReader(input)
while (reader.hasNext()) {
val event = reader.next()
if (event == XMLStreamConstants.START_ELEMENT && reader.localName == "module") {
val attributeName = reader.getAttributeLocalName(0)
require(attributeName == "fileurl") { "Unexpected first attribute in 'module' tag: $attributeName"}
val imlUrl = reader.getAttributeValue(0)
val prefix = "file://${'$'}PROJECT_DIR${'$'}/"
require(imlUrl.startsWith(prefix)) { "Unexpected format of URL: $imlUrl"}
val imlPath = projectDir.resolve(imlUrl.removePrefix(prefix))
val fileName = imlPath.name
val suffix = ".iml"
require(fileName.endsWith(suffix)) { "Unexpected file extension in file path $imlPath" }
val moduleName = fileName.removeSuffix(suffix)
moduleFilesMap[moduleName] = imlPath
}
}
moduleFiles = moduleFilesMap
}
}
fun findResourceFile(moduleName: String, relativePath: String): Path? {
for ((prefix, rootPath) in loadRootsWithPrefixes(moduleName)) {
val relativePathWithoutPrefix = when {
prefix == null -> relativePath
relativePath.startsWith("$prefix/") -> relativePath.removePrefix("$prefix/")
else -> continue
}
val file = rootPath.resolve(relativePathWithoutPrefix)
if (file.exists()) {
return file
}
}
return null
}
private fun loadRootsWithPrefixes(moduleName: String): List<Pair<String?, Path>> {
val imlPath = moduleFiles[moduleName] ?: error("Cannot find module '$moduleName' in project '$projectDir'")
require(imlPath.exists()) { "Module file $imlPath doesn't exist" }
val moduleDir = imlPath.parent
val rootsWithPrefixes = ArrayList<Pair<String?, Path>>()
imlPath.inputStream().buffered().use { input ->
val reader = XMLInputFactory.newDefaultFactory().createXMLStreamReader(input)
while (reader.hasNext()) {
val event = reader.next()
if (event == XMLStreamConstants.START_ELEMENT && reader.localName == "sourceFolder") {
require(reader.attributeCount > 1) { "At least two attributes expected in 'sourceFolder' tag in $imlPath, but ${reader.attributeCount} found" }
val attributeName = reader.getAttributeLocalName(0)
require(attributeName == "url") { "Unexpected first attribute in 'sourceFolder' tag in $imlPath: $attributeName" }
val rootUrl = reader.getAttributeValue(0)
val prefix = "file://"
require(rootUrl.startsWith(prefix)) { "Unexpected format of URL: $rootUrl" }
val rootPath = Path(rootUrl.removePrefix(prefix).replace("${'$'}MODULE_DIR${'$'}", moduleDir.pathString))
val typeAttributeName = reader.getAttributeLocalName(1)
val typeAttributeValue = reader.getAttributeValue(1)
val directoryPrefix = when {
typeAttributeName == "isTestSource" && typeAttributeValue == "false" -> {
if (reader.attributeCount > 2 && reader.getAttributeLocalName(2) == "packagePrefix") {
reader.getAttributeValue(2).replace('.', '/').takeIf { it.isNotEmpty() }
}
else {
null
}
}
typeAttributeName == "type" && typeAttributeValue == "java-resource" -> {
if (reader.attributeCount > 2 && reader.getAttributeLocalName(2) == "relativeOutputPath") {
reader.getAttributeValue(2).removeSuffix("/").takeIf { it.isNotEmpty() }
}
else {
null
}
}
else -> {
continue
}
}
rootsWithPrefixes.add(directoryPrefix to rootPath)
}
}
}
return rootsWithPrefixes
}
}
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" packagePrefix="com.intellij.tools.devLauncher" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.tools.devLauncher" scope="TEST" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
<orderEntry type="library" scope="TEST" name="JUnit5" level="project" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
</component>
</module>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/root.iml" filepath="$PROJECT_DIR$/root.iml" />
<module fileurl="file://$PROJECT_DIR$/simple/simple.iml" filepath="$PROJECT_DIR$/simple/simple.iml" />
<module fileurl="file://$PROJECT_DIR$/withPrefix/withPrefix.iml" filepath="$PROJECT_DIR$/withPrefix/withPrefix.iml" />
</modules>
</component>
</project>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/../additional-resources">
<sourceFolder url="file://$MODULE_DIR$/../additional-resources" type="java-resource" relativeOutputPath="prefix3" />
</content>
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" relativeOutputPath="prefix1" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="prefix2" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,39 @@
package com.intellij.tools.devLauncher
import com.intellij.openapi.application.ex.PathManagerEx
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.invariantSeparatorsPathString
import kotlin.io.path.relativeTo
class ModuleResourceFileFinderTest {
private lateinit var projectDir: Path
private lateinit var finder: ModuleResourceFileFinder
@BeforeEach
fun setUp() {
projectDir = Path(PathManagerEx.getCommunityHomePath()).resolve("tools/devLauncher/tests/testData/moduleResourceFinderProject")
finder = ModuleResourceFileFinder(projectDir)
}
@Test
fun `simple roots`() {
assertPath("simple/resources/a.txt", finder.findResourceFile("simple", "a.txt"))
assertPath("simple/src/b/b.txt", finder.findResourceFile("simple", "b/b.txt"))
assertPath(null, finder.findResourceFile("simple", "c.txt"))
}
@Test
fun `roots with prefixes`() {
assertPath("withPrefix/resources/a.txt", finder.findResourceFile("withPrefix", "prefix1/a.txt"))
assertPath("withPrefix/src/b/b.txt", finder.findResourceFile("withPrefix", "prefix2/b/b.txt"))
assertPath("additional-resources/c.txt", finder.findResourceFile("withPrefix", "prefix3/c.txt"))
}
private fun assertPath(expectedPath: String?, file: Path?) {
assertEquals(expectedPath, file?.relativeTo(projectDir)?.invariantSeparatorsPathString)
}
}