ScalaPB 与 Scala.js 和 Scala(jvm) - 存在链接错误

标签 scala playframework protocol-buffers scala.js scalapb

我的 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)
}

(当我通过它发送字符串时,websocket 本身工作得很好!)

现在我得到了这个巨大的堆栈跟踪:

[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)

现在,playclient 都无法解析原始类:(

(我还知道编译中的冗余PB.targets...,我只是认为共享可能无法在那里工作,所以我再次将其添加到两个不同的设置中)

最佳答案

使用纯 CrossProject,您需要指定 ScalaPB 查找 proto 文件的实际路径(它猜测的值是错误的)。这是一个最小的例子:

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"
    )
  )

关于ScalaPB 与 Scala.js 和 Scala(jvm) - 存在链接错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41862045/

相关文章:

java - sparkR:实例化 'org.apache.spark.sql.hive.HiveSessionState' 时出错:

scala - 为什么在 Play Framework 中使用 @Singleton 而不是 Scala 的对象?

scala - 实现 Play 2.1.1 的写入

python - 如何在 python 中向 Dialogflow 事件添加参数

java - Google protobuf可以用于android中C和Java服务之间的通信吗?

scala - 有什么方法可以将类型保存在 Scala 列表中吗?

scala - 为什么 scala.util.Failure 有一个类型参数?

scala - 在Scala中合并数组

java - java中如何将上传的多个pdf文件存储到特定位置?

java - Java 中的 Protocol Buffer : can we handle primitive arrays efficiently?