ScalaPB с Scala.js и Scala(jvm) — были ошибки связывания

У меня есть несколько подпроектов в моем sbt, один из которых является сервером (на основе playframework), другой — клиентской стороной (scala.js), а третий — связью между ними в форме protobuf (scalapb).

А это мой 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

а это 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"

Это один файл proto:

syntax = "proto3";

package tld.awesomeness.proto;

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

После компиляции получаю Test.class

Теперь в клиенте я пытаюсь:

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

(Сам веб-сокет работал нормально, когда я отправлял через него строки!)

Теперь я получаю эту огромную трассировку стека:

[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

Мой ide находит все, но, очевидно, я делаю что-то не так. Я уже просмотрел https://github.com/thesamet/scalapbjs-test, но но безрезультатно.

Проблема появляется с этой строкой val msg = Test().withId(1337)

редактировать: после комментария я изменил 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)

теперь теперь ни play, ни client не могут разрешить прото-класс :(

(Также я знаю о избыточном PB.targets in Compile..., я просто подумал, что совместное использование может не работать там, поэтому я снова добавил его в обе разные настройки)


person Community    schedule 25.01.2017    source источник
comment
proto должен быть crossProject, потому что от него зависят и проект JVM play, и проект JS client.   -  person sjrd    schedule 26.01.2017
comment
@sjrd можешь привести пример? Я знаю о scala-js.org/doc/project/cross-build .html, но действительно забыл об этом здесь. Моя вина. Но теперь, когда я его добавил, я понимаю, что класс больше не может быть найден, поэтому я предполагаю, что проект protobuf должен быть настроен по-другому, чтобы иметь одну папку src, но при этом быть скомпилированным для js и java.   -  person    schedule 26.01.2017
comment
Используйте crossProject.crossType(CrossType.Pure), если у вас нет исходных каталогов для конкретной платформы.   -  person sjrd    schedule 27.01.2017
comment
Одного этого, к сожалению, недостаточно, нужно копать глубже (ни один из проектов не может найти прототип... он также не компилируется в соответствующие /целевые папки :()   -  person    schedule 27.01.2017


Ответы (1)


С чистым CrossProject вам нужно указать фактический путь, по которому ScalaPB должен искать прото-файлы (значение, которое он угадывает, неверно). Вот минимальный пример:

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"
    )
  )
person thesamet    schedule 28.01.2017