Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

using classpath: resolve relatively to the declaring file #161

Merged
merged 2 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions core/src/main/scala/replpp/UsingDirectives.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,13 @@ object UsingDirectives {
def findResolvers(lines: IterableOnce[String]): Seq[String] =
scanFor(ResolverDirective, lines)

def findClasspathEntries(lines: IterableOnce[String]): Seq[String] =
scanFor(ClasspathDirective, lines)
def findClasspathEntries(files: IterableOnce[Path]): Seq[Path] = {
for {
file <- files.iterator.toSeq
classpathEntry <- scanFor(ClasspathDirective, util.linesFromFile(file))
pathRelativeToDeclaringFile = file.getParent.resolve(classpathEntry)
} yield pathRelativeToDeclaringFile
}

private def scanFor(directive: String, lines: IterableOnce[String]): Seq[String] = {
lines
Expand Down
17 changes: 11 additions & 6 deletions core/src/main/scala/replpp/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,34 @@ package object replpp {
compilerArgs.result()
}

/** recursively find all relevant source files from main script, global predef file,
* provided predef files, other scripts that were imported with `using file` directive */
def allSourceFiles(config: Config): Seq[Path] =
(allPredefFiles(config) ++ config.scriptFile).distinct.sorted

def allPredefFiles(config: Config): Seq[Path] = {
val allPredefFiles = mutable.Set.empty[Path]
allPredefFiles ++= config.predefFiles
globalPredefFileMaybe.foreach(allPredefFiles.addOne)

// the directly resolved predef files might reference additional files via `using` directive
val predefFilesDirect = allPredefFiles.toSet
predefFilesDirect.foreach { file =>
val importedFiles = UsingDirectives.findImportedFilesRecursively(file, visited = allPredefFiles.toSet)
predefFilesDirect.foreach { predefFile =>
val importedFiles = UsingDirectives.findImportedFilesRecursively(predefFile, visited = allPredefFiles.toSet)
allPredefFiles ++= importedFiles
}

// the script (if any) might also reference additional files via `using` directive
config.scriptFile.foreach { file =>
val importedFiles = UsingDirectives.findImportedFilesRecursively(file, visited = allPredefFiles.toSet)
config.scriptFile.foreach { scriptFile =>
val importedFiles = UsingDirectives.findImportedFilesRecursively(scriptFile, visited = allPredefFiles.toSet)
allPredefFiles ++= importedFiles
}

allPredefFiles.toSeq.sorted
}

def allPredefLines(config: Config): Seq[String] =
allPredefFiles(config).flatMap(linesFromFile)
def allSourceLines(config: Config): Seq[String] =
allSourceFiles(config).flatMap(linesFromFile)

/** precompile given predef files (if any) and update Config to include the results in the classpath */
def precompilePredefFiles(config: Config): Config = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package replpp.scripting

import replpp.{Config, allPredefFiles}
import replpp.{Config, allPredefFiles, allSourceFiles}

import java.nio.file.Files

Expand Down
14 changes: 6 additions & 8 deletions core/src/main/scala/replpp/util/ClasspathHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,21 @@ object ClasspathHelper {
Using.resource(Source.fromFile(path.toFile))(_.getLines.toSeq)
}.getOrElse(Seq.empty)

val fromDependencies = dependencyArtifacts(config, scriptLines)
val fromDependencies = dependencyArtifacts(config)
fromDependencies.foreach(entries.addOne)
if (fromDependencies.nonEmpty && !quiet) {
println(s"resolved dependencies - adding ${fromDependencies.size} artifact(s) to classpath - to list them, enable verbose mode")
if (verboseEnabled(config)) fromDependencies.foreach(println)
}

val allLines = allPredefLines(config) ++ scriptLines
val additionalEntries = config.classpathConfig.additionalClasspathEntries ++ UsingDirectives.findClasspathEntries(allLines)
additionalEntries.map(Paths.get(_)).foreach(entries.addOne)
config.classpathConfig.additionalClasspathEntries.map(Paths.get(_)).foreach(entries.addOne)
UsingDirectives.findClasspathEntries(allSourceFiles(config)).iterator.foreach(entries.addOne)

entries.result().sorted
entries.result().distinct.sorted
}

private[util] def dependencyArtifacts(config: Config, scriptLines: Seq[String]): Seq[Path] = {
val allLines = allPredefLines(config) ++ scriptLines

private[util] def dependencyArtifacts(config: Config): Seq[Path] = {
val allLines = allSourceLines(config)
val resolvers = config.classpathConfig.resolvers ++ UsingDirectives.findResolvers(allLines)
val allDependencies = config.classpathConfig.dependencies ++ UsingDirectives.findDeclaredDependencies(allLines)
Dependencies.resolve(allDependencies, resolvers, verboseEnabled(config)).get
Expand Down
12 changes: 8 additions & 4 deletions core/src/test/scala/replpp/PredefTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class PredefTests extends AnyWordSpec with Matchers {
given Colors = Colors.BlackWhite

"recursively resolve `//> using file` directive" in {
val script = os.temp("val mainScript = 5")
val additionalScript1 = os.temp("val additionalScript1 = 10")
val additionalScript2 = os.temp("val additionalScript2 = 20")
val predefFile = os.temp(
Expand All @@ -15,17 +16,19 @@ class PredefTests extends AnyWordSpec with Matchers {
|//> using file $additionalScript2
|""".stripMargin)

allPredefLines(Config(predefFiles = Seq(predefFile.toNIO))).sorted shouldBe
allSourceLines(Config(predefFiles = Seq(predefFile.toNIO), scriptFile = Some(script.toNIO))).sorted shouldBe
Seq(
s"//> using file $additionalScript1",
s"//> using file $additionalScript2",
"val additionalScript1 = 10",
"val additionalScript2 = 20",
"val predefCode = 1"
"val mainScript = 5",
"val predefCode = 1",
).sorted
}

"recursively resolve `//> using file` directive - with recursive loops" in {
val script = os.temp("val mainScript = 5")
val additionalScript1 = os.temp(suffix = "-script1")
val additionalScript2 = os.temp(suffix = "-script2")

Expand All @@ -43,13 +46,14 @@ class PredefTests extends AnyWordSpec with Matchers {
|""".stripMargin)

// most importantly, this should not loop endlessly due to the recursive imports
allPredefLines(Config(predefFiles = Seq(predefFile.toNIO))).distinct.sorted shouldBe
allSourceLines(Config(predefFiles = Seq(predefFile.toNIO), scriptFile = Some(script.toNIO))).distinct.sorted shouldBe
Seq(
s"//> using file $additionalScript1",
s"//> using file $additionalScript2",
"val additionalScript1 = 10",
"val additionalScript2 = 20",
"val predefCode = 1"
"val mainScript = 5",
"val predefCode = 1",
).sorted
}
}
37 changes: 24 additions & 13 deletions core/src/test/scala/replpp/UsingDirectivesTests.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package replpp

import java.nio.file.Paths
import java.nio.file.{Path, Paths}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

import scala.jdk.CollectionConverters.*

class UsingDirectivesTests extends AnyWordSpec with Matchers {
Expand Down Expand Up @@ -78,18 +79,28 @@ class UsingDirectivesTests extends AnyWordSpec with Matchers {
results should not contain "https://commented.out/repo"
}

"find declared classpath entries" in {
val source =
"""
|//> using classpath /path/to/cp1
|//> using classpath ../path/to/cp2
|// //> using classpath cp3
|""".stripMargin

val results = UsingDirectives.findClasspathEntries(source.linesIterator)
results should contain("/path/to/cp1")
results should contain("../path/to/cp2")
results should not contain "cp3"
if (scala.util.Properties.isWin) {
info("paths work differently on windows - ignoring some tests")
} else {
"find declared classpath entries" in {
val scriptFile = os.temp(
"""
|//> using classpath /path/to/cp1
|//> using classpath path/to/cp2
|//> using classpath ../path/to/cp3
|// //> using classpath cp4
|""".stripMargin
).toNIO

val scriptParentDir = scriptFile.getParent

val results = UsingDirectives.findClasspathEntries(Seq(scriptFile))
results should contain(Path.of("/path/to/cp1"))
results should contain(scriptParentDir.resolve("path/to/cp2"))
results should contain(scriptParentDir.resolve("../path/to/cp3"))
results should not contain Path.of("cp3")
results.size shouldBe 3 // just to triple check
}
}

}
5 changes: 3 additions & 2 deletions core/src/test/scala/replpp/util/ClasspathHelperTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@ class ClasspathHelperTests extends AnyWordSpec with Matchers {
"org.scala-lang:scala-library:2.13.10",
"org.scala-lang::scala3-library:3.3.0",
)))
val deps = ClasspathHelper.dependencyArtifacts(config, scriptLines = Seq.empty)
val deps = ClasspathHelper.dependencyArtifacts(config)
deps.size shouldBe 2

assert(deps.find(_.endsWith("scala3-library_3-3.3.0.jar")).isDefined)
assert(deps.find(_.endsWith("scala-library-2.13.10.jar")).isDefined)
}

"declared in scriptFile" in {
val deps = ClasspathHelper.dependencyArtifacts(Config(), scriptLines = Seq("//> using dep com.michaelpollmeier::colordiff:0.36"))
val script = os.temp("//> using dep com.michaelpollmeier::colordiff:0.36")
val deps = ClasspathHelper.dependencyArtifacts(Config(scriptFile = Some(script.toNIO)))
deps.size shouldBe 4

assert(deps.find(_.endsWith("colordiff_3-0.36.jar")).isDefined)
Expand Down
Loading