diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | README.md | 23 | ||||
-rw-r--r-- | examples/build-scalajs/README.md | 12 | ||||
-rw-r--r-- | examples/build-scalajs/build/build.scala | 27 | ||||
-rw-r--r-- | examples/build-scalajs/build/build/build.scala | 11 | ||||
-rw-r--r-- | examples/build-scalajs/js/src/main/scala/App.scala | 22 | ||||
-rw-r--r-- | examples/build-scalajs/js/src/main/scala/Pictures.scala | 105 | ||||
-rw-r--r-- | examples/build-scalajs/server/app.js | 39 | ||||
-rw-r--r-- | examples/build-scalajs/server/package.json | 14 | ||||
-rw-r--r-- | examples/build-scalajs/server/public/index.html | 18 | ||||
-rw-r--r-- | examples/build-scalajs/shared/src/main/scala/prototype/Picture.scala | 3 | ||||
-rw-r--r-- | plugins/scalajs/ScalaJs.scala | 82 | ||||
-rw-r--r-- | plugins/scalajs/ScalaJsInformation.scala | 25 | ||||
-rw-r--r-- | plugins/scalajs/build/build.scala | 9 | ||||
-rw-r--r-- | stage1/Stage1Lib.scala | 2 | ||||
-rw-r--r-- | stage2/BasicBuild.scala | 12 | ||||
-rw-r--r-- | test/simple-fixed/build/build.scala | 2 | ||||
-rw-r--r-- | test/simple/build/build.scala | 2 | ||||
-rw-r--r-- | test/test.scala | 2 |
19 files changed, 402 insertions, 11 deletions
@@ -9,3 +9,6 @@ target/ cache_ .idea realpath/realpath +node_modules +*fastopt* +*fullopt* @@ -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{ |