-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Golem Scala plugin and exporter macro (#1)
* Add modules and settings * Implement worker macro * Implement golem scala plugin * Add example1 test * Add example2 test * Run SBT tests on CI * Add JvmPlugin requirement * Add README * Add JDK 11 LTS on CI
- Loading branch information
1 parent
49a1b62
commit 6a0d0d2
Showing
18 changed files
with
286 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
golem-scala | ||
=========== | ||
|
||
Avoid any boilerplate in your project by using just one annotation to export your Golem worker from Scala to JS. | ||
|
||
Setup | ||
----- | ||
|
||
Add golem-scala as a dependency in `project/plugins.sbt`: | ||
|
||
```scala | ||
addSbtPlugin("cloud.golem" % "golem-scala" % "x.y.z") | ||
``` | ||
|
||
Usage | ||
----- | ||
|
||
Golem-scala is automatically loaded, it just needs to be enabled with `enablePlugins(GolemScalaPlugin)` in your `build.sbt`: | ||
|
||
```scala | ||
ThisBuild / version := "0.1.0-SNAPSHOT" | ||
ThisBuild / scalaVersion := "2.13.13" | ||
|
||
lazy val root = (project in file(".")) | ||
.enablePlugins(GolemScalaPlugin) | ||
``` | ||
|
||
Then you will be able to annotate your Golem worker object with the `@cloud.golem.Worker` annotation: | ||
|
||
```scala | ||
package example | ||
|
||
@cloud.golem.Worker | ||
object ShoppingCart { self => | ||
|
||
def initializeCart(userId: String): String = { | ||
println(s"Initializing cart for user $userId") | ||
if (math.random() > 0.1) userId | ||
else "Error while initializing cart" | ||
} | ||
|
||
// ... | ||
|
||
} | ||
|
||
``` | ||
|
||
Once done that, it will be enough to run `sbt fullLinkJS` and the plugin will take care of exporting your worker in JS. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,22 @@ | ||
import Settings.* | ||
|
||
ThisBuild / scalaVersion := Versions.scala2_12 | ||
ThisBuild / organization := "cloud.golem" | ||
|
||
lazy val root = (project in file(".")) | ||
.settings( | ||
name := "golem-scala", | ||
addSbtPlugin("org.scala-js" % "sbt-scalajs" % Versions.scalaJS) | ||
) | ||
.settings(scriptedLaunchOpts += s"-Dplugin.version=${version.value}") | ||
.enablePlugins(SbtPlugin) | ||
.dependsOn(macros) | ||
.aggregate(macros) | ||
|
||
lazy val macros = project | ||
.settings( | ||
name := "golem-scala-macros", | ||
crossScalaVersions += Versions.scala2_13, | ||
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, | ||
) | ||
.macroParadiseSettings |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package cloud.golem | ||
|
||
import scala.annotation.{StaticAnnotation, compileTimeOnly} | ||
import scala.language.experimental.macros | ||
import scala.reflect.macros.whitebox | ||
|
||
@compileTimeOnly("Enable macro paradise to expand macro annotations") | ||
final class Worker extends StaticAnnotation { | ||
def macroTransform(annottees: Any*): Any = macro WorkerExport.impl | ||
} | ||
|
||
object WorkerExport { | ||
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { | ||
import c.universe._ | ||
|
||
val result = annottees.head.tree match { | ||
case q"$mods object $name extends $parent { ..$self => ..$stats }" => | ||
val topLevelName = { | ||
val parentFullName = parent.toString | ||
// Define export top level as superclass name or "api", if missing | ||
var n = | ||
parentFullName.split("\\.").lastOption.getOrElse(parentFullName) | ||
n = n.replaceAll("\\$", "") | ||
n = if (n == "AnyRef") "api" else n | ||
n.toLowerCase | ||
} | ||
c.info( | ||
NoPosition, | ||
s"Exporting worker object $name to $topLevelName", | ||
force = false | ||
) | ||
val newMods = Modifiers( | ||
mods.flags | Flag.FINAL, | ||
mods.privateWithin, | ||
mods.annotations :+ q"new scala.scalajs.js.annotation.JSExportAll" :+ q"new scala.scalajs.js.annotation.JSExportTopLevel(${s"$topLevelName"})" | ||
) | ||
q""" | ||
$newMods object $name extends $parent { $self => | ||
..$stats | ||
} | ||
""" | ||
|
||
case _ => | ||
c.abort(c.enclosingPosition, "Failed to export worker object") | ||
} | ||
|
||
c.Expr[Any](result) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import sbt.* | ||
import sbt.Keys.* | ||
|
||
object Settings { | ||
|
||
implicit final class ProjectSettings(project: sbt.Project) { | ||
|
||
def macroParadiseSettings: sbt.Project = | ||
project.settings( | ||
scalacOptions ++= { | ||
if (scalaVersion.value.startsWith("2.13")) Seq("-Ymacro-annotations") | ||
else Nil | ||
}, | ||
libraryDependencies ++= { | ||
if (scalaVersion.value.startsWith("2.12")) { | ||
Seq( | ||
compilerPlugin( | ||
"org.scalamacros" % "paradise" % Versions.scalaMacrosParadise cross CrossVersion.full | ||
) | ||
) | ||
} else Nil | ||
} | ||
) | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
object Versions { | ||
val scala2_12 = "2.12.19" | ||
val scala2_13 = "2.13.13" | ||
val scalaMacrosParadise = "2.1.1" | ||
val scalaJS = "1.14.0" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package cloud.golem | ||
|
||
import sbt.* | ||
import sbt.Keys.* | ||
import org.scalajs.sbtplugin.ScalaJSPlugin | ||
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.* | ||
import sbt.plugins.JvmPlugin | ||
|
||
object GolemScalaPlugin extends AutoPlugin { | ||
private object Versions { | ||
val macros = "0.1.0" | ||
val scalaMacrosParadise = "2.1.1" | ||
} | ||
|
||
override def trigger: PluginTrigger = allRequirements | ||
|
||
override def requires: Plugins = JvmPlugin && ScalaJSPlugin | ||
|
||
override lazy val projectSettings: Seq[Setting[?]] = Seq( | ||
scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) }, | ||
libraryDependencies += "cloud.golem" %% "golem-scala-macros" % Versions.macros | ||
) ++ macroParadiseSettings | ||
|
||
private lazy val macroParadiseSettings = Seq( | ||
scalacOptions ++= { | ||
if (scalaVersion.value.startsWith("2.13")) Seq("-Ymacro-annotations") | ||
else Nil | ||
}, | ||
libraryDependencies ++= { | ||
if (scalaVersion.value.startsWith("2.12")) { | ||
Seq( | ||
compilerPlugin( | ||
"org.scalamacros" % "paradise" % Versions.scalaMacrosParadise cross CrossVersion.full | ||
) | ||
) | ||
} else Nil | ||
} | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
ThisBuild / version := "0.1" | ||
ThisBuild / scalaVersion := "2.13.13" | ||
ThisBuild / crossScalaVersions += "2.12.19" | ||
|
||
lazy val root = (project in file(".")) | ||
.enablePlugins(GolemScalaPlugin) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sbt.version=1.9.9 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
sys.props.get("plugin.version") match { | ||
case Some(version) => addSbtPlugin("cloud.golem" % "golem-scala" % version) | ||
case _ => | ||
sys.error("""|The system property 'plugin.version' is not defined. | ||
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin) | ||
} |
12 changes: 12 additions & 0 deletions
12
src/sbt-test/golem-scala/example1/src/main/scala/example/ShoppingCart.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package example | ||
|
||
@cloud.golem.Worker | ||
object ShoppingCart { self => | ||
|
||
def initializeCart(userId: String): String = { | ||
println(s"Initializing cart for user $userId") | ||
if (math.random() > 0.1) userId | ||
else "Error while initializing cart" | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
> +clean | ||
> +fullLinkJS | ||
$ exists target/scala-2.12/root-opt/main.js | ||
$ exists target/scala-2.12/root-opt/main.js.map | ||
$ exists target/scala-2.13/root-opt/main.js | ||
$ exists target/scala-2.13/root-opt/main.js.map | ||
> +clean | ||
> +fastLinkJS | ||
$ exists target/scala-2.12/root-fastopt/main.js | ||
$ exists target/scala-2.12/root-fastopt/main.js.map | ||
$ exists target/scala-2.13/root-fastopt/main.js | ||
$ exists target/scala-2.13/root-fastopt/main.js.map |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
ThisBuild / version := "0.1" | ||
ThisBuild / scalaVersion := "2.13.13" | ||
ThisBuild / crossScalaVersions += "2.12.19" | ||
|
||
lazy val root = (project in file(".")) | ||
.enablePlugins(GolemScalaPlugin) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sbt.version=1.9.9 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
sys.props.get("plugin.version") match { | ||
case Some(version) => addSbtPlugin("cloud.golem" % "golem-scala" % version) | ||
case _ => | ||
sys.error("""|The system property 'plugin.version' is not defined. | ||
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin) | ||
} |
17 changes: 17 additions & 0 deletions
17
src/sbt-test/golem-scala/example2/src/main/scala/example/Api.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package example | ||
|
||
trait Api { | ||
import scala.scalajs.js | ||
import scala.scalajs.js.JSConverters._ | ||
|
||
type WitResult[+Ok, +Err] = Ok | ||
object WitResult { | ||
def ok[Ok](value: Ok): WitResult[Ok, Nothing] = value | ||
|
||
def err[Err](value: Err): WitResult[Nothing, Err] = throw js.JavaScriptException(value) | ||
|
||
val unit: WitResult[Unit, Nothing] = () | ||
} | ||
|
||
def initializeCart(userId: String): WitResult[String, String] | ||
} |
12 changes: 12 additions & 0 deletions
12
src/sbt-test/golem-scala/example2/src/main/scala/example/ShoppingCart.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package example | ||
|
||
@cloud.golem.Worker | ||
object ShoppingCart extends Api { self => | ||
|
||
def initializeCart(userId: String): WitResult[String, String] = { | ||
println(s"Initializing cart for user $userId") | ||
if (math.random() > 0.1) WitResult.ok(userId) | ||
else WitResult.err("Error while initializing cart") | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
> +clean | ||
> +fullLinkJS | ||
$ exists target/scala-2.12/root-opt/main.js | ||
$ exists target/scala-2.12/root-opt/main.js.map | ||
$ exists target/scala-2.13/root-opt/main.js | ||
$ exists target/scala-2.13/root-opt/main.js.map | ||
> +clean | ||
> +fastLinkJS | ||
$ exists target/scala-2.12/root-fastopt/main.js | ||
$ exists target/scala-2.12/root-fastopt/main.js.map | ||
$ exists target/scala-2.13/root-fastopt/main.js | ||
$ exists target/scala-2.13/root-fastopt/main.js.map |