Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I change a project's id in SBT 1.0?

Tags:

scala

sbt

I have a bunch of SBT 0.13 project definitions that look like this:

lazy val coreBase = crossProject.crossType(CrossType.Pure).in(file("core"))
  .settings(...)
  .jvmConfigure(_.copy(id = "core"))
  .jsConfigure(_.copy(id = "coreJS"))

lazy val core = coreBase.jvm
lazy val coreJS = coreBase.js

(Mostly because I'm resentful about having to maintain Scala.js builds and don't want to have to type the JVM suffix every time I'm changing projects, etc.)

This doesn't compile in SBT 1.0 because Project doesn't have a copy method now.

Okay, let's check the migration guide.

Many of the case classes are replaced with pseudo case classes generated using Contraband. Migrate .copy(foo = xxx) to withFoo(xxx).

Cool, let's try it.

build.sbt:100: error: value withId is not a member of sbt.Project
  .jvmConfigure(_.withId("core"))
                  ^

So I asked on Gitter and got crickets.

The links for the 1.0 API docs actually point to something now, which is nice, but they're not very helpful in this case, and trying to read the SBT source gives me a headache. I'm not in a rush to update to 1.0, but I'm going to have to at some point, I guess, and maybe some helpful person will have answered this by then.

like image 581
Travis Brown Avatar asked Sep 18 '17 04:09

Travis Brown


1 Answers

(This answer has been edited with information about sbt 1.1.0+ and sbt-crossproject 0.3.1+, which significantly simplify the whole thing.)

With sbt 1.1.0 and later, you can use .withId("core"). But there's better with sbt-crossproject 0.3.1+, see below.

I don't know about changing the ID of a Project, but here is also a completely different way to solve your original issue, i.e., have core/coreJS instead of coreJVM/coreJS. The idea is to customize crossProject to use the IDs you want to begin with.

First, you'll need to use sbt-crossproject. It is the new "standard" for compilation across several platforms, co-designed by @densh from Scala Native and myself (from Scala.js). Scala.js 1.x will allways use sbt-crossproject, but it is also possible to use sbt-crossproject with Scala.js 0.6.x. For that, follow the instructions in the readme. In particular, don't forget the "shadowing" part:

// Shadow sbt-scalajs' crossProject and CrossType from Scala.js 0.6.x
import sbtcrossproject.{crossProject, CrossType}

sbt-crossproject is more flexible than Scala.js' hard-coded crossProject. This means you can customize it more easily. In particular, it has a generic notion of Platform, defining how any given platform behaves.

For a cross JVM/JS project, the new-style crossProject invocation would be

lazy val coreBase = crossProject(JVMPlatform, JSPlatform)
  .crossType(CrossType.Pure)
  .in(file("core"))
  .settings(...)
  .jvmConfigure(_.copy(id = "core"))
  .jsConfigure(_.copy(id = "coreJS"))

lazy val core = coreBase.jvm
lazy val coreJS = coreBase.js

Starting with sbt-crossproject 0.3.1, you can simply tell it not to add the platform suffix for one of your platforms. In your case, you want to avoid the suffix for the JVM platform, so you would write:

lazy val coreBase = crossProject(JVMPlatform, JSPlatform)
  .withoutSuffixFor(JVMPlatform)
  .crossType(CrossType.Pure)
  ...

lazy val core = coreBase.jvm
lazy val coreJS = coreBase.js

and that's all you need to do!

Old answer, applicable to sbt-crossproject 0.3.0 and before

JVMPlatform and JSPlatform are not an ADT; they are designed in an OO style. This means you can create your own. In particular, you can create your own JVMPlatformNoSuffix that would do the same as JVMPlatform but without adding a suffix to the project ID:

import sbt._
import sbtcrossproject._

case object JVMPlatformNoSuffix extends Platform {
  def identifier: String = "jvm"
  def sbtSuffix: String = "" // <-- here is the magical empty string
  def enable(project: Project): Project = project
  val crossBinary: CrossVersion = CrossVersion.binary
  val crossFull: CrossVersion = CrossVersion.full
}

Now that's not quite enough yet, because .jvmSettings(...) and friends are defined to act on a JVMPlatform, not on any other Platform such as JVMPlatformNoSuffix. You'll therefore have to redefine that as well:

implicit def JVMNoSuffixCrossProjectBuilderOps(
    builder: CrossProject.Builder): JVMNoSuffixCrossProjectOps =
  new JVMNoSuffixCrossProjectOps(builder)

implicit class JVMNoSuffixCrossProjectOps(project: CrossProject) {
  def jvm: Project = project.projects(JVMPlatformNoSuffix)

  def jvmSettings(ss: Def.SettingsDefinition*): CrossProject =
    jvmConfigure(_.settings(ss: _*))

  def jvmConfigure(transformer: Project => Project): CrossProject =
    project.configurePlatform(JVMPlatformNoSuffix)(transformer)
}

Once you have all of that in your build (hidden away in a project/JVMPlatformNoSuffix.scala in order not to pollute the .sbt file), you can define the above cross-project as:

lazy val coreBase = crossProject(JVMPlatformNoSuffix, JSPlatform)
  .crossType(CrossType.Pure)
  .in(file("core"))
  .settings(...)

lazy val core = coreBase.jvm
lazy val coreJS = coreBase.js

without any need to explicitly patch the project IDs.

like image 164
sjrd Avatar answered Oct 15 '22 17:10

sjrd