2

I have multiple subprojects in my sbt, one being a server (base upon playframework) the other being the clientside (scala.js) and the third one being the communication between the two in form of protobuf (scalapb).

Now, this is my build.sbt:

lazy val generalSettings = Seq(
  organization := "tld.awesomeness",
  version := "0.0.1",
  scalaVersion := "2.12.1"
)

val CrossDependencies = new
  {
    val scalaTest = "org.scalatest" %% "scalatest" % "3.0.1" % "test"
    val scalactic = "org.scalactic" %% "scalactic" % "3.0.1"
    val scalaTags = "com.lihaoyi" %% "scalatags" % "0.6.2"
  }

lazy val proto = (project in file("modules/proto"))
  .settings(generalSettings: _*)
  .settings(
    PB.targets in Compile := Seq(
      scalapb.gen() -> (sourceManaged in Compile).value
    ),
    // If you need scalapb/scalapb.proto or anything from google/protobuf/*.proto
    libraryDependencies ++= Seq(
      "com.trueaccord.scalapb" %% "scalapb-runtime" % com.trueaccord.scalapb.compiler.Version.scalapbVersion % "protobuf",
      "com.trueaccord.scalapb" %%% "scalapb-runtime" % com.trueaccord.scalapb.compiler.Version.scalapbVersion,
      "com.trueaccord.scalapb" %%% "scalapb-runtime" % com.trueaccord.scalapb.compiler.Version.scalapbVersion % "protobuf"
    )
  )

lazy val play = (project in file("modules/play"))
  .enablePlugins(PlayScala)
  .settings(generalSettings: _*)
  .settings(
    name := "play",
    libraryDependencies ++= Seq(
      CrossDependencies.scalaTest,
      CrossDependencies.scalactic,
      CrossDependencies.scalaTags,
      "com.typesafe.play" %% "play-json" % "2.6.0-M1"),
    scalaJSProjects := Seq(client),
    pipelineStages in Assets := Seq(scalaJSPipeline),
    compile in Compile := ((compile in Compile) dependsOn scalaJSPipeline).value
  )
  .aggregate(slick)
  .dependsOn(slick)
  .aggregate(flyway)
  .dependsOn(flyway)
  .aggregate(proto)
  .dependsOn(proto)

lazy val client = (project in file("modules/client"))
  .enablePlugins(ScalaJSPlugin, ScalaJSWeb)
  .settings(generalSettings: _*)
  .settings(
    name := "client",
    libraryDependencies += CrossDependencies.scalaTags,
    persistLauncher := true
  )
  .aggregate(proto)
  .dependsOn(proto)

// Loads the jvm project at sbt startup
onLoad in Global := (Command.process("project play", _: State)) compose (onLoad in Global).value

fork in run := true

and this is the plugins.sbt:

// Scala.JS
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.14")
addSbtPlugin("com.vmunier" % "sbt-web-scalajs" % "1.0.2")

// Play
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-SNAPSHOT")

// Proto
addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.3" exclude ("com.trueaccord.scalapb", "protoc-bridge_2.10"))
libraryDependencies += "com.trueaccord.scalapb" %% "compilerplugin-shaded" % "0.5.47"

This is one proto file:

syntax = "proto3";

package tld.awesomeness.proto;

message Test {
    int32 id = 1;
    string email = 2;
}

After compilation I get Test.class

Now in the client I try to:

private def doSend(ws: WebSocket): Unit =
{
    val msg = Test().withId(1337)
    val a: ArrayBuffer = new ArrayBuffer(msg.toByteArray.length)
    msg.toByteArray
    ws.send(a)
}

(The websocket itself worked all just fine when I sent strings through it!)

Now I get this huge stacktrace:

[info] Fast optimizing /home/sorona/awesomeness/modules/client/target/scala-2.12/client-fastopt.js
[error] Referring to non-existent class tld.awesomeness.proto.Test.Test$
[error]   called from tld.awesomeness.ScalaJSTest$.doSend(org.scalajs.dom.raw.WebSocket)scala.Unit
[error]   called from tld.awesomeness.ScalaJSTest$.tld$awesomeness$ScalaJSTest$$$anonfun$call$1(org.scalajs.dom.raw.Event,org.scalajs.dom.raw.WebSocket)org.scalajs.dom.raw.Event
[error]   called from tld.awesomeness.ScalaJSTest$.call()scala.Unit
[error]   called from tld.awesomeness.Main$.main()scala.Unit
[error]   called from scala.scalajs.js.JSApp.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.main
[error]   exported to JavaScript with @JSExport
[error] involving instantiated classes:
[error]   tld.awesomeness.ScalaJSTest$
[error]   tld.awesomeness.Main$
[error] Referring to non-existent class tld.awesomeness.proto.Test.Test
[error]   called from tld.awesomeness.ScalaJSTest$.doSend(org.scalajs.dom.raw.WebSocket)scala.Unit
[error]   called from tld.awesomeness.ScalaJSTest$.tld$awesomeness$ScalaJSTest$$$anonfun$call$1(org.scalajs.dom.raw.Event,org.scalajs.dom.raw.WebSocket)org.scalajs.dom.raw.Event
[error]   called from tld.awesomeness.ScalaJSTest$.call()scala.Unit
[error]   called from tld.awesomeness.Main$.main()scala.Unit
[error]   called from scala.scalajs.js.JSApp.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.main
[error]   exported to JavaScript with @JSExport
[error] involving instantiated classes:
[error]   tld.awesomeness.ScalaJSTest$
[error]   tld.awesomeness.Main$
[error] Referring to non-existent method tld.awesomeness.proto.Test.Test.toByteArray()[scala.Byte
[error]   called from tld.awesomeness.ScalaJSTest$.doSend(org.scalajs.dom.raw.WebSocket)scala.Unit
[error]   called from tld.awesomeness.ScalaJSTest$.tld$awesomeness$ScalaJSTest$$$anonfun$call$1(org.scalajs.dom.raw.Event,org.scalajs.dom.raw.WebSocket)org.scalajs.dom.raw.Event
[error]   called from tld.awesomeness.ScalaJSTest$.call()scala.Unit
[error]   called from tld.awesomeness.Main$.main()scala.Unit
[error]   called from scala.scalajs.js.JSApp.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.main
[error]   exported to JavaScript with @JSExport
[error] involving instantiated classes:
[error]   tld.awesomeness.ScalaJSTest$
[error]   tld.awesomeness.Main$
[error] Referring to non-existent method tld.awesomeness.proto.Test.Test.withId(scala.Int)tld.awesomeness.proto.Test.Test
[error]   called from tld.awesomeness.ScalaJSTest$.doSend(org.scalajs.dom.raw.WebSocket)scala.Unit
[error]   called from tld.awesomeness.ScalaJSTest$.tld$awesomeness$ScalaJSTest$$$anonfun$call$1(org.scalajs.dom.raw.Event,org.scalajs.dom.raw.WebSocket)org.scalajs.dom.raw.Event
[error]   called from tld.awesomeness.ScalaJSTest$.call()scala.Unit
[error]   called from tld.awesomeness.Main$.main()scala.Unit
[error]   called from scala.scalajs.js.JSApp.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.main
[error]   exported to JavaScript with @JSExport
[error] involving instantiated classes:
[error]   tld.awesomeness.ScalaJSTest$
[error]   tld.awesomeness.Main$
[error] Referring to non-existent method tld.awesomeness.proto.Test.Test$.apply$default$2()java.lang.String
[error]   called from tld.awesomeness.ScalaJSTest$.doSend(org.scalajs.dom.raw.WebSocket)scala.Unit
[error]   called from tld.awesomeness.ScalaJSTest$.tld$awesomeness$ScalaJSTest$$$anonfun$call$1(org.scalajs.dom.raw.Event,org.scalajs.dom.raw.WebSocket)org.scalajs.dom.raw.Event
[error]   called from tld.awesomeness.ScalaJSTest$.call()scala.Unit
[error]   called from tld.awesomeness.Main$.main()scala.Unit
[error]   called from scala.scalajs.js.JSApp.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.main
[error]   exported to JavaScript with @JSExport
[error] involving instantiated classes:
[error]   tld.awesomeness.ScalaJSTest$
[error]   tld.awesomeness.Main$
[error] Referring to non-existent method tld.awesomeness.proto.Test.Test$.apply$default$1()scala.Int
[error]   called from tld.awesomeness.ScalaJSTest$.doSend(org.scalajs.dom.raw.WebSocket)scala.Unit
[error]   called from tld.awesomeness.ScalaJSTest$.tld$awesomeness$ScalaJSTest$$$anonfun$call$1(org.scalajs.dom.raw.Event,org.scalajs.dom.raw.WebSocket)org.scalajs.dom.raw.Event
[error]   called from tld.awesomeness.ScalaJSTest$.call()scala.Unit
[error]   called from tld.awesomeness.Main$.main()scala.Unit
[error]   called from scala.scalajs.js.JSApp.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.main
[error]   exported to JavaScript with @JSExport
[error] involving instantiated classes:
[error]   tld.awesomeness.ScalaJSTest$
[error]   tld.awesomeness.Main$
[error] Referring to non-existent method tld.awesomeness.proto.Test.Test.<init>(scala.Int,java.lang.String)
[error]   called from tld.awesomeness.ScalaJSTest$.doSend(org.scalajs.dom.raw.WebSocket)scala.Unit
[error]   called from tld.awesomeness.ScalaJSTest$.tld$awesomeness$ScalaJSTest$$$anonfun$call$1(org.scalajs.dom.raw.Event,org.scalajs.dom.raw.WebSocket)org.scalajs.dom.raw.Event
[error]   called from tld.awesomeness.ScalaJSTest$.call()scala.Unit
[error]   called from tld.awesomeness.Main$.main()scala.Unit
[error]   called from scala.scalajs.js.JSApp.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.$$js$exported$meth$main()java.lang.Object
[error]   called from tld.awesomeness.Main$.main
[error]   exported to JavaScript with @JSExport
[error] involving instantiated classes:
[error]   tld.awesomeness.ScalaJSTest$
[error]   tld.awesomeness.Main$
java.lang.RuntimeException: There were linking errors
        at scala.sys.package$.error(package.scala:27)
        at org.scalajs.core.tools.linker.frontend.BaseLinker.linkInternal(BaseLinker.scala:133)
        at org.scalajs.core.tools.linker.frontend.BaseLinker.linkInternal(BaseLinker.scala:86)
        at org.scalajs.core.tools.linker.frontend.LinkerFrontend$$anonfun$4.apply(LinkerFrontend.scala:54)
        at org.scalajs.core.tools.linker.frontend.LinkerFrontend$$anonfun$4.apply(LinkerFrontend.scala:54)
        at org.scalajs.core.tools.logging.Logger$class.time(Logger.scala:28)
        at org.scalajs.sbtplugin.Loggers$SbtLoggerWrapper.time(Loggers.scala:7)
        at org.scalajs.core.tools.linker.frontend.LinkerFrontend.link(LinkerFrontend.scala:53)
        at org.scalajs.core.tools.linker.Linker$$anonfun$link$1.apply$mcV$sp(Linker.scala:50)
        at org.scalajs.core.tools.linker.Linker$$anonfun$link$1.apply(Linker.scala:49)
        at org.scalajs.core.tools.linker.Linker$$anonfun$link$1.apply(Linker.scala:49)
        at org.scalajs.core.tools.linker.Linker.guard(Linker.scala:67)
        at org.scalajs.core.tools.linker.Linker.link(Linker.scala:49)
        at org.scalajs.core.tools.linker.ClearableLinker$$anonfun$link$1.apply(ClearableLinker.scala:51)
        at org.scalajs.core.tools.linker.ClearableLinker$$anonfun$link$1.apply(ClearableLinker.scala:51)
        at org.scalajs.core.tools.linker.ClearableLinker.linkerOp(ClearableLinker.scala:62)
        at org.scalajs.core.tools.linker.ClearableLinker.link(ClearableLinker.scala:51)
        at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$org$scalajs$sbtplugin$ScalaJSPluginInternal$$scalaJSStageSettings$4$$anonfun$apply$6$$anonfun$apply$7.apply(ScalaJSPluginInternal.scala:251)
        at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$org$scalajs$sbtplugin$ScalaJSPluginInternal$$scalaJSStageSettings$4$$anonfun$apply$6$$anonfun$apply$7.apply(ScalaJSPluginInternal.scala:239)
        at sbt.FileFunction$$anonfun$cached$1.apply(Tracked.scala:253)
        at sbt.FileFunction$$anonfun$cached$1.apply(Tracked.scala:253)
        at sbt.FileFunction$$anonfun$cached$2$$anonfun$apply$3$$anonfun$apply$4.apply(Tracked.scala:267)
        at sbt.FileFunction$$anonfun$cached$2$$anonfun$apply$3$$anonfun$apply$4.apply(Tracked.scala:263)
        at sbt.Difference.apply(Tracked.scala:224)
        at sbt.Difference.apply(Tracked.scala:206)
        at sbt.FileFunction$$anonfun$cached$2$$anonfun$apply$3.apply(Tracked.scala:263)
        at sbt.FileFunction$$anonfun$cached$2$$anonfun$apply$3.apply(Tracked.scala:262)
        at sbt.Difference.apply(Tracked.scala:224)
        at sbt.Difference.apply(Tracked.scala:200)
        at sbt.FileFunction$$anonfun$cached$2.apply(Tracked.scala:262)
        at sbt.FileFunction$$anonfun$cached$2.apply(Tracked.scala:260)
        at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$org$scalajs$sbtplugin$ScalaJSPluginInternal$$scalaJSStageSettings$4$$anonfun$apply$6.apply(ScalaJSPluginInternal.scala:256)
        at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$org$scalajs$sbtplugin$ScalaJSPluginInternal$$scalaJSStageSettings$4$$anonfun$apply$6.apply(ScalaJSPluginInternal.scala:237)
        at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
        at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:40)
        at sbt.std.Transform$$anon$4.work(System.scala:63)
        at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:228)
        at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:228)
        at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:17)
        at sbt.Execute.work(Execute.scala:237)
        at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:228)
        at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:228)
        at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:159)
        at sbt.CompletionService$$anon$2.call(CompletionService.scala:28)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)
[error] (client/compile:fastOptJS) There were linking errors

My ide finds everything but obviously I am doing something wrong. I already had a look at https://github.com/thesamet/scalapbjs-test but to no avail.

Problem appears with that line val msg = Test().withId(1337)

edit: after comment I changed build.sbt:

lazy val proto = (crossProject in file("modules/proto"))
  .settings(generalSettings: _*)
  .settings(
    PB.targets in Compile := Seq(
      scalapb.gen() -> (sourceManaged in Compile).value
    )).
  jvmSettings(
    libraryDependencies += "com.trueaccord.scalapb" %% "scalapb-runtime" % com.trueaccord.scalapb.compiler.Version.scalapbVersion % "protobuf",
    PB.targets in Compile := Seq(
      scalapb.gen() -> (sourceManaged in Compile).value
    )
  ).
  jsSettings(
    libraryDependencies ++= Seq(
      "com.trueaccord.scalapb" %%% "scalapb-runtime" % com.trueaccord.scalapb.compiler.Version.scalapbVersion,
      "com.trueaccord.scalapb" %%% "scalapb-runtime" % com.trueaccord.scalapb.compiler.Version.scalapbVersion % "protobuf"
    ),
    PB.targets in Compile := Seq(
      scalapb.gen() -> (sourceManaged in Compile).value
    )
  )

lazy val protoJs = proto.js
lazy val protoJVM = proto.jvm

lazy val play = (project in file("modules/play"))
  .enablePlugins(PlayScala)
  .settings(generalSettings: _*)
  .settings(
    name := "play",
    libraryDependencies ++= Seq(
      CrossDependencies.scalaTest,
      CrossDependencies.scalactic,
      CrossDependencies.scalaTags,
      "com.typesafe.play" %% "play-json" % "2.6.0-M1"),
    scalaJSProjects := Seq(client),
    pipelineStages in Assets := Seq(scalaJSPipeline),
    compile in Compile := ((compile in Compile) dependsOn scalaJSPipeline).value
  )
  .aggregate(slick)
  .dependsOn(slick)
  .aggregate(flyway)
  .dependsOn(flyway)
  .aggregate(protoJVM)
  .dependsOn(protoJVM)

lazy val client = (project in file("modules/client"))
  .enablePlugins(ScalaJSPlugin, ScalaJSWeb)
  .settings(generalSettings: _*)
  .settings(
    name := "client",
    libraryDependencies += CrossDependencies.scalaTags,
    persistLauncher := true
  )
  .aggregate(protoJs)
  .dependsOn(protoJs)

now now neither play nor client can resolve the proto-class :(

(Also I am aware of the redundant PB.targets in Compile..., I just thought that sharing might not work there, so I added it to both distinct settings again)

  • 1
    `proto` should be a `crossProject`, because both the JVM project `play` and the JS project `client` depend on it. – sjrd Jan 25 '17 at 22:03
  • @sjrd can you give an example? I know about https://www.scala-js.org/doc/project/cross-build.html but did indeed forget about this here. Mea culpa. But now t hat I added it, I get that the class cannot be found anymore so I assume that the protobuf project needs to be setup differently to have one src folder but still be compiled for js and java. –  Jan 26 '17 at 19:42
  • Use a `crossProject.crossType(CrossType.Pure)` if you do not have any platform-specific source directories. – sjrd Jan 26 '17 at 23:01
  • That alone sadly is not enough, need to dig deeper (neither project can find the proto... it"s not being compiled to their respective /target folders either :() –  Jan 27 '17 at 17:45

1 Answers1

4

With pure CrossProject you need to specify the actual path ScalaPB should look for proto files (the value it guesses is wrong). Here is a minimal example:

lazy val proto = (crossProject.crossType(CrossType.Pure) in file("proto"))
  .settings(
    PB.targets in Compile := Seq(
      scalapb.gen() -> (sourceManaged in Compile).value
    ),
    // The trick is in this line:
    PB.protoSources in Compile := Seq(file("proto/src/main/protobuf")),
    libraryDependencies ++= Seq(
      "com.trueaccord.scalapb" %%% "scalapb-runtime" % com.trueaccord.scalapb.compiler.Version.scalapbVersion,
      "com.trueaccord.scalapb" %%% "scalapb-runtime" % com.trueaccord.scalapb.compiler.Version.scalapbVersion % "protobuf"
    )
  )
thesamet
  • 6,382
  • 2
  • 31
  • 42