scalasbtproject-structure

Why can't sbt/assembly find the main classes in a project with multiple subprojects?


We're combining two of our Scala projects because they share a lot of functionality. I'm setting up the new repo, and have the following build.sbt:

ThisBuild / scalaVersion := "2.13.10"

libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % Test

lazy val project1 = (project in file("project1"))
  .dependsOn(core)
  .settings(
    name := "Project 1",
    assembly / mainClass := Some("project1.Main"),
    assembly / assemblyJarName := "project1.jar",
  )

lazy val project2 = (project in file("project2"))
  .dependsOn(core)
  .settings(
    name := "Project 2",
    assembly / mainClass := Some("project2.Main"),
    assembly / assemblyJarName := "project2.jar",
  )

lazy val core = (project in file("core"))
  .settings(
    name := "Core",
  )

Basically, Project 1 and Project 2 should both depend on Core, but not on each other.

My src directory looks like this:

src
  main/scala
    core
      Core.scala
    project1
      Main.scala
    project2
      Main.scala
  test/scala
    core
      TestCore.scala
    project1
      TestProject1.scala
    project2
      TestProject2.scala

For the moment, Core.scala is:

package core

object Core {
    def hello() = {
        println("hello from core!")
    }
}

project1/Main.scala is:

package project1

import core.Core

object Main {
    def main(args: Array[String]): Unit = {
        hello()
        Core.hello()
    }

    def hello() = { println("hello from project 1!") }
}

and similarly, project2/Main.scala is:

package project2

import core.Core

object Main {
    def main(args: Array[String]): Unit = {
        hello()
        Core.hello()
    }

    def hello() = { println("hello from project 2!") }
}

I can run both of these main classes in the sbt terminal from the project root, either by entering run and selecting the main class I'd like to use, or entering runMain <main_class> (the two main classes are project1.Main and project2.Main). However, if I switch to one of the subprojects (for example, by entering project project1) and then enter run, sbt is no longer able to detect a main class. Additionally, if I compile the project using assembly, I can't run either project1.jar or project2.jar because they can't find their main classes either. Why exactly is this happening?

Furthermore, I noticed that I can still import project2 into Project 1 and project1 into Project 2, even though I've only set dependsOn for core. Is this expected behavior?


Solution

  • You're confusing completely different notions, subprojects and packages.

    If you want to have subprojects

    lazy val project1 = (project in file("project1"))
    
    lazy val project2 = (project in file("project2"))
    
    lazy val core = (project in file("core"))
    

    then the project structure should be completely different

    core
      src
        main/scala
          Core.scala
        test/scala
          TestCore.scala
    project1
      src
        main/scala
          Main.scala
        test/scala
          TestProject1.scala
    project2
      src
        main/scala
          Main.scala
        test/scala
          TestProject2.scala
    

    https://www.scala-sbt.org/1.x/docs/Directories.html

    https://www.scala-sbt.org/1.x/docs/Multi-Project.html

    If you put

    package core
    
    object Core 
    
    package project1
    
    object Main
    
    package project2
    
    object Main
    

    to the same directory src/main/scala then core, project1, project2 are packages, not subprojects and you have the only project.

    Surely you can have both subprojects and packages. Then the project structure can be

    core                    <-- this is a subproject
      src
        main/scala
          core              <-- this is a package
            Core.scala
        test/scala
          core
            TestCore.scala
    project1
      src
        main/scala
          project1
            Main.scala
        test/scala
          project1
            TestProject1.scala
    project2
      src
        main/scala
          project2
            Main.scala
        test/scala
          project2
            TestProject2.scala
    

    but generally there is no connection between subproject names and package names.

    In principle you can modify the project structure so that it will differ from default

    https://www.scala-sbt.org/1.x/docs/Howto-Customizing-Paths.html

    if I switch to one of the subprojects (for example, by entering project project1) and then enter run, sbt is no longer able to detect a main class

    That's because now your subprojects project1, project2, core (declared in build.sbt) are empty. You put everything to the single directory src/main/scala that belongs to

    lazy val root = (project in file("."))
    

    which is created implicitly by default if you don't create it manually.

    Maven: difference module - java package

    Is a Maven project necessarily a Java package?

    Whats the difference between package and groupId in maven