Skip to content

Make Scala.js usable for people #9637

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

Merged
merged 4 commits into from
Aug 27, 2020
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
25 changes: 13 additions & 12 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,6 @@ object MyScalaJSPlugin extends AutoPlugin {
override def projectSettings: Seq[Setting[_]] = Def.settings(
commonBootstrappedSettings,

/* Remove the Scala.js compiler plugin for scalac, and enable the
* Scala.js back-end of dotty instead.
*/
libraryDependencies := {
val deps = libraryDependencies.value
deps.filterNot(_.name.startsWith("scalajs-compiler")).map(_.withDottyCompat(scalaVersion.value))
},
scalacOptions += "-scalajs",

// Replace the JVM JUnit dependency by the Scala.js one
libraryDependencies ~= {
_.filter(!_.name.startsWith("junit-interface"))
Expand Down Expand Up @@ -289,8 +280,11 @@ object Build {
Some((packageBin in (`dotty-sbt-bridge`, Compile)).value)
},

// Use the same name as the non-bootstrapped projects for the artifacts
moduleName ~= { _.stripSuffix("-bootstrapped") },
// Use the same name as the non-bootstrapped projects for the artifacts.
// Remove the `js` suffix because JS artifacts are published using their special crossVersion.
// The order of the two `stripSuffix`es is important, so that
// dotty-library-bootstrappedjs becomes dotty-library.
moduleName ~= { _.stripSuffix("js").stripSuffix("-bootstrapped") },

// Enforce that the only Scala 2 classfiles we unpickle come from scala-library
/*
Expand Down Expand Up @@ -778,8 +772,14 @@ object Build {
asDottyLibrary(Bootstrapped).
enablePlugins(MyScalaJSPlugin).
settings(
libraryDependencies +=
("org.scala-js" %% "scalajs-library" % scalaJSVersion).withDottyCompat(scalaVersion.value),
unmanagedSourceDirectories in Compile :=
(unmanagedSourceDirectories in (`dotty-library-bootstrapped`, Compile)).value,

// Make sure `dotty-bootstrapped/test` doesn't fail on this project for no reason
test in Test := {},
testOnly in Test := {},
)

lazy val tastyCoreSettings = Seq(
Expand Down Expand Up @@ -1397,7 +1397,8 @@ object Build {
def asDottyRoot(implicit mode: Mode): Project = project.withCommonSettings.
aggregate(`dotty-interfaces`, dottyLibrary, dottyCompiler, tastyCore, dottyDoc, `dotty-sbt-bridge`).
bootstrappedAggregate(`scala-library`, `scala-compiler`, `scala-reflect`, scalap,
`dotty-language-server`, `dotty-staging`, `dotty-tasty-inspector`, `dotty-tastydoc`).
`dotty-language-server`, `dotty-staging`, `dotty-tasty-inspector`, `dotty-tastydoc`,
`dotty-library-bootstrappedJS`).
dependsOn(tastyCore).
dependsOn(dottyCompiler).
dependsOn(dottyLibrary).
Expand Down
11 changes: 11 additions & 0 deletions sbt-dotty/sbt-test/scalajs/basic/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package test

import scala.scalajs.js
import org.scalajs.dom

object Main {
def main(args: Array[String]): Unit = {
println("Hello Scala.js")
dom.console.log("using a Scala.js dependency")
}
}
8 changes: 8 additions & 0 deletions sbt-dotty/sbt-test/scalajs/basic/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
enablePlugins(ScalaJSPlugin)

scalaVersion := sys.props("plugin.scalaVersion")

// Test withDottyCompat for %%% dependencies
libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "1.1.0").withDottyCompat(scalaVersion.value)

scalaJSUseMainModuleInitializer := true
2 changes: 2 additions & 0 deletions sbt-dotty/sbt-test/scalajs/basic/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version"))
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.1.1")
1 change: 1 addition & 0 deletions sbt-dotty/sbt-test/scalajs/basic/test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
> run
73 changes: 68 additions & 5 deletions sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import scala.util.Properties.isJavaAtLeast
object DottyPlugin extends AutoPlugin {
object autoImport {
val isDotty = settingKey[Boolean]("Is this project compiled with Dotty?")
val isDottyJS = settingKey[Boolean]("Is this project compiled with Dotty and Scala.js?")

// NOTE:
// - this is a def to support `scalaVersion := dottyLatestNightlyBuild`
Expand Down Expand Up @@ -93,7 +94,7 @@ object DottyPlugin extends AutoPlugin {
val name = moduleID.name
if (name != "dotty" && name != "dotty-library" && name != "dotty-compiler")
moduleID.crossVersion match {
case _: librarymanagement.Binary =>
case binary: librarymanagement.Binary =>
val compatVersion =
CrossVersion.partialVersion(scalaVersion) match {
case Some((3, _)) =>
Expand All @@ -107,7 +108,7 @@ object DottyPlugin extends AutoPlugin {
""
}
if (compatVersion.nonEmpty)
moduleID.cross(CrossVersion.constant(compatVersion))
moduleID.cross(CrossVersion.constant(binary.prefix + compatVersion + binary.suffix))
else
moduleID
case _ =>
Expand Down Expand Up @@ -170,6 +171,29 @@ object DottyPlugin extends AutoPlugin {
Seq(
isDotty := scalaVersion.value.startsWith("0.") || scalaVersion.value.startsWith("3."),

/* The way the integration with Scala.js works basically assumes that the settings of ScalaJSPlugin
* will be applied before those of DottyPlugin. It seems to be the case in the tests I did, perhaps
* because ScalaJSPlugin is explicitly enabled, while DottyPlugin is triggered. However, I could
* not find an authoritative source on the topic.
*
* There is an alternative implementation that would not have that assumption: it would be to have
* another DottyJSPlugin, that would be auto-triggered by the presence of *both* DottyPlugin and
* ScalaJSPlugin. That plugin would be guaranteed to have its settings be applied after both of them,
* by the documented rules. However, that would require sbt-dotty to depend on sbt-scalajs to be
* able to refer to ScalaJSPlugin.
*
* When the logic of sbt-dotty moves to sbt itself, the logic specific to the Dotty-Scala.js
* combination will have to move to sbt-scalajs. Doing so currently wouldn't work since we
* observe that the settings of DottyPlugin are applied after ScalaJSPlugin, so ScalaJSPlugin
* wouldn't be able to fix up things like the dependency on dotty-library.
*/
isDottyJS := {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way this works basically assumes that the settings of ScalaJSPlugin will be applied before those of DottyPlugin. It seems to be the case in the tests I did, perhaps because ScalaJSPlugin is explicitly enabled, while DottyPlugin is triggered. However, I could not find an authoritative source on the topic.

I have an alternative implementation in mind, that would not have that assumption: it would be to have another DottyJSPlugin, that would be auto-triggered by the presence of DottyPlugin and ScalaJSPlugin. That plugin would be guaranteed to have its settings be applied after both of them, by the documented rules. However, that would require sbt-dotty to depend on sbt-scalajs to be able to refer to ScalaJSPlugin.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully in the not-too-distant future sbt-dotty will be obsoleted by sbt supporting dotty natively, so we need to plan for that too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When that happens, the Scala.js-Dotty combination will have to be handled by sbt-scalajs. Currently though, since apparently DottyPlugin gets applied after ScalaJSPlugin, there's nothing sbt-scalajs can do, AFAICT.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When that happens, the Scala.js-Dotty combination will have to be handled by sbt-scalajs.

OK, can you add a comment mentioning this around here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

isDotty.value && (crossVersion.value match {
case binary: librarymanagement.Binary => binary.prefix.contains("sjs1_")
case _ => false
})
},

scalaOrganization := {
if (isDotty.value)
"ch.epfl.lamp"
Expand Down Expand Up @@ -317,12 +341,51 @@ object DottyPlugin extends AutoPlugin {

// Because managedScalaInstance is false, sbt won't add the standard library to our dependencies for us
libraryDependencies ++= {
if (isDotty.value && autoScalaLibrary.value)
Seq(scalaOrganization.value %% "dotty-library" % scalaVersion.value)
else
if (isDotty.value && autoScalaLibrary.value) {
val name =
if (isDottyJS.value) "dotty-library_sjs1"
else "dotty-library"
Seq(scalaOrganization.value %% name % scalaVersion.value)
} else
Seq()
},

// Patch up some more options if this is Dotty with Scala.js
scalacOptions := {
val prev = scalacOptions.value
/* The `&& !prev.contains("-scalajs")` is future-proof, for when sbt-scalajs adds that
* option itself but sbt-dotty is still required for the other Dotty-related stuff.
*/
if (isDottyJS.value && !prev.contains("-scalajs")) prev :+ "-scalajs"
else prev
},
libraryDependencies := {
val prev = libraryDependencies.value
if (!isDottyJS.value) {
prev
} else {
prev
/* Remove the dependencies we don't want:
* * We don't want scalajs-library, because we need the one that comes
* as a dependency of dotty-library_sjs1
* * We don't want scalajs-compiler, because that's a compiler plugin,
* which is replaced by the `-scalajs` flag in dotc.
*/
.filterNot { moduleID =>
moduleID.organization == "org.scala-js" && (
moduleID.name == "scalajs-library" || moduleID.name == "scalajs-compiler"
)
}
// Apply withDottyCompat to the dependency on scalajs-test-bridge
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this applies transitively to scalajs-test-interface?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because the dependency on scalajs-test-interface will be lookup up in its POM file. And in the POM file, this has already been hard-wired to scalajs-test-interface_2.13.

.map { moduleID =>
if (moduleID.organization == "org.scala-js" && moduleID.name == "scalajs-test-bridge")
moduleID.withDottyCompat(scalaVersion.value)
else
moduleID
}
}
},

// Turns off the warning:
// [warn] Binary version (0.9.0-RC1) for dependency ...;0.9.0-RC1
// [warn] in ... differs from Scala binary version in project (0.9).
Expand Down