SBT multiproject setup where a subproject's build files depend on unmanaged JARs

I managed to compile the projects with the following changes.

First, I got compilation errors when trying to compile the projectB @ b558245 revision, which is referenced in your GitHub project. I checked the latest tag, v15.0.0, and it compiled.

Second, in projectA/build.sbt, projectB should be defined as

lazy val projectB = RootProject(file("../projectB/src"))

../projectB/src/project is the builder project that builds projectB. It requires things in scala compiler and sbt, and thus produced the errors you saw when you tried to compile it directly.

Third, projectB failed to compile as a dependency of projectA. That's because, the Utils object (in projectB/src/project/Utils.scala) which was intended to refer to the layout of projectB, was initialized with a wrong directory (../projectA)

object Utils {
   /** MMT root directory */
   val root = File("..").canonical  // Got the wrong directory when compiled with projectA

So, we have to do some modifications to Utils.scala and it dependents, to ensure it always find the right position of projectB. Here is the patch to projectB to make it compile from projectA. Might not be the best solution, but it works on my laptop.

diff --git a/src/build.sbt b/src/build.sbt
index 059ef9c8e..b7495c8eb 100644
--- a/src/build.sbt
+++ b/src/build.sbt
@@ -1,10 +1,13 @@
 import scala.io.Source
 import sbt.Keys._
+import Utils.utils
+
+utils in ThisBuild := Utils((baseDirectory in src).value)

 // =================================
 // META-DATA and Versioning
 // =================================
-version in ThisBuild := {Source.fromFile("mmt-api/resources/versioning/system.txt").getLines.mkString.trim}
+version in ThisBuild := {Source.fromFile(baseDirectory.value / "mmt-api/resources/versioning/system.txt").getLines.mkString.trim}

 val now = {
   import java.text.SimpleDateFormat
@@ -106,7 +109,7 @@ def mmtProjectsSettings(nameStr: String) = commonSettings(nameStr) ++ Seq(

   unmanagedBase := baseDirectory.value  / "lib",

-  publishTo := Some(Resolver.file("file", Utils.deploy.toJava / " main")),
+  publishTo := Some(Resolver.file("file", utils.value.deploy.toJava / " main")),

   install := {},
   deploy := Utils.deployPackage("main/" + nameStr + ".jar").value
@@ -153,8 +156,12 @@ lazy val mmt = (project in file("mmt")).
   settings(
     exportJars := false,
     publish := {},
-    deploy := {
-      assembly in Compile map Utils.deployTo(Utils.deploy / "mmt.jar")
+    deploy := Def.taskDyn {
+      val jar = (assembly in Compile).value
+      val u = utils.value
+      Def.task {
+        Utils.deployTo(u.deploy / "mmt.jar")(jar)
+      }
     }.value,
     assemblyExcludedJars in assembly := {
       val cp = (fullClasspath in assembly).value
@@ -172,13 +179,13 @@ lazy val mmt = (project in file("mmt")).

 // MMT is split into multiple subprojects to that are managed independently.

-val apiJars = Seq(
+def apiJars(u: Utils) = Seq(
   "scala-compiler.jar",
   "scala-reflect.jar",
   "scala-parser-combinators.jar",
   "scala-xml.jar",
   "xz.jar",
-).map(Utils.lib.toJava / _ )
+).map(u.lib.toJava / _ )

 // The kernel upon which everything else depends. Maintainer: Florian
 lazy val api = (project in file("mmt-api")).
@@ -188,8 +195,8 @@ lazy val api = (project in file("mmt-api")).
   settings(
     scalacOptions in Compile ++= Seq("-language:existentials"),
     scalaSource in Compile := baseDirectory.value / "src" / "main",
-    unmanagedJars in Compile ++= apiJars,
-    unmanagedJars in Test ++= apiJars,
+    unmanagedJars in Compile ++= apiJars(utils.value),
+    unmanagedJars in Test ++= apiJars(utils.value),
   )


@@ -226,7 +233,7 @@ lazy val jedit = (project in file("jEdit-mmt")).
     resourceDirectory in Compile := baseDirectory.value / "src/resources",
     unmanagedJars in Compile ++= jeditJars map (baseDirectory.value / "lib" / _),
     deploy := Utils.deployPackage("main/MMTPlugin.jar").value,
-    install := Utils.installJEditJars
+    install := utils.value.installJEditJars
   )

 // MMT IntelliJ-Plugin. Maintainer: Dennis
@@ -299,7 +306,7 @@ lazy val concepts = (project in file("concept-browser")).
     libraryDependencies ++= Seq(
       "org.ccil.cowan.tagsoup" % "tagsoup" % "1.2"
     ),
-    unmanagedJars in Compile += Utils.lib.toJava / "scala-xml.jar"
+    unmanagedJars in Compile += utils.value.lib.toJava / "scala-xml.jar"
  )

 // =================================
@@ -389,7 +396,7 @@ lazy val oeis = (project in file("mmt-oeis")).
   dependsOn(planetary).
   settings(mmtProjectsSettings("mmt-oeis"): _*).
   settings(
-    unmanagedJars in Compile += Utils.lib.toJava / "scala-parser-combinators.jar"
+    unmanagedJars in Compile += utils.value.lib.toJava / "scala-parser-combinators.jar"
   )

 // =================================
@@ -416,11 +423,15 @@ lazy val lfcatalog = (project in file("lfcatalog")).
   settings(commonSettings("lfcatalog")).
   settings(
     scalaSource in Compile := baseDirectory.value / "src",
-    publishTo := Some(Resolver.file("file", Utils.deploy.toJava / " main")),
-    deployLFCatalog := {
-      assembly in Compile map Utils.deployTo(Utils.deploy / "lfcatalog" / "lfcatalog.jar")
+    publishTo := Some(Resolver.file("file", utils.value.deploy.toJava / " main")),
+    deployLFCatalog := Def.taskDyn {
+      val jar = (assembly in Compile).value
+      val u = utils.value
+      Def.task {
+        Utils.deployTo(u.deploy / "lfcatalog" / "lfcatalog.jar")(jar)
+      }
     }.value,
-    unmanagedJars in Compile += Utils.lib.toJava / "scala-xml.jar"
+    unmanagedJars in Compile += utils.value.lib.toJava / "scala-xml.jar"
   )

 // =================================
diff --git a/src/project/Utils.scala b/src/project/Utils.scala
index 2f9b94fd4..8f862666e 100644
--- a/src/project/Utils.scala
+++ b/src/project/Utils.scala
@@ -1,9 +1,84 @@
 import java.nio.file.Files
 import java.nio.file.StandardCopyOption._
+import sbt.Keys.packageBin
+import sbt._

 object Utils {
+
+  val utils = settingKey[Utils]("Utils")
+
+  def apply(base: java.io.File) = new Utils(File(base))
+
+  def error(s: String) = throw new Exception(s)
+
+  // ************************************************** deploy-specific code (see also the TaskKey's deploy and deployFull)
+
+  /*
+   * copies files to deploy folder
+   */
+  def deployTo(target: File)(jar: sbt.File): Unit = {
+    Files.copy(jar.toPath, target.toPath, REPLACE_EXISTING)
+    println("copied file: " + jar)
+    println("to file: " + target)
+  }
+
+  /**
+    * packages the compiled binaries and copies to deploy
+    */
+  def deployPackage(name: String) : Def.Initialize[Task[Unit]] = Def.taskDyn {
+    val j = (packageBin in Compile).value
+    val u = utils.value
+    Def.task {
+      deployTo(u.deploy / name)(j)
+    }
+  }
+
+  /**
+    * packages the compiled binaries and copies to deploy
+    */
+  def deployMathHub(target: File): Def.Initialize[Task[Unit]] =
+    packageBin in Compile map {jar => deployTo(target)(jar)}
+
+  // ************************************************** file system utilities
+
+  /** copy a file */
+  def copy(from: File, to: File) {
+    println(s"copying $from to $to")
+    if (!from.exists) {
+      error(s"error: file $from not found (when trying to copy it to $to)")
+    } else if (!to.exists || from.lastModified > to.lastModified) {
+      Files.copy(from.toPath, to.toPath, REPLACE_EXISTING)
+    } else {
+      println("skipped (up-to-date)")
+    }
+    println("\n")
+  }
+
+  /**
+    * Recursively deletes a given folder
+    * @param log
+    * @param path
+    */
+  def delRecursive(log: Logger, path: File): Unit = {
+    def delRecursive(path: File): Unit = {
+      path.listFiles foreach { f =>
+        if (f.isDirectory) delRecursive(f)
+        else {
+          f.delete()
+          log.debug("deleted file: " + path)
+        }
+      }
+      path.delete()
+      log.debug("deleted directory: " + path)
+    }
+    if (path.exists && path.isDirectory) delRecursive(path)
+    else log.warn("ignoring missing directory: " + path)
+  }
+}
+
+class Utils(base: File) {
    /** MMT root directory */
-   val root = File("..").canonical
+   val root = (base / "..").canonical
    /** source folder */
    val src = root / "src"
    /** MMT deploy directory */
@@ -21,33 +96,6 @@ object Utils {
    /** executes a shell command (in the src folder) */
    def runscript(command: String) = sys.process.Process(Seq(command), src.getAbsoluteFile).!!

-   def error(s: String) = throw new Exception(s)
-
-  // ************************************************** deploy-specific code (see also the TaskKey's deploy and deployFull)
-
- /**
-   * packages the compiled binaries and copies to deploy
-   */
-  import sbt.Keys.packageBin
-  import sbt._
-  def deployPackage(name: String): Def.Initialize[Task[Unit]] =
-    packageBin in Compile map {jar => deployTo(Utils.deploy / name)(jar)}
-
- /**
-   * packages the compiled binaries and copies to deploy
-   */
-  def deployMathHub(target: File): Def.Initialize[Task[Unit]] =
-    packageBin in Compile map {jar => deployTo(target)(jar)}
-
-  /*
-   * copies files to deploy folder
-   */
-  def deployTo(target: File)(jar: sbt.File): Unit = {
-    Files.copy(jar.toPath, target.toPath, REPLACE_EXISTING)
-    println("copied file: " + jar)
-    println("to file: " + target)
-  }
-

   // ************************************************** MathHub-specific code

@@ -79,7 +127,7 @@ object Utils {
       settings.get(killJEdit).foreach {x => runscript(x)}
      Thread.sleep(500)
       val fname = settings.get(jeditSettingsFolder).getOrElse {
-        error(s"cannot copy jars because there is no setting '$jeditSettingsFolder' in $settingsFile")
+        Utils.error(s"cannot copy jars because there is no setting '$jeditSettingsFolder' in $settingsFile")
         return
       }
       val jsf = File(fname) / "jars"
@@ -92,47 +140,10 @@ object Utils {
    }
    /** copy all jEdit jars to a directory */
    def copyJEditJars(to: File) {
-      copy(deploy/"mmt.jar", to/"MMTPlugin.jar")
+      Utils.copy(deploy/"mmt.jar", to/"MMTPlugin.jar")
       // all other jars are bundled with the above
       // val jEditDeps = List("scala-library.jar","scala-parser-combinators.jar","scala-reflect.jar","scala-xml.jar","tiscaf.jar")
       // jEditDeps.foreach {f => copy(deploy/"lib"/f, to/f)}
       // copy(deploy/"lfcatalog"/"lfcatalog.jar", to/"lfcatalog.jar")
    }
-
-
-  // ************************************************** file system utilities
-
-   /** copy a file */
-   def copy(from: File, to: File) {
-      println(s"copying $from to $to")
-      if (!from.exists) {
-         error(s"error: file $from not found (when trying to copy it to $to)")
-      } else if (!to.exists || from.lastModified > to.lastModified) {
-         Files.copy(from.toPath, to.toPath, REPLACE_EXISTING)
-      } else {
-         println("skipped (up-to-date)")
-      }
-      println("\n")
-   }
-
-  /**
-    * Recursively deletes a given folder
-    * @param log
-    * @param path
-    */
-  def delRecursive(log: Logger, path: File): Unit = {
-    def delRecursive(path: File): Unit = {
-      path.listFiles foreach { f =>
-        if (f.isDirectory) delRecursive(f)
-        else {
-          f.delete()
-          log.debug("deleted file: " + path)
-        }
-      }
-      path.delete()
-      log.debug("deleted directory: " + path)
-    }
-    if (path.exists && path.isDirectory) delRecursive(path)
-    else log.warn("ignoring missing directory: " + path)
-  }
 }
diff --git a/src/project/build.properties b/src/project/build.properties
index 9f782f704..1fc4b8093 100644
--- a/src/project/build.properties
+++ b/src/project/build.properties
@@ -1 +1 @@
-sbt.version=1.1.1
\ No newline at end of file
+sbt.version=1.2.8
\ No newline at end of file
diff --git a/src/travis.sbt b/src/travis.sbt
index 88fb446d3..a71be9d2e 100644
--- a/src/travis.sbt
+++ b/src/travis.sbt
@@ -2,6 +2,7 @@ import sbt._
 import sbt.Keys._
 import travis.Matrix._
 import travis.Config._
+import Utils.utils

 import scala.io.Source

@@ -86,11 +87,11 @@ travisConfig := {
 val genTravisYML = taskKey[Unit]("Print out travis.yml configuration")
 genTravisYML := {
   // read the prefix and the config
-  val prefix = Source.fromFile(Utils.src / "project" / "prefix.travis.yml").getLines.filter(!_.startsWith("##")).mkString("\n")
+  val prefix = Source.fromFile(utils.value.src / "project" / "prefix.travis.yml").getLines.filter(!_.startsWith("##")).mkString("\n")
   val config = travisConfig.value.serialize

   // and write it into .travis.yml
-  val outFile = Utils.root / ".travis.yml"
+  val outFile = utils.value.root / ".travis.yml"
   IO.write(outFile, prefix + "\n" + config)
   streams.value.log.info(s"Wrote $outFile")
 }