diff --git a/core/api/src/main/scala/org/virtuslab/ideprobe/ProbeExtensions.scala b/core/api/src/main/scala/org/virtuslab/ideprobe/ProbeExtensions.scala index 13da51c9..9cfbe3aa 100644 --- a/core/api/src/main/scala/org/virtuslab/ideprobe/ProbeExtensions.scala +++ b/core/api/src/main/scala/org/virtuslab/ideprobe/ProbeExtensions.scala @@ -65,6 +65,10 @@ trait ProbeExtensions { path.getFileName.toString } + def exists: Boolean = { + Files.exists(path) + } + def isFile: Boolean = { Files.isRegularFile(path) } diff --git a/core/driver/sources/src/main/resources/reference.conf b/core/driver/sources/src/main/resources/reference.conf index f1e81fce..ba21a4e6 100644 --- a/core/driver/sources/src/main/resources/reference.conf +++ b/core/driver/sources/src/main/resources/reference.conf @@ -109,6 +109,23 @@ probe { // ~/.pluginVerifier/ides/IC-[revision]/ directory (if exists). If it does not exist under specified path, // then ide-probe will download it from one of the 6 official repositories. // + // If your config contains a pattern pointing to an IntelliJ instance that exists on local filesystem, then you + // can use globs (* widcard characters) as parts of the pattern. One * wildcard character replaces + // one atomic directory on the path. Multiple * wildcards can be used in one pattern. For example - this can + // be useful if you want to use .zip OR installed IntelliJ downloaded earlier by some gradle task: + // intellij.repositories = [ + // "file:///"${HOME}"/.gradle/caches/modules-2/files-2.1/com.jetbrains.intellij.idea/ideaIC/[revision]/*/ideaIC-[revision]/" + // ] + // - as you can see - the * wildcard is handy as it replaces a hash that could not be known easily. There could be + // multiple different directories in the * place - but not all of them lead to a valid IntelliJ resource. ide-probe + // deals with such situations and finds the correct path to a valid IntelliJ resource. The pattern must start with + // "file:" if resolving * wildcards should be used. + // Note: example below would work as good as the previous one. Multiple * wildcards in one path are handled properly: + // intellij.repositories = [ + // "file:///"${HOME}"/.gradle/*/modules-2/*/*/ideaIC/[revision]/*/ideaIC-[revision]/" + // ] + // Note: you can NOT use the * wildcard in place of the last element of the pattern (as that would lead to ambiguous results). + // // Note: if you use the `intellij.repositories` config with a value pointing to an installed // IntelliJ instance directory - then DO NOT use the `probe.intellij.path` config in the same time. // The `probe.intellij.path` config takes precedence over `intellij.repositories` in such case, so the path diff --git a/core/driver/sources/src/main/scala/org/virtuslab/ideprobe/dependencies/IntelliJResolver.scala b/core/driver/sources/src/main/scala/org/virtuslab/ideprobe/dependencies/IntelliJResolver.scala index 844408dc..cfa5e1c8 100644 --- a/core/driver/sources/src/main/scala/org/virtuslab/ideprobe/dependencies/IntelliJResolver.scala +++ b/core/driver/sources/src/main/scala/org/virtuslab/ideprobe/dependencies/IntelliJResolver.scala @@ -1,8 +1,14 @@ package org.virtuslab.ideprobe.dependencies +import java.nio.file.Paths + +import scala.annotation.tailrec + import org.virtuslab.ideprobe.IntelliJFixture import org.virtuslab.ideprobe.config.DependenciesConfig +import org.virtuslab.ideprobe.Extensions.PathExtension + trait IntelliJResolver { def community: DependencyResolver[IntelliJVersion] def ultimate: DependencyResolver[IntelliJVersion] @@ -37,9 +43,54 @@ case class IntelliJPatternResolver(pattern: String) extends IntelliJResolver { "version" -> version.releaseOrBuild, "release" -> version.release.getOrElse("snapshot-release") ) - val replaced = replacements.foldLeft(pattern) { case (path, (pattern, replacement)) => + val replacedBeforeResolvingGlobs = replacements.foldLeft(pattern) { case (path, (pattern, replacement)) => path.replace(s"[$pattern]", replacement) } + val replaced = + if (replacedBeforeResolvingGlobs.startsWith("file:")) + resolveGlobsInPattern(replacedBeforeResolvingGlobs) + else + replacedBeforeResolvingGlobs + Dependency(replaced) } + + private def resolveGlobsInPattern(pathPattern: String): String = + replaceGlobsWithExistingDirectories(List(pathPattern), pathPattern).head + + // Solution below assumes that each * character is used to mark one part of the path (one atomic directory), + // for example: "file:///home/.cache/ides/com.jetbrains.intellij.idea/ideaIC/[revision]/*/ideaIC-[revision]/". + // Wildcard character should NOT be used as the last element of the pattern - to avoid ambiguous results. + // Works only for files in local filesystem. + @tailrec + private def replaceGlobsWithExistingDirectories(paths: List[String], originalPattern: String): List[String] = + if (paths.exists(pathMightBeValidResource(_, originalPattern))) + paths.filter(pathMightBeValidResource(_, originalPattern)) + else + replaceGlobsWithExistingDirectories(paths.flatMap(replaceFirstFoundWildcardWithDirectories), originalPattern) + + private def pathMightBeValidResource(candidatePath: String, originalPattern: String): Boolean = + !candidatePath.contains('*') && // below - make sure that candidatePath's last path element is same as in pattern + candidatePath.substring(candidatePath.lastIndexOf('/')) == originalPattern.substring( + originalPattern.lastIndexOf('/') + ) && Paths.get(candidatePath.replace("file:", "")).exists + + private def replaceFirstFoundWildcardWithDirectories(path: String): List[String] = { + def removeLastFileSeparator(path: String): String = if (path.endsWith("/")) path.init else path + + val pathUntilWildcard = Paths.get(path.substring(0, path.indexOf('*')).replace("file:", "")) + val stringPathAfterWildcard = path.substring(path.indexOf('*') + 1) + + if (pathUntilWildcard.exists) { + pathUntilWildcard + .directChildren() + .map { replaced => + (if (path.startsWith("file:")) "file:" else "") + removeLastFileSeparator( + replaced.toString + ) + stringPathAfterWildcard + } + } else { + Nil + } + } } diff --git a/core/driver/sources/src/test/resources/org/virtuslab/ideprobe/dependencies/intellij/maven/com/invalid/intellij/idea/artifact/1.0/artifact-1.0.zip b/core/driver/sources/src/test/resources/org/virtuslab/ideprobe/dependencies/intellij/maven/com/invalid/intellij/idea/artifact/1.0/artifact-1.0.zip new file mode 100644 index 00000000..e69de29b diff --git a/core/driver/sources/src/test/resources/org/virtuslab/ideprobe/dependencies/intellij/maven/com/jetbrains/intellij/idea/artifact/1.0/artifact-1.0.dmg b/core/driver/sources/src/test/resources/org/virtuslab/ideprobe/dependencies/intellij/maven/com/jetbrains/intellij/idea/artifact/1.0/artifact-1.0.dmg new file mode 100644 index 00000000..e69de29b diff --git a/core/driver/sources/src/test/scala/org/virtuslab/ideprobe/dependencies/IntelliJResolverTest.scala b/core/driver/sources/src/test/scala/org/virtuslab/ideprobe/dependencies/IntelliJResolverTest.scala index c03b117f..b7c8556f 100644 --- a/core/driver/sources/src/test/scala/org/virtuslab/ideprobe/dependencies/IntelliJResolverTest.scala +++ b/core/driver/sources/src/test/scala/org/virtuslab/ideprobe/dependencies/IntelliJResolverTest.scala @@ -11,6 +11,7 @@ import org.junit.runners.JUnit4 import org.virtuslab.ideprobe.Config import org.virtuslab.ideprobe.ConfigFormat +import org.virtuslab.ideprobe.IntelliJFixture import org.virtuslab.ideprobe.config.DependenciesConfig @RunWith(classOf[JUnit4]) @@ -47,6 +48,21 @@ class IntelliJResolverTest extends ConfigFormat { assertExists(artifactUri) } + @Test + def resolvesIntelliJPatternWithGlobesUsed(): Unit = { + val probeConfig = IntelliJFixture.readIdeProbeConfig( + Config.fromString(s""" + |probe.resolvers.intellij.repositories = [ + | "$mavenRepo/com/*/intellij/*/$mavenArtifact/${mavenVersion.build}/$mavenArtifact-${mavenVersion.build}.zip" + |] + |""".stripMargin), + "probe" + ) + val repo = IntelliJResolver.fromConfig(probeConfig.resolvers).head + val artifactUri = repo.resolve(mavenVersion) + assertExists(artifactUri) + } + private def assertExists(dependency: Dependency): Unit = dependency match { case Dependency.Artifact(uri) => assertTrue(s"Resolved invalid artifact: $uri", Files.exists(Paths.get(uri)))