From f7eccae630dc98e94da36a4969ac4afcbcbd7cfa Mon Sep 17 00:00:00 2001 From: Tobias Kahlert Date: Wed, 5 Jun 2024 19:57:12 +0200 Subject: [PATCH] implemented new searchable options index file patching #SCL-22649 --- .../searchableoptions/BuildIndex.scala | 113 +++++++++++++----- 1 file changed, 84 insertions(+), 29 deletions(-) diff --git a/ideaSupport/src/main/scala/org/jetbrains/sbtidea/searchableoptions/BuildIndex.scala b/ideaSupport/src/main/scala/org/jetbrains/sbtidea/searchableoptions/BuildIndex.scala index 7d25b104..7229cdcb 100644 --- a/ideaSupport/src/main/scala/org/jetbrains/sbtidea/searchableoptions/BuildIndex.scala +++ b/ideaSupport/src/main/scala/org/jetbrains/sbtidea/searchableoptions/BuildIndex.scala @@ -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} @@ -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 /target/searchableOptions. + * + * In the old version before 2024.2 there was one search index xml file per jar-file, which was dumped into + * /target/searchableOptions//search/.searchableOptions.xml + * and which we needed to copy to /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 + * /taget/serachableOptions/p--searchableOptions.json + * and which we need to copy to /p--searchableOptions.json + * plugin-jar is the jar archive that contains the meta-data for the plugin with 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) @@ -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) { @@ -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) + ) } }