-1

Edit 2: Observations and questions

  1. I am pretty sure along with the commenter below Justin that the problem is due to an errant build.sbt configuration. However, this is the first time I have seen an errant build.sbt configuration that literally works for everything else except for pickers. Maybe that is becaus they use macros and I as a rule avoid them.

  2. Why would it matter whether Flow.merge is used vs Flow.map if the problem is with the sbt?

  3. Suspicious build.sbt extract

    lazy val server = project .dependsOn(sharedJvm, client)

  4. Suspicious stack trace

So this is the top of the stack: it goes from a method I cannot find to the linking environment to the string encoding utils. Ok.

server java.lang.RuntimeException: stub

Huh? stub?

server  at scala.sys.package$.error(package.scala:27)
server  at scala.scalajs.runtime.package$.linkingInfo(package.scala:143)
server  at scala.scalajs.runtime.package$.environmentInfo(package.scala:137)

HUH?

server  at scala.scalajs.js.Dynamic$.global(Dynamic.scala:78)

???

server  at boopickle.StringCodec$.encodeUTF8(StringCodec.scala:56)

Edit 1: My big and beautiful build.sbt might be the problem

What you cannot see is that I organized in my project folder:

  • JvmDependencies.scala which has regular Jvm dependencies
  • SjsDependencies.scala which has Def.settingsKeys of libraryDependencies on JsModuleIDs
  • WebJarDependencies.scala which has javascripts and css's

build.sbt

lazy val    shared = (crossProject.crossType(CrossType.Pure) in file("shared"))
  .configure(_.enablePlugins(ScalaJSPlugin))
  .settings(SjsDependencies.pickling.toSettingsDefinition(): _*)
  .settings(SjsDependencies.tagsAndDom.toSettingsDefinition(): _*)
  .settings(SjsDependencies.css.toSettingsDefinition(): _*)

lazy val sharedJvm = shared.jvm
lazy val  sharedJs = shared.js

lazy val    cmdlne = project
  .dependsOn(sharedJvm)
  .settings(
    libraryDependencies ++= (
      JvmDependencies.commandLine ++
      JvmDependencies.logging     ++
      JvmDependencies.akka        ++
      JvmDependencies.serialization
    )
  )

lazy val    client = project
  .enablePlugins(ScalaJSPlugin, SbtWeb, SbtSass)
  .dependsOn(sharedJs)
  .settings(
    (SjsDependencies.shapeless ++ SjsDependencies.audiovideo ++ SjsDependencies.databind ++ SjsDependencies.functional ++ SjsDependencies.lensing ++ SjsDependencies.logging ++ SjsDependencies.reactive).toSettingsDefinition(),
    jsDependencies ++= WebjarDependencies.js,
    libraryDependencies ++= WebjarDependencies.notJs,
    persistLauncher in Compile := true
  )

lazy val    server = project
  .dependsOn(sharedJvm, client)
  .enablePlugins(SbtNativePackager)
  .settings(

    copyWebJarResources := { streams.value.log("Copying webjar resources")
      val `Web Modules target directory` = (resourceManaged in Compile).value / "assets"
      val `Web Modules source directory` = (WebKeys.assets in Assets in client).value / "lib"

      final class UsefulFileFilter(acceptable: String*) extends FileFilter {

        // TODO ADJUST TO EXCLUDE JS MAP FILES
        import scala.collection.JavaConversions._
        def accept(file: File) = (file.isDirectory && FileUtils.listFiles(file, acceptable.toArray, true).nonEmpty) || acceptable.contains(file.ext) && !file.name.contains(".js.")
      }

      val `file filter` = new UsefulFileFilter("css", "scss", "sass", "less", "map")

      IO.createDirectory(`Web Modules target directory`)
      IO.copyDirectory(source = `Web Modules source directory`, target = `Web Modules target directory` / "script")
      FileUtils.copyDirectory(`Web Modules source directory`, `Web Modules target directory` / "style", `file filter`)
    },

    // run the copy after compile/assets but before managed resources
    copyWebJarResources <<= copyWebJarResources dependsOn(compile in Compile, WebKeys.assets in Compile in client, fastOptJS in Compile in client),
    managedResources in Compile <<= (managedResources in Compile) dependsOn copyWebJarResources,

    watchSources <++= (watchSources in client),


    resourceGenerators in Compile <+= Def.task {
      val files = ((crossTarget in(client, Compile)).value ** ("*.js" || "*.map")).get
      val mappings: Seq[(File,String)] = files pair rebase((crossTarget in(client, Compile)).value, ((resourceManaged in  Compile).value / "assets/").getAbsolutePath )
      val map: Seq[(File, File)] = mappings.map { case (s, t) => (s, file(t))}
      IO.copy(map).toSeq
    },

    reStart <<= reStart dependsOn (managedResources in Compile),

    libraryDependencies ++= (
      JvmDependencies.akka                ++
      JvmDependencies.jarlocating         ++
      JvmDependencies.functional          ++
      JvmDependencies.serverPickling      ++
      JvmDependencies.logging             ++
      JvmDependencies.serialization       ++
      JvmDependencies.testing
    )
  )

Edit 0: A very obscure chat thread has a guy saying what I am feeling: no, not **** scala, but

Mark Eibes @i-am-the-slime Oct 15 2015 09:37 @ochrons I'm still fighting. I can't seem to pickle anything anymore. https://gitter.im/scala-js/scala-js/archives/2015/10/15

I have a rather simple requirement - I have one web socket route on a akka http server that is defined AkkaServerLogEventToMessageHandler():

object AkkaServerLogEventToMessageHandler
  extends Directives {

  val sourceOfLogs =

    Source.actorPublisher[AkkaServerLogMessage](AkkaServerLogEventPublisher.props) map {
      event ⇒
        BinaryMessage(
          ByteString(
            Pickle.intoBytes[AkkaServerLogMessage](event)
          )
        )
    }

  def apply(): server.Route = {
    handleWebSocketMessages(
      Flow[Message].merge(sourceOfLogs)
    )
  }

}

This fits into a tiny set of routes in the most obvious way.

Now why is that I cannot get boopickle, upickle, or prickle to serialize something as simple as this stupid case class?

sealed case class AkkaServerLogMessage(
                                 message:   String,
                                 level:     Int,
                                 timestamp: Long
                               )
  • No nesting
  • All primitive types
  • No generics
  • Only three of them

These all produced roughly the same error

  • Using all three of the common picklers to write
  • Using TextMessage instead of BinaryMessage and the corresponding upickle or prickle writeJs or whatever methods
  • Varying the case class down to nothing (nothing, as in no members)
  • Varying the input itself to the case class
  • Importing various permutations of Implicits and underscore stuff

... specifically, they gave me variations on the same stupid error (not the same error, but considerably similar)

server [ERROR] [04/21/2016 22:04:00.362] [app-akka.actor.default-dispatcher-7] [akka.actor.ActorSystemImpl(app)] WebSocket handler failed with stub
server java.lang.RuntimeException: stub
server  at scala.sys.package$.error(package.scala:27)
server  at scala.scalajs.runtime.package$.linkingInfo(package.scala:143)
server  at scala.scalajs.runtime.package$.environmentInfo(package.scala:137)
server  at scala.scalajs.js.Dynamic$.global(Dynamic.scala:78)
server  at boopickle.StringCodec$.encodeUTF8(StringCodec.scala:56)
server  at boopickle.Encoder.writeString(Codecs.scala:338)
server  at boopickle.BasicPicklers$StringPickler$.pickle(Pickler.scala:183)
server  at boopickle.BasicPicklers$StringPickler$.pickle(Pickler.scala:134)
server  at boopickle.PickleState.pickle(Pickler.scala:511)
server  at shindig.clientaccess.handler.AkkaServerLogEventToMessageHandler$$anonfun$1$Pickler$macro$1$2$.pickle(AkkaServerLogEventToMessageHandler.scala:35)
server  at shindig.clientaccess.handler.AkkaServerLogEventToMessageHandler$$anonfun$1$Pickler$macro$1$2$.pickle(AkkaServerLogEventToMessageHandler.scala:35)
server  at boopickle.PickleImpl$.apply(Default.scala:70)
server  at boopickle.PickleImpl$.intoBytes(Default.scala:75)
server  at shindig.clientaccess.handler.AkkaServerLogEventToMessageHandler$$anonfun$1.apply(AkkaServerLogEventToMessageHandler.scala:35)
server  at shindig.clientaccess.handler.AkkaServerLogEventToMessageHandler$$anonfun$1.apply(AkkaServerLogEventToMessageHandler.scala:31)

This worked

  • Not using Flow.merge (defeats the purpose, I want to keep sending out put with logs)
  • Using a static value
  • Other useless things

Appeal

Please let me know where and why I am stupid... I spent four hours on this problem today in different forms, and it is driving me nuts.

tacos_tacos_tacos
  • 10,277
  • 11
  • 73
  • 126

2 Answers2

1

In your build.sbt, you have:

lazy val shared = (crossProject.crossType(CrossType.Pure) in file("shared"))
  .configure(_.enablePlugins(ScalaJSPlugin))

Do not do this. You must not enable the Scala.js plugin on a cross-project, ever. This also adds it to the JVM side, which will wreak havoc. Most notably, this will cause %%% to resolve the Scala.js artifacts of your dependencies in the JVM project, and that is really bad. This is what causes your issue.

crossProject already adds the Scala.js plugin to the JS part, and only that one. So simply remove that enablePlugins line.

sjrd
  • 21,805
  • 2
  • 61
  • 91
0

Mystery solved. Thanks to @Justin du Coeur for pointing me in the right direction.

The reason boopickle wasn't working in particular was because in the dependency chain I was including both the sjs and the jvm version of boopickle in the server project.

I removed the server dependsOn for client and for sharedJs and also removed boopickle from the shared dependencies. Now it works.

tacos_tacos_tacos
  • 10,277
  • 11
  • 73
  • 126