diff --git a/core/src/main/scala/replpp/UsingDirectives.scala b/core/src/main/scala/replpp/UsingDirectives.scala index 5defad7..03cb97b 100644 --- a/core/src/main/scala/replpp/UsingDirectives.scala +++ b/core/src/main/scala/replpp/UsingDirectives.scala @@ -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 diff --git a/core/src/main/scala/replpp/package.scala b/core/src/main/scala/replpp/package.scala index 3f0d88d..cb0f90c 100644 --- a/core/src/main/scala/replpp/package.scala +++ b/core/src/main/scala/replpp/package.scala @@ -41,6 +41,11 @@ 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 @@ -48,22 +53,22 @@ package object replpp { // 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 = { diff --git a/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala b/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala index df91991..cea2c4a 100644 --- a/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala +++ b/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala @@ -1,6 +1,6 @@ package replpp.scripting -import replpp.{Config, allPredefFiles} +import replpp.{Config, allPredefFiles, allSourceFiles} import java.nio.file.Files diff --git a/core/src/main/scala/replpp/util/ClasspathHelper.scala b/core/src/main/scala/replpp/util/ClasspathHelper.scala index f679868..8429c5e 100644 --- a/core/src/main/scala/replpp/util/ClasspathHelper.scala +++ b/core/src/main/scala/replpp/util/ClasspathHelper.scala @@ -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 diff --git a/core/src/test/scala/replpp/PredefTests.scala b/core/src/test/scala/replpp/PredefTests.scala index 644816d..cd90751 100644 --- a/core/src/test/scala/replpp/PredefTests.scala +++ b/core/src/test/scala/replpp/PredefTests.scala @@ -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( @@ -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") @@ -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 } } diff --git a/core/src/test/scala/replpp/UsingDirectivesTests.scala b/core/src/test/scala/replpp/UsingDirectivesTests.scala index 3487716..c9dbfcf 100644 --- a/core/src/test/scala/replpp/UsingDirectivesTests.scala +++ b/core/src/test/scala/replpp/UsingDirectivesTests.scala @@ -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 { @@ -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 + } } } diff --git a/core/src/test/scala/replpp/util/ClasspathHelperTests.scala b/core/src/test/scala/replpp/util/ClasspathHelperTests.scala index 83375e5..93bc5db 100644 --- a/core/src/test/scala/replpp/util/ClasspathHelperTests.scala +++ b/core/src/test/scala/replpp/util/ClasspathHelperTests.scala @@ -29,7 +29,7 @@ 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) @@ -37,7 +37,8 @@ class ClasspathHelperTests extends AnyWordSpec with Matchers { } "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)