Using SBT to manage projects that contain both Scala and Python

Using SBT to manage projects that contain both Scala and Python

In a current project, I've built Python code for interacting with a specific datasource; now, I'm working on building a Scala version.
I've rearranged things so that all of the Python code lives in src/main/python within the SBT project for my Scala code, but this got me thinking: Is there any nice way to integrate the project management between the two?  To set up SBT so that I can run my Python distutils installation/sdist generation or sphinx document generation as SBT tasks?
Or, more generally: Is there a standard method for running arbitrary system tasks by way of SBT?


Answer 1:

From the docs (

sbt includes a process library to simplify working with external
processes. The library is available without import in build
definitions and at the interpreter started by the consoleProject task.

To run an external command, follow it with an exclamation mark !:

"find project -name *.jar" !

Answer 2:

To run Python unit tests of python code with SBT test tasks I did this in build.sbt:

//define task that should be run with tests.
val testPythonTask = TaskKey[Unit]("testPython", "Run python tests.")

val command = "python3 -m unittest"
val workingDirectory = new File("python/working/directory")

testPythonTask := {
  val s: TaskStreams = streams.value"Executing task testPython")
    // optional
    // optional system variables
    "CLASSPATH" -> "path/to.jar",
    "OTHER_SYS_VAR" -> "other_value") ! s.log

//attach custom test task to default test tasks
test in Test := {
  (test in Test).value

testOnly in Test := {
  (testOnly in Test).value

Answer 3:

You can create a python task which zips the source files. This example depends on the assembly task:

  lazy val pythonAssembly = TaskKey[Unit]("pythonAssembly", "Zips all files in src/main/python")

  lazy val pythonAssemblyTask = pythonAssembly := {
    val baseDir = sourceDirectory.value
    val targetDir = assembly.value.getParentFile.getParent
    val target = new File(targetDir + s"/python/rfu-api-client-python-${Commons.appVersion}.zip")
    val pythonBaseDir = new File(baseDir + "/main/python")
    val pythonFiles = Path.allSubpaths(pythonBaseDir)

    println("Zipping files in " + pythonBaseDir)
    pythonFiles foreach { case (_, s) => println(s) }, target)
    println(s"Created $target")