Skip to content

Commit

Permalink
Fix glob resolver stalling on non-existent paths (#325)
Browse files Browse the repository at this point in the history
  • Loading branch information
PawelLipski authored Nov 22, 2022
1 parent 8c2a6e0 commit a208722
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 45 deletions.
2 changes: 1 addition & 1 deletion core/driver/sources/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ probe {
//
// `probe.workspace.path` can point to:
// a) a directory on the filesystem:
// `probe.workspace.path = "file://home/foo/bar"`
// `probe.workspace.path = "file:///home/foo/bar"`
// b) a directory within a jar:
// `probe.workspace.path = "jar:file://foo.jar!/bar/baz"`
// c) a directory on the classpath:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ package org.virtuslab.ideprobe.dependencies
trait DependencyResolver[Key] {
def resolve(key: Key): Dependency
}

object DependencyResolver {
def apply[Key](f: Key => Dependency): DependencyResolver[Key] = new DependencyResolver[Key] {
def resolve(key: Key): Dependency = f(key)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package org.virtuslab.ideprobe.dependencies

import java.io.File
import java.nio.file.Path
import java.nio.file.Paths

import scala.annotation.tailrec

import org.virtuslab.ideprobe.IntelliJFixture
import org.virtuslab.ideprobe.config.DependenciesConfig

Expand Down Expand Up @@ -31,7 +31,7 @@ case class IntelliJPatternResolver(pattern: String) extends IntelliJResolver {
def community: DependencyResolver[IntelliJVersion] = resolver("ideaIC")
def ultimate: DependencyResolver[IntelliJVersion] = resolver("ideaIU")

def resolver(artifact: String): DependencyResolver[IntelliJVersion] = { version =>
def resolver(artifact: String): DependencyResolver[IntelliJVersion] = DependencyResolver { version =>
val replacements = Map(
"organisation" -> "com.jetbrains.intellij",
"orgPath" -> "com/jetbrains/intellij",
Expand All @@ -43,54 +43,40 @@ case class IntelliJPatternResolver(pattern: String) extends IntelliJResolver {
"version" -> version.releaseOrBuild,
"release" -> version.release.getOrElse("snapshot-release")
)
val replacedBeforeResolvingGlobs = replacements.foldLeft(pattern) { case (path, (pattern, replacement)) =>
val artifactWithReplacementsApplied = replacements.foldLeft(pattern) { case (path, (pattern, replacement)) =>
path.replace(s"[$pattern]", replacement)
}
val replaced =
if (replacedBeforeResolvingGlobs.startsWith("file:"))
resolveGlobsInPattern(replacedBeforeResolvingGlobs)
else
replacedBeforeResolvingGlobs

Dependency(replaced)
if (artifactWithReplacementsApplied.startsWith("file:"))
findFilesMatchingGlob(artifactWithReplacementsApplied.stripPrefix("file:"))
.map("file:" + _)
.map(Dependency.apply)
.getOrElse(Dependency.Missing)
else
Dependency(artifactWithReplacementsApplied)
}

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),
// The 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)
private def findFilesMatchingGlob(pathPattern: String): Option[String] = {
findFilesMatchingGlobRecursively(
Paths.get(pathPattern).getRoot,
pathPattern.split(File.separatorChar).filter(_.nonEmpty).toList
).headOption
}

if (pathUntilWildcard.exists) {
pathUntilWildcard
.directChildren()
.map { replaced =>
(if (path.startsWith("file:")) "file:" else "") + removeLastFileSeparator(
replaced.toString
) + stringPathAfterWildcard
}
} else {
Nil
private def findFilesMatchingGlobRecursively(accumulatedPath: Path, remainingSegments: List[String]): List[String] = {
remainingSegments match {
case "*" :: tail =>
accumulatedPath.directChildren().flatMap(child => findFilesMatchingGlobRecursively(child, tail))
case head :: tail if accumulatedPath.resolve(head).exists =>
findFilesMatchingGlobRecursively(accumulatedPath.resolve(head), tail)
case _ :: _ =>
Nil
case Nil =>
List(accumulatedPath.toString)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.virtuslab.ideprobe.dependencies
import java.nio.file.Files
import java.nio.file.Paths

import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Test
Expand Down Expand Up @@ -53,13 +54,17 @@ class IntelliJResolverTest extends ConfigFormat {
val probeConfig = IntelliJFixture.readIdeProbeConfig(
Config.fromString(s"""
|probe.resolvers.intellij.repositories = [
| "file:///non/existent/path",
| "file:///non/*/existent/path",
| "$mavenRepo/com/*/intellij/*/$mavenArtifact/${mavenVersion.build}/$mavenArtifact-${mavenVersion.build}.zip"
|]
|""".stripMargin),
"probe"
)
val repo = IntelliJResolver.fromConfig(probeConfig.resolvers).head
val artifactUri = repo.resolve(mavenVersion)
val resolvers = IntelliJResolver.fromConfig(probeConfig.resolvers)
assertEquals(Dependency.Missing, resolvers(0).resolve(mavenVersion))
assertEquals(Dependency.Missing, resolvers(1).resolve(mavenVersion))
val artifactUri = resolvers(2).resolve(mavenVersion)
assertExists(artifactUri)
}

Expand Down

0 comments on commit a208722

Please sign in to comment.