Skip to content

Commit

Permalink
implemented new searchable options index file patching #SCL-22649
Browse files Browse the repository at this point in the history
  • Loading branch information
SrTobi committed Jun 5, 2024
1 parent 0f50458 commit f7eccae
Showing 1 changed file with 84 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.jetbrains.sbtidea.searchableoptions

import org.jetbrains.sbtidea.Keys.{intellijBaseDirectory, intellijVMOptions}
import org.jetbrains.sbtidea.download.NioUtils
import org.jetbrains.sbtidea.download.plugin.LocalPluginRegistry
import org.jetbrains.sbtidea.packaging.*
import org.jetbrains.sbtidea.packaging.PackagingKeys.packageArtifact
import org.jetbrains.sbtidea.packaging.artifact.{DistBuilder, DumbIncrementalCache, IncrementalCache}
Expand All @@ -14,10 +15,30 @@ import java.nio.file.{Files, Path}
import java.util.function.Predicate
import scala.collection.JavaConverters.*

/**
* Task to generate searchable options indices and insert them into the plugin jars
* To generate the indices we run a headless idea with the `traverseUI` command, which will dump all indices
* into <root>/target/searchableOptions.
*
* In the old version before 2024.2 there was one search index xml file per jar-file, which was dumped into
* <root>/target/searchableOptions/<name-of-jar>/search/<name-of-jar>.searchableOptions.xml
* and which we needed to copy to <jar-file>/search/searchableOptions.xml
* for all jar files that belong to the plugin.
*
* Since 2024.2 there are json index files per plugin, which are dumped to
* <root>/taget/serachableOptions/p-<plugin-id>-searchableOptions.json
* and which we need to copy to <plugin-jar>/p-<plugin-id>-searchableOptions.json
* plugin-jar is the jar archive that contains the meta-data for the plugin with plugin-id <plugin-id>.
*
* BuildIndex handles both cases simultaneously, so it should work in any version of intellij.
*/
object BuildIndex {

private val IDX_DIR = "search"
private type IndexElement = (Path, Path) // jar file -> options.xml
private trait IndexElement
private object IndexElement {
case class Old(jar: Path, xml: Path) extends IndexElement
case class New(jar: Path, json: Path) extends IndexElement
}

def createTask: Def.Initialize[Task[Unit]] = Def.task {
implicit val log: PluginLogger = new SbtPluginLogger(streams.value)
Expand All @@ -34,15 +55,16 @@ object BuildIndex {

val tmp = Files.createTempDirectory("sbt-idea-searchable-options-building-")
try {
val indexRoots = getIndexFiles(pluginRoot, indexOutputPath.toPath)
val indexRoots =
getIndexFilesOld(pluginRoot, indexOutputPath.toPath) ++
getIndexFilesNew(pluginRoot, indexOutputPath.toPath)
val indexedMappings = prepareMappings(indexRoots, tmp)

if (indexRoots.isEmpty)
log.error(s"No options search index built for plugin root: $pluginRoot")
throw new MessageOnlyException(s"No options search index built for plugin root: $pluginRoot")

if (indexedMappings.isEmpty)
log.error(s"No options search index packaged from given roots: $indexRoots")

throw new MessageOnlyException(s"No options search index packaged from given roots: $indexRoots")

indexedMappings.foreach { case (jar, mapping) =>
val distBuilder = new DistBuilder(streams.value, target.value) {
Expand All @@ -58,41 +80,74 @@ object BuildIndex {
}
}


private def getIndexFiles(pluginOutputDir: Path, indexOutputDir: Path): Seq[IndexElement] = {
if (!indexOutputDir.exists)
return Nil

private def listArtifactJars(pluginOutputDir: Path): Map[String, Path] = {
val predicate = new Predicate[Path] { override def test(p: Path): Boolean = p.toString.endsWith("jar") }

val allArtifactJars = Files.walk(pluginOutputDir)
Files.walk(pluginOutputDir)
.filter(predicate)
.iterator().asScala
.map(path => path.getFileName.toString -> path)
.toMap
}

val indexesForPlugin: Seq[(Path, Path)] = indexOutputDir
private def getIndexFilesOld(pluginOutputDir: Path, indexOutputDir: Path): Seq[IndexElement] = {
if (!indexOutputDir.exists)
return Nil

val allArtifactJars = listArtifactJars(pluginOutputDir)

val indexesForPlugin: Seq[IndexElement] = indexOutputDir
.list
.filter(idx => allArtifactJars.contains(idx.getFileName.toString))
.filter(idx => (idx / IDX_DIR).exists && (idx / IDX_DIR).isDir && (idx / IDX_DIR).list.nonEmpty)
.foldLeft(Seq.empty[IndexElement]) { (acc, idx) =>
acc :+ allArtifactJars(idx.getFileName.toString) -> (idx / IDX_DIR).list.head
}
.map(idx => IndexElement.Old(allArtifactJars(idx.getFileName.toString), (idx / IDX_DIR).list.head))

indexesForPlugin
}

private def prepareMappings(indexes: Seq[IndexElement], tmp: Path): Seq[(Path, Mapping)] =
indexes.zipWithIndex.map { case ((jar, indexXML), idx) =>
// copy the xml file into a temporary directory that has the required target structure: search/searchableOptions.xml
val source = tmp / s"index-${jar.getFileName}-$idx"
val searchDir = source / "search"
Files.createDirectories(searchDir)
Files.copy(indexXML, searchDir / "searchableOptions.xml")

jar ->
Mapping(source.toFile,
jar.toFile,
MappingMetaData.EMPTY.copy(kind = MAPPING_KIND.MISC))
private def getIndexFilesNew(pluginOutputDir: Path, indexOutputDir: Path)(implicit log: PluginLogger): Seq[IndexElement] = {
if (!indexOutputDir.exists)
return Nil

val allArtifactJars = listArtifactJars(pluginOutputDir)

def getPluginId(jarPath: Path): Option[String] =
LocalPluginRegistry.extractPluginMetaData(jarPath).toOption.map(_.id)

for {
jarPath <- allArtifactJars.values.toSeq
pluginId <- getPluginId(jarPath)
indexPath = indexOutputDir / s"p-$pluginId-searchableOptions.json"
if Files.exists(indexPath)
} yield IndexElement.New(jarPath, indexPath)
}

private def prepareMappings(indexes: Seq[IndexElement], tmp: Path)(implicit log: PluginLogger): Seq[(Path, Mapping)] =
indexes.map {
case IndexElement.Old(jar, indexXML) =>
// copy the xml file into a temporary directory that has the required target structure: search/searchableOptions.xml
val source = tmp / s"index-${jar.getFileName}-xml"
val searchDir = source / "search"
Files.createDirectories(searchDir)
Files.copy(indexXML, searchDir / "searchableOptions.xml")

log.info(s"SearchIndex Mapping: $indexXML -> ${jar / "search" / "searchableOptions.xml"}")

jar ->
Mapping(source.toFile,
jar.toFile,
MappingMetaData.EMPTY.copy(kind = MAPPING_KIND.MISC)
)
case IndexElement.New(jar, indexJson) =>
val source = tmp / s"index-${jar.getFileName}-json"
Files.createDirectories(source)
Files.copy(indexJson, source / indexJson.getFileName.toString)

log.info(s"SearchIndex Mapping: $indexJson -> ${jar / indexJson.getFileName.toString}")

jar ->
Mapping(source.toFile,
jar.toFile,
MappingMetaData.EMPTY.copy(kind = MAPPING_KIND.MISC)
)
}
}

0 comments on commit f7eccae

Please sign in to comment.