Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using DependsOn between two ScalaJS SBT projects

(Long question ahead. Simplified tl;dr at the bottom).

I have two ScalaJS projects built with SBT - "myapp" and "mylib", in the following directory structure

root/build.sbt

root/myapp/build.sbt
root/myapp/jvm/
root/myapp/js/
root/myapp/shared/

root/mylib/build.sbt
root/mylib/jvm
root/mylib/js
root/mylib/shared

lib exports an artifact named "com.example:mylib:0.1", which as used as a libraryDependency for myapp.

myapp and mylib are in separate repositories, contain their own build files, and should be able to be build completely separately (i.e. they must contain their own individual build config).

In production, they will be built separately with mylib being first published as a maven artifact before building myapp separately.

In development however, I want to be able to merge these into a parent SBT project so that both can be developed in parallel without needing to use publishLocal after each change.

In a traditional (not scalajs) project this would be quite easy

$ROOT/build.sbt:

lazy val mylib = project
lazy val myapp = project.dependsOn(mylib)

However in ScalaJS, we actually have two projects inside each module - appJVM, appJS, libJVM and libJS. As such, the above configuration only finds the aggregate root project and does not correctly apply the dependsOn configuration to the actual JVM and JS projects.

(i.e. myapp and mylib build.sbt each contains two projects, and an aggregate root project)

Ideally I'd like to be able to do something like the following

lazy val mylibJVM = project
lazy val myappJVM = project.dependsOn(mylibJVM)

lazy val mylibJS = project
lazy val myappJS = project.dependsOn(myappJS)

Unfortunately this just creates new projects within the root instead of importing the subprojects themselves.

I've also tried various combinations of paths (such as)

lazy val mylibJVM = project.in(file("mylib/jvm"))

But this doesn't see configuration in build.sbt file in mylib

Ultimately I keep running up against the same problem - when importing an existing multi-project SBT project into a parent sbt file, it imports the root project, but does not seem to provide a way to import a subproject from an existing multimodule SBT file in a way that lets me add dependsOn configuration to it.

tl;dr

If I have

  • root/mylib/build.sbt with multiple projects defined and
  • root/myapp/build.sbt with multiple projects defined

Is it possible to import individual subprojects into root/build.sbt instead of the root project from the submodule?

i.e. Can I have two layers of multiproject builds.

like image 256
James Davies Avatar asked Jul 06 '16 01:07

James Davies


1 Answers

After spending a lot of time digging through SBT source code, I managed to figure out a solution. This isn't clean, but it works. (For bonus points, it imports correctly into IntelliJ).

// Add this function to your root build.sbt file. 
// It can be used to define a dependency between any
// `ProjectRef` without needing a full project definition.
def addDep(from:String, to:String) = {
  buildDependencies in Global <<= (
  buildDependencies in Global, 
  thisProjectRef in from, 
  thisProjectRef in to) { 
    (deps, fromref, toref) => 
      deps.addClasspath(fromref, ResolvedClasspathDependency(toref, None))
  }
}

// `project` will import the `build.sbt` file
// in the subdirectory of the same name as the `lazy val`
// (performed by an SBT macro). i.e. `./mylib/build.sbt`
// 
// This won't reference the actual subprojects directly, 
// will but import them into the namespace such that they 
// can be referenced as "ProjectRefs", which are implicitly
// converted to from strings.
// 
// We then aggregate the JVM and JS ScalaJS projects 
// into the new root project we've defined. (Which unfortunately
// won't inherit anything from the child build.sbt)

lazy val mylib = project.aggregate("mylibJVM","mylibJS")
lazy val myapp = project.aggregate("myappJVM","myappJS")

// Define a root project to aggregate everything
lazy val root = project.in(file(".")).aggregate(mylib,myapp)


// We now call our custom function to define a ClassPath dependency
// between `myapp` -> `mylib` for both JVM and JS subprojects.
// In particular, this will correctly find exported artifacts
// so that `myapp` can refer to `mylib` in libraryDependencies
// without needing to use `publishLocal`. 
addDep("myappJVM", "mylibJVM")
addDep("myappJS","mylibJS")
like image 197
James Davies Avatar answered Nov 11 '22 19:11

James Davies