Skip to content

Commit de4280e

Browse files
Accept dependencies via using directives too
1 parent c36de8a commit de4280e

File tree

12 files changed

+367
-56
lines changed

12 files changed

+367
-56
lines changed

.scalafmt.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ fileOverride {
2222
}
2323
project.excludeFilters = [
2424
".metals"
25-
"examples/scala-3" # Scala 3 scripts not supported yet
25+
"examples" # Scala 3 scripts and using directives not supported yet
2626
"out"
2727
]

build.sc

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ object `generate-reference-doc` extends SbtModule {
8888
def moduleDeps = Seq(
8989
cli
9090
)
91+
def repositories = super.repositories ++ Seq(
92+
coursier.Repositories.sonatype("snapshots")
93+
)
9194
def ivyDeps = Agg(
9295
Deps.caseApp,
9396
Deps.munit
@@ -115,6 +118,9 @@ class Build(val crossScalaVersion: String)
115118
`test-runner`(),
116119
`tasty-lib`()
117120
)
121+
def repositories = super.repositories ++ Seq(
122+
coursier.Repositories.sonatype("snapshots")
123+
)
118124
def ivyDeps = super.ivyDeps() ++ Agg(
119125
Deps.asm,
120126
Deps.bloopConfig,
@@ -134,7 +140,8 @@ class Build(val crossScalaVersion: String)
134140
Deps.scalametaTrees,
135141
Deps.scalaparse,
136142
Deps.shapeless,
137-
Deps.swoval
143+
Deps.swoval,
144+
Deps.usingDirectives
138145
)
139146

140147
private def vcsState = {
@@ -229,6 +236,9 @@ trait Cli extends SbtModule with CliLaunchers with ScalaCliPublishModule with Fo
229236
build(Scala.defaultInternal),
230237
`test-runner`(Scala.defaultInternal)
231238
)
239+
def repositories = super.repositories ++ Seq(
240+
coursier.Repositories.sonatype("snapshots")
241+
)
232242
def ivyDeps = super.ivyDeps() ++ Agg(
233243
Deps.caseApp,
234244
Deps.coursierLauncher,

examples/utest/MyTests.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import $ivy.`com.lihaoyi::utest::0.7.10`, utest._
1+
@using lib "com.lihaoyi::utest::0.7.10"
2+
3+
import utest._
24

35
object MyTests extends TestSuite {
46
val tests = Tests {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package scala.build.preprocessing
2+
3+
import com.virtuslab.using_directives.custom.utils.Position
4+
import com.virtuslab.using_directives.reporter.Reporter
5+
6+
import java.io.PrintStream
7+
8+
class DirectivesOutputStreamReporter(out: PrintStream) extends Reporter {
9+
10+
private var errorCount = 0
11+
12+
private def msgWithPos(pos: Position, msg: String): String =
13+
s"${pos.getLine}:${pos.getColumn}:\n$msg"
14+
15+
private def errorMessage(msg: String): String = {
16+
errorCount += 1
17+
s"ERROR: $msg"
18+
}
19+
private def warningMessage(msg: String): String =
20+
s"WARNING: $msg"
21+
22+
override def error(msg: String): Unit =
23+
out.println(errorMessage(msg))
24+
override def error(position: Position, msg: String): Unit =
25+
out.println(msgWithPos(position, errorMessage(msg)))
26+
override def warning(msg: String): Unit =
27+
out.println(warningMessage(msg))
28+
override def warning(position: Position, msg: String): Unit =
29+
out.println(msgWithPos(position, warningMessage(msg)))
30+
31+
override def hasErrors(): Boolean =
32+
errorCount != 0
33+
34+
override def reset(): Unit = {
35+
errorCount = 0
36+
}
37+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package scala.build.preprocessing
2+
3+
import com.virtuslab.using_directives.custom.model.{Path, Value}
4+
import dependency.AnyDependency
5+
import dependency.parser.DependencyParser
6+
7+
import scala.build.options.{BuildOptions, ClassPathOptions, ScalaOptions}
8+
import scala.collection.JavaConverters._
9+
10+
object DirectivesProcessor {
11+
12+
private val processors = Map(
13+
"lib" -> (processLib _)
14+
)
15+
16+
private def processLib(value: Any): BuildOptions = {
17+
18+
val extraDependencies = Some(value)
19+
.collect {
20+
case list: java.util.List[_] =>
21+
list
22+
.asScala
23+
.map {
24+
case v: Value[_] => v.get()
25+
}
26+
.collect {
27+
case s: String => s
28+
}
29+
.toVector
30+
case s: String =>
31+
Vector(s)
32+
}
33+
.map { deps =>
34+
// Really necessary? (might already be handled by the coursier-dependency library)
35+
val deps0 = deps.map(_.filter(!_.isSpaceChar))
36+
37+
deps0.map(parseDependency)
38+
}
39+
.getOrElse(Vector.empty)
40+
41+
BuildOptions(
42+
classPathOptions = ClassPathOptions(
43+
extraDependencies = extraDependencies
44+
)
45+
)
46+
}
47+
48+
private def processScala(value: Any): BuildOptions = {
49+
50+
val versions = Some(value)
51+
.toList
52+
.collect {
53+
case list: java.util.List[_] =>
54+
list.asScala.collect { case v: String => v }.toList
55+
case v: String =>
56+
List(v)
57+
}
58+
.flatten
59+
.map(_.filter(!_.isSpaceChar))
60+
.filter(_.nonEmpty)
61+
.distinct
62+
63+
versions match {
64+
case Nil => BuildOptions()
65+
case v :: Nil =>
66+
BuildOptions(
67+
scalaOptions = ScalaOptions(
68+
scalaVersion = Some(v)
69+
)
70+
)
71+
case _ =>
72+
val highest = versions.maxBy(coursier.core.Version(_))
73+
BuildOptions(
74+
scalaOptions = ScalaOptions(
75+
scalaVersion = Some(highest)
76+
)
77+
)
78+
}
79+
}
80+
81+
def process(directives: Map[Path, Value[_]]): BuildOptions = {
82+
83+
val values = directives.map {
84+
case (k, v) =>
85+
k.getPath.asScala.mkString(".") -> (v.get: Any)
86+
}
87+
88+
values
89+
.iterator
90+
.flatMap {
91+
case (k, v) =>
92+
processors.get(k).iterator.map { f =>
93+
f(v)
94+
}
95+
}
96+
.foldLeft(BuildOptions())(_ orElse _)
97+
}
98+
99+
private def parseDependency(str: String): AnyDependency =
100+
DependencyParser.parse(str) match {
101+
case Left(msg) => sys.error(s"Malformed dependency '$str': $msg")
102+
case Right(dep) => dep
103+
}
104+
}

modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package scala.build.preprocessing
22

3+
import com.virtuslab.using_directives.{Context, UsingDirectivesProcessor}
4+
import com.virtuslab.using_directives.config.Settings
5+
import com.virtuslab.using_directives.custom.model.Path
6+
import com.virtuslab.using_directives.reporter.ConsoleReporter
37
import dependency.AnyDependency
48
import dependency.parser.DependencyParser
59

@@ -8,6 +12,7 @@ import java.nio.charset.StandardCharsets
812
import scala.build.{Inputs, Os, Sources}
913
import scala.build.internal.AmmUtil
1014
import scala.build.options.{BuildOptions, ClassPathOptions}
15+
import scala.collection.JavaConverters._
1116

1217
case object ScalaPreprocessor extends Preprocessor {
1318
def preprocess(input: Inputs.SingleElement): Option[Seq[PreprocessedSource]] =
@@ -50,12 +55,6 @@ case object ScalaPreprocessor extends Preprocessor {
5055
None
5156
}
5257

53-
private def parseDependency(str: String): AnyDependency =
54-
DependencyParser.parse(str) match {
55-
case Left(msg) => sys.error(s"Malformed dependency '$str': $msg")
56-
case Right(dep) => dep
57-
}
58-
5958
def process(path: os.Path): Option[(BuildOptions, String)] = {
6059
val printablePath =
6160
if (path.startsWith(Os.pwd)) path.relativeTo(Os.pwd).toString
@@ -65,6 +64,65 @@ case object ScalaPreprocessor extends Preprocessor {
6564
}
6665
def process(content: String, printablePath: String): Option[(BuildOptions, String)] = {
6766

67+
val afterUsing = processUsing(content, printablePath)
68+
val afterProcessImports = processSpecialImports(
69+
afterUsing.map(_._2).getOrElse(content),
70+
printablePath
71+
)
72+
73+
if (afterUsing.isEmpty && afterProcessImports.isEmpty) None
74+
else {
75+
val allOptions = afterUsing.map(_._1).toSeq ++ afterProcessImports.map(_._1).toSeq
76+
val summedOptions = allOptions.foldLeft(BuildOptions())(_ orElse _)
77+
val lastContent = afterProcessImports
78+
.map(_._2)
79+
.orElse(afterUsing.map(_._2))
80+
.getOrElse(content)
81+
Some((summedOptions, lastContent))
82+
}
83+
}
84+
85+
private def processUsing(
86+
content: String,
87+
printablePath: String
88+
): Option[(BuildOptions, String)] = {
89+
90+
val processor = {
91+
val reporter = new DirectivesOutputStreamReporter(System.err) // TODO Get that via a logger
92+
val settings = new Settings
93+
val context = new Context(reporter, settings)
94+
new UsingDirectivesProcessor(context)
95+
}
96+
97+
val contentChars = content.toCharArray
98+
val directives = processor.extract(contentChars)
99+
100+
val updatedOptions = DirectivesProcessor.process(directives.getFlattenedMap.asScala.toMap)
101+
102+
val codeOffset = directives.getCodeOffset()
103+
104+
val updatedContentOpt =
105+
if (codeOffset > 0)
106+
Some {
107+
val headerBytes = contentChars
108+
.iterator
109+
.take(codeOffset)
110+
.map(c => if (c.isControl) c else ' ')
111+
.toArray
112+
val mainBytes = contentChars.drop(codeOffset)
113+
new String(headerBytes ++ mainBytes)
114+
}
115+
else None
116+
117+
if (updatedContentOpt.isEmpty) None
118+
else Some((updatedOptions, updatedContentOpt.getOrElse(content)))
119+
}
120+
121+
private def processSpecialImports(
122+
content: String,
123+
printablePath: String
124+
): Option[(BuildOptions, String)] = {
125+
68126
import fastparse._
69127
import scalaparse._
70128
import scala.build.internal.ScalaParse._
@@ -128,10 +186,16 @@ case object ScalaPreprocessor extends Preprocessor {
128186
val deps = dependencyTrees.map(_.prefix.drop(1).mkString("."))
129187
val options = BuildOptions(
130188
classPathOptions = ClassPathOptions(
131-
extraDependencies = deps.map(ScalaPreprocessor.parseDependency)
189+
extraDependencies = deps.map(parseDependency)
132190
)
133191
)
134192
Some((options, newCode))
135193
}
136194
}
195+
196+
private def parseDependency(str: String): AnyDependency =
197+
DependencyParser.parse(str) match {
198+
case Left(msg) => sys.error(s"Malformed dependency '$str': $msg")
199+
case Right(dep) => dep
200+
}
137201
}

0 commit comments

Comments
 (0)