portable-scala/sbt-crossproject

Dependency Resolution in Root Project of Pure Cross Project

bpholt opened this issue · 2 comments

The cats-scalatest project set up a Pure cross project for JVM and JS platforms on several Scala versions. Dependency resolution seems to work fine throughout the matrix, except for the root project. The broken config is available for reference on the scalajs-cross-pure-broken branch.

sbt:cats-scalatest> projects
[info] In file: ~/cats-scalatest/
[info] 	 * cats-scalatest
[info] 	   catsScalatestJS
[info] 	   catsScalatestJVM

Running sbt test or sbt compile results in compilation failures in the root (cats-scalatest) project that indicate missing dependencies, e.g.:

[info] Compiling 5 Scala sources to ~/cats-scalatest/target/scala-2.12/classes ...
[error] ~/cats-scalatest/src/main/scala/cats/scalatest/EitherMatchers.scala:3:12: object scalatest is not a member of package org
[error] import org.scalatest.matchers.{BeMatcher, MatchResult, Matcher}
[error]            ^

Running sbt catsScalatestJS/compile or sbt catsScalatestJVM/compile both work fine.

I would have expected compile or cats-scalatest/compile to call the platform-specific projects' compile tasks, not execute one of its own.

FWIW, I noticed that setting the Scala version using ++ excludes the core project:

sbt:cats-scalatest> ++ 2.11.12 -v
[info] Setting Scala version to 2.11.12 on 2 projects.
[info] Switching Scala version on:
[info]     catsScalatestJVM (2.11.12, 2.12.10, 2.13.1)
[info]     catsScalatestJS (2.11.12, 2.12.10, 2.13.1)
[info] Excluding projects:
[info]   * cats-scalatest (2.12.10)
[info] Reapplying settings...
[info] Set current project to cats-scalatest (in build file:~/cats-scalatest/)

Maybe cats-scalatest should be excluded more broadly, and it's not for some reason?

We worked around the issue by switching to a Full cross project on the primary branch. The broken config is preserved on the scalajs-cross-pure-broken branch. (There is no platform-specific code for this project, so ideally it will switch back to Pure once we figure out what's going wrong.)

I had this occur too. I think it is due to the combination of .crossType(Pure) and .in(file(".")).
What I think happens is sbt-crossproject creates three subprojects (xxxJVM, xxxJS and the "shared" project xxx). However, the "shared" xxx project conflicts with SBTs default "root" project (because SBT doesn't recognize a crossProject(...).in(file(".")) as being a root project, unlike project.in("."))

Thus, settings added to the crossProject don't get propagated to the "shared" project as it's "overwrriten" by SBT's aggregate root. This also breaks the .dependsOn relationships that sbt-crossproject usually sets up.

I think the best workarounds are either to use CrossType.Full or to move your code into a subproject (ie, don't use .in(file(".")))

sjrd commented

That is a general problem with root projects in sbt. There's nothing sbt-crossproject can do about it.

Remember that crossProject is only a builder for two projects: one for JVM and one for JS. The third project that is actually in "." is the auto-generated root project, which has nothing to do with sbt-crossproject. By default it aggregates all projects in the build but also exists on its own. In a Pure setting, the directory layout will be such that sources of the cross-project also appear as sources of the root project, but none of the settings or any of the other configurations apply.

There are several workarounds:

  • Never call any task (but clean) on the root project
  • Put your pure cross-project in a subdirectory
  • Redefine the tasks you're interested to call so that they do nothing in the root project (e.g., Compile / compile := sbt.internal.inc.Analysis.Empty); this is tedious

There are probably others.

Anyway, I'm going to close this issue because, as I said, there's nothing we can do about it.