Skip to content

Commit e519e47

Browse files
committed
Fix inheritance of compiler plug-ins
- ScalaCompiler: Include compiler plug-ins defined on platform module - Coursier: Only return artefact path if requested version matches Closes #16.
1 parent e85a3d0 commit e519e47

File tree

12 files changed

+277
-31
lines changed

12 files changed

+277
-31
lines changed

src/main/scala/seed/artefact/ArtefactResolution.scala

+25-8
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,11 @@ object ArtefactResolution {
105105
def compilerDeps(build: Build, module: Module): List[Set[JavaDep]] = {
106106
def f(build: Build, module: Module, platform: Platform): Set[JavaDep] = {
107107
import build.project.scalaOrganisation
108+
val platformModule = BuildConfig.platformModule(module, platform)
109+
108110
val platformVer = BuildConfig.platformVersion(build, module, platform)
109-
val compilerVer = BuildConfig.scalaVersion(build.project, List(module))
111+
val compilerVer = BuildConfig.scalaVersion(build.project,
112+
platformModule.toList :+ module)
110113

111114
val scalaDeps = Set(
112115
Artefact.scalaCompiler(scalaOrganisation) -> compilerVer,
@@ -123,15 +126,15 @@ object ArtefactResolution {
123126
scalaDeps.map { case (artefact, version) =>
124127
javaDepFromArtefact(artefact, version, platform, platformVer,
125128
compilerVer)
126-
} ++ module.compilerDeps.map(dep =>
127-
javaDepFromScalaDep(dep, platform, platformVer, compilerVer))
129+
} ++ {
130+
val dependencies =
131+
module.compilerDeps ++ platformModule.toList.flatMap(_.compilerDeps)
132+
mergeDeps(dependencies).map(dep =>
133+
javaDepFromScalaDep(dep, platform, platformVer, compilerVer))
134+
}
128135
}
129136

130-
module.targets.map(target =>
131-
f(build,
132-
BuildConfig.platformModule(module, target).getOrElse(module),
133-
target)
134-
).filter(_.nonEmpty)
137+
module.targets.map(target => f(build, module, target)).filter(_.nonEmpty)
135138
}
136139

137140
def allCompilerDeps(build: Build): List[Set[JavaDep]] =
@@ -278,4 +281,18 @@ object ArtefactResolution {
278281

279282
(resolvedCachePath, platformResolution, compilerResolution)
280283
}
284+
285+
/** @return If there are two dependencies with the same organisation and
286+
* artefact name, only retain the last one, regardless of the version
287+
* number.
288+
*/
289+
def mergeDeps[T <: Dep](deps: List[T]): List[T] =
290+
deps.foldLeft(List[T]()) { case (acc, cur) =>
291+
acc.find(
292+
d => d.organisation == cur.organisation && d.artefact == cur.artefact
293+
) match {
294+
case None => acc :+ cur
295+
case Some(previous) => acc.diff(List(previous)) :+ cur
296+
}
297+
}
281298
}

src/main/scala/seed/artefact/Coursier.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ object Coursier {
209209
platformVersion, compilerVersion))
210210

211211
result.resolution.dependencyArtifacts().find { case (dep, attr, art) =>
212-
dep.module.name.value == name
212+
dep.module.name.value == name && dep.version == version
213213
}.map(a => result.artefacts(a._3.url).toPath)
214214
}
215215
}

src/main/scala/seed/generation/util/ScalaCompiler.scala

+20-12
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@ import java.nio.file.Path
44

55
import seed.artefact.Coursier
66
import seed.config.BuildConfig
7+
import seed.artefact.ArtefactResolution
78
import seed.model.Build.Module
89
import seed.model.Platform.{JavaScript, Native}
910
import seed.model.{Artefact, Build, Platform}
1011

1112
object ScalaCompiler {
12-
def resolveCompiler(build: Build,
13-
module: Module,
14-
compilerResolution: List[Coursier.ResolutionResult],
13+
def resolveCompiler(compilerResolution: List[Coursier.ResolutionResult],
1514
artefact: Artefact,
15+
artefactVersion: String,
1616
platform: Platform,
17+
platformVer: String,
1718
compilerVer: String
1819
): Path = {
19-
val platformVer = BuildConfig.platformVersion(build, module, platform)
20-
2120
compilerResolution.iterator.flatMap(resolution =>
2221
Coursier.artefactPath(resolution, artefact, platform,
23-
platformVer, compilerVer, platformVer)
22+
platformVer, compilerVer, artefactVersion)
2423
).toList.headOption.getOrElse(
2524
throw new Exception(s"Artefact '$artefact' missing"))
2625
}
@@ -30,16 +29,25 @@ object ScalaCompiler {
3029
compilerResolution: List[Coursier.ResolutionResult],
3130
platform: Platform,
3231
compilerVer: String): List[String] = {
32+
import ArtefactResolution.mergeDeps
33+
34+
val platformVer = BuildConfig.platformVersion(build, module, platform)
3335
val moduleDeps = BuildConfig.collectModuleDeps(build, module, platform)
3436
val modules = moduleDeps.map(build.module) :+ module
3537
val artefacts =
36-
(if (platform == JavaScript) List(Artefact.ScalaJsCompiler)
37-
else if (platform == Native) List(Artefact.ScalaNativePlugin)
38+
(if (platform == JavaScript) List(Artefact.ScalaJsCompiler -> platformVer)
39+
else if (platform == Native) List(Artefact.ScalaNativePlugin -> platformVer)
3840
else List()
39-
) ++ modules.flatMap(_.compilerDeps.map(Artefact.fromDep))
41+
) ++ mergeDeps(modules.flatMap { m =>
42+
val platformModule = BuildConfig.platformModule(m, platform)
43+
val deps =
44+
m.compilerDeps ++ platformModule.toList.flatMap(_.compilerDeps)
45+
mergeDeps(deps)
46+
}).map(d => Artefact.fromDep(d) -> d.version)
4047

41-
artefacts.map(a => resolveCompiler(
42-
build, module, compilerResolution, a, platform, compilerVer)
43-
).map(p => "-Xplugin:" + p)
48+
artefacts.map { case (artefact, version) =>
49+
resolveCompiler(compilerResolution, artefact, version, platform,
50+
platformVer, compilerVer)
51+
}.map(p => "-Xplugin:" + p)
4452
}
4553
}

src/test/scala/seed/artefact/ArtefactResolutionSpec.scala

+41-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ object ArtefactResolutionSpec extends SimpleTestSuite {
5353
JavaDep("net.java.dev.jna", "jna", "4.5.1")))
5454
}
5555

56-
test("compilerDeps()") {
56+
test("Inherit compiler dependencies") {
5757
val build = Build(
5858
project = Project("2.12.8", scalaJsVersion = Some("0.6.26")),
5959
module = Map())
@@ -80,6 +80,46 @@ object ArtefactResolutionSpec extends SimpleTestSuite {
8080
JavaDep("org.scala-lang", "scala-library", "2.12.8"),
8181
JavaDep("org.scala-lang", "scala-reflect", "2.12.8"),
8282
JavaDep("org.scala-js", "scalajs-compiler_2.12.8", "0.6.26"),
83+
JavaDep("org.scalamacros", "paradise_2.12.8", "2.1.1"),
8384
JavaDep("com.softwaremill.clippy", "plugin_2.12", "0.6.0"))))
8485
}
86+
87+
test("Compiler dependencies with multiple versions") {
88+
val build = Build(
89+
project = Project("2.12.8", scalaJsVersion = Some("0.6.26")),
90+
module = Map())
91+
val module = Module(
92+
targets = List(JVM, JavaScript),
93+
compilerDeps = List(
94+
ScalaDep("org.scalamacros", "paradise", "2.1.0", VersionTag.Full)),
95+
js = Some(Module(
96+
compilerDeps = List(
97+
ScalaDep("org.scalamacros", "paradise", "2.1.1", VersionTag.Full)))))
98+
val deps = ArtefactResolution.compilerDeps(build, module)
99+
100+
assertEquals(deps,
101+
List(
102+
Set(
103+
JavaDep("org.scala-lang", "scala-compiler", "2.12.8"),
104+
JavaDep("org.scala-lang", "scala-library", "2.12.8"),
105+
JavaDep("org.scala-lang", "scala-reflect", "2.12.8"),
106+
JavaDep("org.scalamacros", "paradise_2.12.8", "2.1.0")),
107+
Set(
108+
JavaDep("org.scala-lang", "scala-compiler", "2.12.8"),
109+
JavaDep("org.scala-lang", "scala-library", "2.12.8"),
110+
JavaDep("org.scala-lang", "scala-reflect", "2.12.8"),
111+
JavaDep("org.scala-js", "scalajs-compiler_2.12.8", "0.6.26"),
112+
JavaDep("org.scalamacros", "paradise_2.12.8", "2.1.1"))))
113+
}
114+
115+
test("mergeDeps()") {
116+
val deps =
117+
List(
118+
ScalaDep("org.scalamacros", "paradise", "2.1.0", VersionTag.Full),
119+
ScalaDep("org.scalamacros", "paradise", "2.1.1", VersionTag.Full))
120+
121+
assertEquals(
122+
ArtefactResolution.mergeDeps(deps),
123+
List(ScalaDep("org.scalamacros", "paradise", "2.1.1", VersionTag.Full)))
124+
}
85125
}

src/test/scala/seed/artefact/CoursierSpec.scala

+18-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package seed.artefact
22

33
import minitest.SimpleTestSuite
44
import seed.Log
5-
import seed.model.Build
5+
import seed.model.{Artefact, Build, Platform}
66
import seed.model.Build.JavaDep
77

88
object CoursierSpec extends SimpleTestSuite {
9+
var resolution: Coursier.ResolutionResult = _
10+
911
test("Resolve dependency") {
1012
val dep = JavaDep("org.scala-js", "scalajs-dom_sjs0.6_2.12", "0.9.6")
11-
val resolution = Coursier.resolveAndDownload(Set(dep),
13+
resolution = Coursier.resolveAndDownload(Set(dep),
1214
Build.Resolvers(), Coursier.DefaultIvyPath,
1315
Coursier.DefaultCachePath, optionalArtefacts = true, silent = true,
1416
Log.urgent)
@@ -19,4 +21,18 @@ object CoursierSpec extends SimpleTestSuite {
1921
result.exists(_.javaDocJar.nonEmpty) &&
2022
result.exists(_.sourcesJar.nonEmpty))
2123
}
24+
25+
test("Get artefact path") {
26+
val artefact = Artefact("org.scala-js", "scalajs-dom_sjs0.6_2.12", None)
27+
val version = "0.9.6"
28+
29+
val path = Coursier.artefactPath(resolution, artefact, Platform.JavaScript,
30+
"0.6.26", "2.12.8", version)
31+
assert(path.exists(_.toString.endsWith("scalajs-dom_sjs0.6_2.12-0.9.6.jar")))
32+
33+
// Specify unresolved version
34+
val path2 = Coursier.artefactPath(resolution, artefact, Platform.JavaScript,
35+
"0.6.26", "2.12.8", "0.0.1")
36+
assert(path2.isEmpty)
37+
}
2238
}

src/test/scala/seed/generation/BloopIntegrationSpec.scala

+63-7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ object BloopIntegrationSpec extends TestSuite[Unit] {
2525
override def setup(): Unit = ()
2626
override def tearDown(env: Unit): Unit = ()
2727

28+
def readBloopJson(path: Path): bloop.config.Config.File = {
29+
val content = FileUtils.readFileToString(path.toFile, "UTF-8")
30+
31+
import io.circe.parser._
32+
decode[bloop.config.Config.File](content)(
33+
ConfigEncoderDecoders.allDecoder
34+
).right.get
35+
}
36+
2837
def compileAndRun(projectPath: Path) = {
2938
def compile =
3039
TestProcessHelper.runBloop(projectPath)("compile", "example").map { x =>
@@ -47,7 +56,7 @@ object BloopIntegrationSpec extends TestSuite[Unit] {
4756
compileAndRun(projectPath)
4857
}
4958

50-
testAsync("Build project with compiler plug-in") { _ =>
59+
testAsync("Build project with compiler plug-in defined on cross-platform module") { _ =>
5160
val BuildConfig.Result(build, projectPath, _) = BuildConfig.load(
5261
Paths.get("test/example-paradise"), Log.urgent).get
5362
val buildPath = projectPath.resolve("build")
@@ -59,6 +68,58 @@ object BloopIntegrationSpec extends TestSuite[Unit] {
5968
compileAndRun(projectPath)
6069
}
6170

71+
testAsync("Build project with compiler plug-in defined on platform modules") { _ =>
72+
val BuildConfig.Result(build, projectPath, _) = BuildConfig.load(
73+
Paths.get("test/example-paradise-platform"), Log.urgent).get
74+
val buildPath = projectPath.resolve("build")
75+
if (Files.exists(buildPath)) FileUtils.deleteDirectory(buildPath.toFile)
76+
val packageConfig = PackageConfig(tmpfs = false, silent = false,
77+
ivyPath = None, cachePath = None)
78+
cli.Generate.ui(Config(), projectPath, build, Command.Bloop(packageConfig),
79+
Log.urgent)
80+
compileAndRun(projectPath)
81+
}
82+
83+
testAsync("Build project with overridden compiler plug-in version") { _ =>
84+
val projectPath = Paths.get("test/example-paradise-versions")
85+
val BuildConfig.Result(build, _, _) =
86+
BuildConfig.load(projectPath, Log.urgent).get
87+
val buildPath = projectPath.resolve("build")
88+
if (Files.exists(buildPath)) FileUtils.deleteDirectory(buildPath.toFile)
89+
val packageConfig = PackageConfig(tmpfs = false, silent = false,
90+
ivyPath = None, cachePath = None)
91+
cli.Generate.ui(Config(), projectPath, build, Command.Bloop(packageConfig),
92+
Log.urgent)
93+
94+
val bloopPath = projectPath.resolve(".bloop")
95+
96+
val macrosJvm = readBloopJson(bloopPath.resolve("macros-jvm.json"))
97+
val macrosJs = readBloopJson(bloopPath.resolve("macros-js.json"))
98+
val exampleJvm = readBloopJson(bloopPath.resolve("example-jvm.json"))
99+
val exampleJs = readBloopJson(bloopPath.resolve("example-js.json"))
100+
101+
def getFileName(path: String): String = path.drop(path.lastIndexOf('/') + 1)
102+
103+
assertEquals(
104+
macrosJvm.project.scala.get.options.filter(_.contains("paradise"))
105+
.map(getFileName),
106+
List("paradise_2.11.12-2.1.0.jar"))
107+
assertEquals(
108+
macrosJs.project.scala.get.options.filter(_.contains("paradise"))
109+
.map(getFileName),
110+
List("paradise_2.11.12-2.1.1.jar"))
111+
assertEquals(
112+
exampleJvm.project.scala.get.options.filter(_.contains("paradise"))
113+
.map(getFileName),
114+
List("paradise_2.11.12-2.1.0.jar"))
115+
assertEquals(
116+
exampleJs.project.scala.get.options.filter(_.contains("paradise"))
117+
.map(getFileName),
118+
List("paradise_2.11.12-2.1.1.jar"))
119+
120+
compileAndRun(projectPath)
121+
}
122+
62123
testAsync("Build modules with different Scala versions") { _ =>
63124
val BuildConfig.Result(build, projectPath, _) = BuildConfig.load(
64125
Paths.get("test/multiple-scala-versions"), Log.urgent).get
@@ -120,12 +181,7 @@ object BloopIntegrationSpec extends TestSuite[Unit] {
120181
testAsync("Build project with custom command target") { _ =>
121182
buildCustomTarget("custom-command-target").map { _ =>
122183
val path = Paths.get(s"test/custom-command-target/.bloop/demo.json")
123-
val content = FileUtils.readFileToString(path.toFile, "UTF-8")
124-
125-
import io.circe.parser._
126-
val result = decode[bloop.config.Config.File](content)(
127-
ConfigEncoderDecoders.allDecoder
128-
).right.get
184+
val result = readBloopJson(path)
129185

130186
// Should not include "utils" dependency since it does not have any
131187
// Scala sources and no Bloop module.
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# See https://github.com/tindzk/seed/issues/16
2+
[project]
3+
scalaVersion = "2.11.12"
4+
scalaJsVersion = "0.6.26"
5+
scalaOptions = ["-encoding", "UTF-8", "-unchecked", "-deprecation", "-Xfuture"]
6+
7+
[module.macros]
8+
root = "macros"
9+
sources = ["macros"]
10+
targets = ["jvm", "js"]
11+
12+
[module.macros.jvm]
13+
compilerDeps = [
14+
["org.scalamacros", "paradise", "2.1.1", "full"]
15+
]
16+
17+
[module.macros.js]
18+
compilerDeps = [
19+
["org.scalamacros", "paradise", "2.1.1", "full"]
20+
]
21+
22+
[module.example]
23+
root = "example"
24+
sources = ["example"]
25+
targets = ["jvm", "js"]
26+
moduleDeps = ["macros"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@hello
2+
object Test extends App {
3+
println(this.hello)
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import scala.reflect.macros.blackbox
2+
import scala.language.experimental.macros
3+
import scala.annotation.StaticAnnotation
4+
5+
object helloMacro {
6+
def impl(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
7+
import c.universe._
8+
import Flag._
9+
val result = {
10+
annottees.map(_.tree).toList match {
11+
case q"object $name extends ..$parents { ..$body }" :: Nil =>
12+
q"""
13+
object $name extends ..$parents {
14+
def hello: ${typeOf[String]} = "hello"
15+
..$body
16+
}
17+
"""
18+
}
19+
}
20+
c.Expr[Any](result)
21+
}
22+
}
23+
24+
class hello extends StaticAnnotation {
25+
def macroTransform(annottees: Any*): Any = macro helloMacro.impl
26+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[project]
2+
scalaVersion = "2.11.12"
3+
scalaJsVersion = "0.6.26"
4+
scalaOptions = ["-encoding", "UTF-8", "-unchecked", "-deprecation", "-Xfuture"]
5+
6+
[module.macros]
7+
root = "macros"
8+
sources = ["macros"]
9+
targets = ["jvm", "js"]
10+
compilerDeps = [
11+
["org.scalamacros", "paradise", "2.1.0", "full"]
12+
]
13+
14+
[module.macros.js]
15+
compilerDeps = [
16+
["org.scalamacros", "paradise", "2.1.1", "full"]
17+
]
18+
19+
[module.example]
20+
root = "example"
21+
sources = ["example"]
22+
targets = ["jvm", "js"]
23+
moduleDeps = ["macros"]

0 commit comments

Comments
 (0)