aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKatrin Shechtman <katrin.shechtman@gmail.com>2016-06-13 01:23:45 -0400
committerKatrin Shechtman <katrin.shechtman@gmail.com>2016-06-13 11:48:05 -0400
commit59c9e1e4bfc45f843f89da22e0190d19403b4dc6 (patch)
treec754e73fa8ac7f7363dd5a66521e17ef1dbab03d
parentb69826c7bc44573f01366ef472a59def6e4f1fc5 (diff)
downloadcbt-59c9e1e4bfc45f843f89da22e0190d19403b4dc6.tar.gz
cbt-59c9e1e4bfc45f843f89da22e0190d19403b4dc6.tar.bz2
cbt-59c9e1e4bfc45f843f89da22e0190d19403b4dc6.zip
scalajs cross project support as plugin
-rw-r--r--.gitignore3
-rw-r--r--README.md23
-rw-r--r--examples/build-scalajs/README.md12
-rw-r--r--examples/build-scalajs/build/build.scala27
-rw-r--r--examples/build-scalajs/build/build/build.scala11
-rw-r--r--examples/build-scalajs/js/src/main/scala/App.scala22
-rw-r--r--examples/build-scalajs/js/src/main/scala/Pictures.scala105
-rw-r--r--examples/build-scalajs/server/app.js39
-rw-r--r--examples/build-scalajs/server/package.json14
-rw-r--r--examples/build-scalajs/server/public/index.html18
-rw-r--r--examples/build-scalajs/shared/src/main/scala/prototype/Picture.scala3
-rw-r--r--plugins/scalajs/ScalaJs.scala82
-rw-r--r--plugins/scalajs/ScalaJsInformation.scala25
-rw-r--r--plugins/scalajs/build/build.scala9
-rw-r--r--stage1/Stage1Lib.scala2
-rw-r--r--stage2/BasicBuild.scala12
-rw-r--r--test/simple-fixed/build/build.scala2
-rw-r--r--test/simple/build/build.scala2
-rw-r--r--test/test.scala2
19 files changed, 402 insertions, 11 deletions
diff --git a/.gitignore b/.gitignore
index 39d6b64..37ff16e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,6 @@ target/
cache_
.idea
realpath/realpath
+node_modules
+*fastopt*
+*fullopt*
diff --git a/README.md b/README.md
index b2df2b7..94b52a8 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,29 @@ Build scripts also have access to a small unsurprising library for
- packaging jars
- signing / publishing to sonatype/maven
+Scala.js support
+----------------
+
+CBT supports cross-project Scala.js build.
+It preserves same structure as in SBT (https://www.scala-js.org/doc/project/cross-build.html)
+
+ 1. Example for user scalajs project is in: `$CBT_HOME/cbt/examples/build-scalajs`
+ 2. `$CBT_HOME/cbt compile`
+ Will compile JVM and JS sources
+ `$CBT_HOME/cbt jsCompile`
+ Will compile JS sources
+ `$CBT_HOME/cbt jvmCompile`
+ Will compile JVM sources
+ 3. `$CBT_HOME/cbt fastOptJS` and `$CBT_HOME/cbt fullOptJS`
+ Same as in Scala.js sbt project
+
+ Note: Scala.js support is under ongoing development.
+
+ Currently missing features:
+ * No support for jsDependencies:
+ It means that all 3rd party dependencies should added manually, see scalajs build example
+ * No support for test
+
Missing features in comparison with SBT
---------------------------------------
diff --git a/examples/build-scalajs/README.md b/examples/build-scalajs/README.md
new file mode 100644
index 0000000..ecc4821
--- /dev/null
+++ b/examples/build-scalajs/README.md
@@ -0,0 +1,12 @@
+
+Compilation instructions
+-------------------------------------------
+1. `cbt compile`
+2. `cbt fastOptJS`
+
+Execution instructions
+-------------------------------------------
+2. `cd server`
+3. `npm install`
+4. `node app.js`
+5. Go to http://localhost:3000 in a browser \ No newline at end of file
diff --git a/examples/build-scalajs/build/build.scala b/examples/build-scalajs/build/build.scala
new file mode 100644
index 0000000..e6c794a
--- /dev/null
+++ b/examples/build-scalajs/build/build.scala
@@ -0,0 +1,27 @@
+import cbt._
+import java.net.URL
+import java.io.File
+import scala.collection.immutable.Seq
+
+class Build( context: Context ) extends BasicBuild( context ) with ScalaJsBuild {
+
+ override val projectName = "my-project"
+
+ override def dependencies = (
+ super.dependencies ++
+ Resolver( mavenCentral ).bind(
+ //"org.scalatest" %%% "scalatest" % "3.0.0-RC2",
+ "com.github.japgolly.scalajs-react" %%% "core" % "0.10.4", // for example
+ // for example if you want explicitely state scala version
+ "org.scala-js" % "scalajs-dom_sjs0.6_2.11" % "0.9.0"
+ )
+ )
+
+ /* ++ some JVM only dependencies */
+ override def jvmDependencies = Seq.empty
+
+ override def fastOptOutput = {
+ projectDirectory.getAbsolutePath + "/server/public/" + new File(super.fastOptOutput).getName
+ }
+}
+
diff --git a/examples/build-scalajs/build/build/build.scala b/examples/build-scalajs/build/build/build.scala
new file mode 100644
index 0000000..ca01ba1
--- /dev/null
+++ b/examples/build-scalajs/build/build/build.scala
@@ -0,0 +1,11 @@
+import cbt._
+import java.net.URL
+import java.io.File
+import scala.collection.immutable.Seq
+
+class Build( context: Context ) extends BuildBuild( context ){
+
+ override def dependencies =
+ super.dependencies :+
+ BuildDependency(new File(context.cbtHome + "/plugins/scalajs"))
+}
diff --git a/examples/build-scalajs/js/src/main/scala/App.scala b/examples/build-scalajs/js/src/main/scala/App.scala
new file mode 100644
index 0000000..2682936
--- /dev/null
+++ b/examples/build-scalajs/js/src/main/scala/App.scala
@@ -0,0 +1,22 @@
+package prototype
+
+import japgolly.scalajs.react.ReactDOM
+import org.scalajs.dom
+
+import scala.scalajs.js
+import scala.scalajs.js.JSApp
+import scala.scalajs.js.annotation.JSExport
+import scalajs.js.Dynamic.{global => g}
+
+/**
+ * Created by katrin on 2016-04-10.
+ */
+@JSExport("App")
+object App extends JSApp {
+
+ def main(): Unit = {
+
+ val doc = dom.document
+ ReactDOM.render(Pictures.PictureComponent(), doc.getElementById("main"))
+ }
+}
diff --git a/examples/build-scalajs/js/src/main/scala/Pictures.scala b/examples/build-scalajs/js/src/main/scala/Pictures.scala
new file mode 100644
index 0000000..88329ee
--- /dev/null
+++ b/examples/build-scalajs/js/src/main/scala/Pictures.scala
@@ -0,0 +1,105 @@
+package prototype
+
+import japgolly.scalajs.react.{ReactComponentB, BackendScope, Callback}
+import org.scalajs.dom
+
+import scala.scalajs._
+import japgolly.scalajs.react.vdom.all._
+
+import scala.scalajs.js.JSON
+
+/**
+ * Created by katrin on 2016-04-10.
+ */
+object Pictures {
+
+ case class State(pictures: List[Picture], favourites: List[Picture])
+
+ type PicClick = (String, Boolean) => Callback
+
+ class Backend($: BackendScope[Unit, State]) {
+
+ def onPicClick(id: String, favorite: Boolean) =
+ $.state flatMap { s =>
+ if (favorite) {
+ val newPics = s.pictures.map(p => if (p.id == id) p.copy(favorite = false) else p)
+ val newFavs = s.favourites.filter(p => p.id != id)
+ $.modState(_ => State(newPics, newFavs))
+ } else {
+ var newPic: Picture = null
+ val newPics = s.pictures.map(p => if (p.id == id) {
+ newPic = p.copy(favorite = true); newPic
+ } else p)
+ val newFavs = s.favourites.+:(newPic)
+ $.modState(_ => State(newPics, newFavs))
+ }
+ }
+
+ def render(s: State) =
+ div(
+ h1("Popular Pixabay Pics"),
+ pictureList((s.pictures, onPicClick)),
+ h1("Your favorites"),
+ favoriteList((s.favourites, onPicClick)))
+ }
+
+ val picture = ReactComponentB[(Picture, PicClick)]("picture")
+ .render_P { case (p, b) =>
+ div(if (p.favorite) cls := "picture favorite" else cls := "picture", onClick --> b(p.id, p.favorite))(
+ img(src := p.src, title := p.title)
+ )
+ }
+ .build
+
+ val pictureList = ReactComponentB[(List[Picture], PicClick)]("pictureList")
+ .render_P { case (list, b) =>
+ div(`class` := "pictures")(
+ if (list.isEmpty) span("Loading Pics..")
+ else {
+ list.map(p => picture.withKey(p.id)((p, b)))
+ }
+ )
+ }
+ .build
+
+ val favoriteList = ReactComponentB[(List[Picture], PicClick)]("favoriteList")
+ .render_P { case (list, b) =>
+ div(`class` := "favorites")(
+ if (list.isEmpty) span("Click an image to mark as favorite")
+ else {
+ list.map(p => picture.withKey(p.id)((p, b)))
+ }
+ )
+ }
+ .build
+
+ val PictureComponent = ReactComponentB[Unit]("PictureComponent")
+ .initialState(State(Nil, Nil))
+ .renderBackend[Backend]
+ .componentDidMount(scope => Callback {
+ import scalajs.js.Dynamic.{global => g}
+ def isDefined(g: js.Dynamic): Boolean =
+ g.asInstanceOf[js.UndefOr[AnyRef]].isDefined
+ val url = "http://localhost:3000/data"
+ val xhr = new dom.XMLHttpRequest()
+ xhr.open("GET", url)
+ xhr.onload = { (e: dom.Event) =>
+ if (xhr.status == 200) {
+ val result = JSON.parse(xhr.responseText)
+ if (isDefined(result) && isDefined(result.hits)) {
+ val hits = result.hits.asInstanceOf[js.Array[js.Dynamic]]
+ val pics = hits.toList.map(item => Picture(
+ item.id.toString,
+ item.pageURL.toString,
+ item.previewURL.toString,
+ if (item.tags != null) item.tags.asInstanceOf[js.Array[String]].mkString(",") else ""))
+ //if (item.caption != null) item.caption.text.toString else ""))
+ scope.modState(_ => State(pics, Nil)).runNow()
+ }
+ }
+ }
+ xhr.send()
+ })
+ .buildU
+
+}
diff --git a/examples/build-scalajs/server/app.js b/examples/build-scalajs/server/app.js
new file mode 100644
index 0000000..620c26a
--- /dev/null
+++ b/examples/build-scalajs/server/app.js
@@ -0,0 +1,39 @@
+var express = require('express');
+var https = require('https');
+
+var app = express();
+
+app.get('/data', function(req, res){
+
+ var request = https.get(
+ //'https://api.instagram.com/v1/media/popular?client_id=642176ece1e7445e99244cec26f4de1f&callback=?',
+ 'https://pixabay.com/api/?key=2741116-9706ac6d4a58f2b5416225505&q=yellow+flowers&image_type=photo',
+ function(response) {
+
+ var body = "";
+ response.on('data', function(data) {
+ body += data;
+ });
+ response.on('end', function() {
+ console.log(body);
+ try {
+ res.send(JSON.parse(body));
+ } catch (e) {
+ return console.error(e);
+ }
+ });
+ });
+ request.on('error', function(e) {
+ console.log('Problem with request: ' + e.message);
+ });
+ request.end();
+});
+
+app.use(express.static(__dirname + '/public'));
+
+var server = app.listen(3000, function () {
+ var host = server.address().address;
+ var port = server.address().port;
+
+ console.log('Example app listening at http://%s:%s', host, port);
+});
diff --git a/examples/build-scalajs/server/package.json b/examples/build-scalajs/server/package.json
new file mode 100644
index 0000000..d20ec98
--- /dev/null
+++ b/examples/build-scalajs/server/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "server",
+ "version": "1.0.0",
+ "description": "prototype",
+ "main": "app.js",
+ "author": "Katrin",
+ "license": "UNLICENSED",
+ "private": true,
+ "dependencies": {
+ "express": "^4.13.3",
+ "express-ws": "^0.2.6",
+ "body-parser": "^1.14.1"
+ }
+}
diff --git a/examples/build-scalajs/server/public/index.html b/examples/build-scalajs/server/public/index.html
new file mode 100644
index 0000000..08de20d
--- /dev/null
+++ b/examples/build-scalajs/server/public/index.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Prototype</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+</head>
+<body>
+
+ <div id="main"></div>
+
+ <script src="https://fb.me/react-15.1.0.min.js"></script>
+ <script src="https://fb.me/react-dom-15.1.0.min.js"></script>
+ <script type="text/javascript" src="./my-project-fastopt.js"></script>
+ <script type="text/javascript">
+ App().main();
+ </script>
+</body>
+</html>
diff --git a/examples/build-scalajs/shared/src/main/scala/prototype/Picture.scala b/examples/build-scalajs/shared/src/main/scala/prototype/Picture.scala
new file mode 100644
index 0000000..dbb985a
--- /dev/null
+++ b/examples/build-scalajs/shared/src/main/scala/prototype/Picture.scala
@@ -0,0 +1,3 @@
+package prototype
+
+case class Picture(id: String, url: String, src: String, title: String, favorite: Boolean = false)
diff --git a/plugins/scalajs/ScalaJs.scala b/plugins/scalajs/ScalaJs.scala
new file mode 100644
index 0000000..bde3307
--- /dev/null
+++ b/plugins/scalajs/ScalaJs.scala
@@ -0,0 +1,82 @@
+import java.io.File
+
+import cbt._
+
+import scala.collection.immutable.Seq
+
+trait ScalaJsSbtDependencyDsl extends SbtDependencyDsl { self: ScalaJsBuild =>
+
+ //Note: We make same assumption about scala version.
+ //In order to be able to choose different scala version, one has to use %.
+ implicit class ScalaJsDependencyBuilder(groupId: String){
+ def %%%(artifactId: String) = new DependencyBuilder2(
+ groupId, artifactId + artifactIdSuffix, Some(scalaMajorVersion))
+ }
+}
+
+trait ScalaJsBuild extends BasicBuild with ScalaJsSbtDependencyDsl with ScalaJsInformation { outer =>
+
+ def sharedFolder = projectDirectory ++ "/shared"
+ def jvmFolder = projectDirectory ++ "/jvm"
+ def jsFolder = projectDirectory ++ "/js"
+
+ private lazy val jvmBuild = new BasicBuild(outer.context){
+ override def sources = Seq(sharedFolder ++ "/src/main/scala", jvmFolder ++ "/src/main/scala")
+ override def target = jvmFolder ++ "/target"
+ override def dependencies = outer.dependencies ++ jvmDependencies
+ }
+ private lazy val jsBuild = new BasicBuild(outer.context){
+ override def sources = Seq(sharedFolder ++ "/src/main/scala", jsFolder ++ "/src/main/scala")
+ override def target = jsFolder ++ "/target"
+ override def dependencies = outer.dependencies :+ scalaJsLibDep
+ override def scalacOptions = super.scalacOptions ++
+ Seq(s"-Xplugin:${scalaJsCompilerDep.jar.getAbsolutePath}", "-Xplugin-require:scalajs")
+ }
+
+ def jvmDependencies = Seq.empty[Dependency]
+ //TODO: implement
+ def jsDependencies = Seq.empty[Dependency]
+ def jvmCompile: Option[File] = jvmBuild.compile
+ def jsCompile: Option[File] = jsBuild.compile
+ override def compile = {
+ jvmCompile
+ jsCompile
+ }
+
+ trait JsOutputMode {
+ def option: String
+ def fileSuffix: String
+ }
+ case object FastOptJS extends JsOutputMode{
+ override val option = "-f"
+ override val fileSuffix = "fastopt"
+ }
+ case object FullOptJS extends JsOutputMode{
+ override val option = "-u"
+ override val fileSuffix = "fullopt"
+ }
+
+ private def output(mode: JsOutputMode) = s"${jsBuild.target.getAbsolutePath}/$projectName-${mode.fileSuffix}.js"
+
+ //TODO: should process all options that Scalajsld recognizes?
+ private def link(mode: JsOutputMode, outputPath: String) = {
+ lib.runMain(
+ "org.scalajs.cli.Scalajsld",
+ Seq(
+ mode.option,
+ "-s",
+ "--stdlib", s"${scalaJsLibDep.jar.getAbsolutePath}",
+ "-o", outputPath,
+ jsBuild.target.getAbsolutePath) ++
+ jsBuild.dependencies.collect{case d: BoundMavenDependency => d.jar.getAbsolutePath},
+ scalaJsCliDep.classLoader(jsBuild.context.classLoaderCache))
+ }
+ def fastOptJS = link(FastOptJS, fastOptOutput)
+ def fullOptJS = link(FullOptJS, fullOptOutput)
+ def fastOptOutput: String = output(FastOptJS)
+ def fullOptOutput: String = output(FullOptJS)
+}
+
+
+
+
diff --git a/plugins/scalajs/ScalaJsInformation.scala b/plugins/scalajs/ScalaJsInformation.scala
new file mode 100644
index 0000000..938d207
--- /dev/null
+++ b/plugins/scalajs/ScalaJsInformation.scala
@@ -0,0 +1,25 @@
+import cbt._
+
+trait ScalaJsInformation extends BasicBuild { outer =>
+
+ val sjsVersion = "0.6.8"
+ final private val sjsMajorVersion: String = lib.libMajorVersion(sjsVersion)
+ final protected val artifactIdSuffix = s"_sjs$sjsMajorVersion"
+
+ final protected val scalaJsCompilerDep =
+ Resolver( mavenCentral ).bindOne(
+ // Has to be full Scala version because the compiler is incompatible between versions
+ MavenDependency("org.scala-js", "scalajs-compiler_2.11.8", sjsVersion)
+ )
+
+ final protected val scalaJsLibDep =
+ Resolver( mavenCentral ).bindOne(
+ ScalaDependency("org.scala-js", "scalajs-library", sjsVersion)
+ )
+
+ final protected val scalaJsCliDep =
+ Resolver( mavenCentral ).bindOne(
+ ScalaDependency("org.scala-js", "scalajs-cli", sjsVersion)
+ )
+}
+
diff --git a/plugins/scalajs/build/build.scala b/plugins/scalajs/build/build.scala
new file mode 100644
index 0000000..9a73704
--- /dev/null
+++ b/plugins/scalajs/build/build.scala
@@ -0,0 +1,9 @@
+import cbt._
+
+class Build(context: Context) extends BasicBuild(context) {
+
+ override def dependencies =
+ super.dependencies :+
+ context.cbtDependency
+}
+
diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala
index 4376c17..ac2c1b1 100644
--- a/stage1/Stage1Lib.scala
+++ b/stage1/Stage1Lib.scala
@@ -39,7 +39,7 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{
lib =>
implicit val implicitLogger: Logger = logger
- def scalaMajorVersion(scalaMinorVersion: String) = scalaMinorVersion.split("\\.").take(2).mkString(".")
+ def libMajorVersion(libFullVersion: String) = libFullVersion.split("\\.").take(2).mkString(".")
// ========== file system / net ==========
diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala
index ee552a5..978f604 100644
--- a/stage2/BasicBuild.scala
+++ b/stage2/BasicBuild.scala
@@ -39,9 +39,10 @@ class BasicBuild(val context: Context) extends DependencyImplementation with Bui
def defaultScalaVersion: String = constants.scalaVersion
final def scalaVersion = context.scalaVersion getOrElse defaultScalaVersion
- final def scalaMajorVersion: String = lib.scalaMajorVersion(scalaVersion)
+ final def scalaMajorVersion: String = lib.libMajorVersion(scalaVersion)
def crossScalaVersions: Seq[String] = Seq(scalaVersion, "2.10.6")
final def crossScalaVersionsArray: Array[String] = crossScalaVersions.to
+ def projectName = "default"
// TODO: this should probably provide a nice error message if class has constructor signature
def copy(context: Context): BuildInterface = lib.copy(this.getClass, context).asInstanceOf[BuildInterface]
@@ -79,17 +80,14 @@ class BasicBuild(val context: Context) extends DependencyImplementation with Bui
/** Absolute path names for all individual files found in sources directly or contained in directories. */
final def sourceFiles: Seq[File] = lib.sourceFiles(sources)
- protected def assertSourceDirectories(): Unit = {
+ protected def logEmptySourceDirectories(): Unit = {
val nonExisting =
sources
.filterNot( _.exists )
.diff( Seq(defaultSourceDirectory) )
- assert(
- nonExisting.isEmpty,
- "Some sources do not exist: \n"++nonExisting.mkString("\n")
- )
+ if(!nonExisting.isEmpty) logger.stage2("Some sources do not exist: \n"++nonExisting.mkString("\n"))
}
- assertSourceDirectories()
+ logEmptySourceDirectories()
def Resolver( urls: URL* ) = MavenResolver( context.cbtHasChanged, context.paths.mavenCache, urls: _* )
diff --git a/test/simple-fixed/build/build.scala b/test/simple-fixed/build/build.scala
index e30b376..f74820a 100644
--- a/test/simple-fixed/build/build.scala
+++ b/test/simple-fixed/build/build.scala
@@ -27,7 +27,7 @@ class Build(context: cbt.Context) extends BasicBuild(context){
).bind(
"org.cvogt" %% "play-json-extensions" % "0.8.0",
"org.tpolecat" %% "tut-core" % "0.4.2",
- "ai.x" %% "lens" % "1.0.0-SNAPSHOT"
+ "ai.x" %% "lens" % "1.0.0"
)
)
}
diff --git a/test/simple/build/build.scala b/test/simple/build/build.scala
index c7eab6a..15b63fb 100644
--- a/test/simple/build/build.scala
+++ b/test/simple/build/build.scala
@@ -34,7 +34,7 @@ class Build(context: cbt.Context) extends BasicBuild(context){
).bind(
"org.cvogt" %% "play-json-extensions" % "0.8.0",
"org.tpolecat" %% "tut-core" % "0.4.2",
- "ai.x" %% "lens" % "1.0.0-SNAPSHOT"
+ "ai.x" %% "lens" % "1.0.0"
)
)
}
diff --git a/test/test.scala b/test/test.scala
index 13fd44f..59684f4 100644
--- a/test/test.scala
+++ b/test/test.scala
@@ -141,7 +141,7 @@ object Main{
++
Dependencies(
Resolver( mavenCentral, sonatypeSnapshots ).bind(
- MavenDependency("ai.x","lens_2.11","1.0.0-SNAPSHOT")
+ MavenDependency("ai.x","lens_2.11","1.0.0")
)
).classpath.strings
).foreach{