Search code examples
scalaplayframeworksbtscala-reflect

Sbt: call subproject's source code from root project


So my project structure looks like this: My root project contains the settings, tasks and configs. In addition is has a subprojects folder which contains all subprojects. I have created a task on the root project that depends on some code in a subproject X. Is it possible to call a method from subproject x inside that task definition?

My code looks like this :

lazy val rootSettings: Seq[Setting[_]] = Seq (someRootTask :=  { //I need to call an object from a subproject here..}) 

I tried to use the reflection api with no success:

import scala.reflect.runtime.{universe => u }
lazy val docSettings: Seq[Setting[_]] = Seq(
  rootTask := {
      val subproject = baseDirectory.in(playApp).value.getAbsolutePath
      val mirror = u.runtimeMirror(getClass.getClassLoader)
      val clazz = mirror.staticModule(subproject+"/" +"controllers.pckg.obj" ) 

      val cm = mirror.reflectModule(clazz)
      val instanceMirror = mirror.reflect(cm.instance)
      val methodName ="sayHi" 

      val methodSymbol = u.typeOf[instanceMirror.type].declaration(u.newTermName(methodName)).asMethod
      val method = instanceMirror.reflectMethod(methodSymbol)

      method.apply()
  }
)
// still can't point to the object i want to call. 

The code above throws an error. It can't find the object, i know its path but i can't reference to it as package.class from the root project.


Solution

  • Reference scala file from build.sbt

    If I understood correctly your project looks like

    root
      project
        build.properties
        build.sbt
      subproject1
        src
          main
            scala
              com.example.package1
                App.scala
      subproject2
        src
          main
            scala
              com.example.package2
      build.sbt
    

    In project/build.sbt I can write

    Compile / unmanagedSourceDirectories += baseDirectory.value / ".." / "subproject1" / "src" / "main" / "scala"
    

    Suppose subproject1/src/main/scala/com/example/package1/App.scala is

    package com.example.package1
    
    object App {
      def foo(): Unit = println("foo")
    }
    

    Then in root build.sbt I can call foo

    name := "sbtdemo"
    
    version := "0.1"
    
    ThisBuild / scalaVersion := "2.13.4"
    
    lazy val sampleUnitTask = taskKey[Unit]("A sample unit task.")
    
    lazy val rootSettings: Seq[Setting[_]] = Seq(
      sampleUnitTask := {
        com.example.package1.App.foo()
      }
    )
    
    lazy val root = project
      .in(file("."))
      .dependsOn(subproject1, subproject2)
      .settings(rootSettings)
    
    lazy val subproject1 = project
      .in(file("subproject1"))
    
    lazy val subproject2 = project
      .in(file("subproject2"))
    

    If in sbt shell I run root/sampleUnitTask it prints foo.


    I created Play project with sbt new playframework/play-scala-seed.g8. Everything seems to work. I added project/build.sbt and subproject1/src/main/scala/com/example/package1/App.scala as above. Then with the following root build.sbt

    name := """playframeworkdemo"""
    organization := "com.example"
    
    version := "1.0-SNAPSHOT"
    
    lazy val sampleUnitTask = taskKey[Unit]("A sample unit task.")
    
    lazy val rootSettings: Seq[Setting[_]] = Seq(
      sampleUnitTask := {
        com.example.package1.App.foo()
      }
    )
    
    lazy val root = (project in file(".")).enablePlugins(PlayScala)
      .dependsOn(subproject1, subproject2)
      .settings(rootSettings)
    
    ThisBuild / scalaVersion := "2.13.3"
    
    libraryDependencies += guice
    libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test
    
    // Adds additional packages into Twirl
    //TwirlKeys.templateImports += "com.example.controllers._"
    
    // Adds additional packages into conf/routes
    // play.sbt.routes.RoutesKeys.routesImport += "com.example.binders._"
    
    lazy val subproject1 = project
      .in(file("subproject1"))
    
    lazy val subproject2 = project
      .in(file("subproject2"))
    

    root/sampleUnitTask executed in sbt shell prints foo.