Using GitHub projects as Scala library dependencies with sbt and sbteclipse

Okay, this is pretty cool. With sbt, you can magically refer to dependencies that are set up as GitHub projects, and those projects don't need to have a jar file, they can just be a source code library. This means you can save your Scala libraries as source code to GitHub (like you normally would), then pull them into your other Scala projects simply by referencing them in your build.sbt file.

Assuming you're comfortable with sbt, here's a quick six-step example that shows how to pull a GitHub library into a simple Scala project:

1) First, install SBT (0.11.x or newer) if you don't already have it.

2) Once that's done, create an empty directory named something like "SbtGithubTest", then cd into that directory.

3) Put these contents into a build.sbt file in that directory:

name := "SoundPlayerTest"

version := "1.0"

scalaVersion := "2.9.1"

This is a normal, basic build.sbt file.

4) Put these contents into a file named Test.scala in that directory:

package foobar

import com.alvinalexander.sound._

object TestSbtGithub extends App {

  // you'll need your own short sound clip
  val testClip = "/Users/al/Sarah/plugins/DDRandomNoise/HAL9000/this-mission-too-important.wav"
  val player = SoundFilePlayer.getSoundFilePlayer(testClip)

  try {
    player.play
    // necessary b/c play() doesn't block, and i didn't want to add a
    // listener to this short test
    Thread.sleep(5000)
  } catch {
    case e:Exception => println(e.getMessage)
  }

}

5) Create a subdirectory named project, and put these contents in a file named Build.scala in that subdirectory:

import sbt._

object MyBuild extends Build {

  lazy val root = Project("root", file(".")) dependsOn(soundPlayerProject)
  lazy val soundPlayerProject = RootProject(uri("git://github.com/alvinj/SoundFilePlayer.git"))

}

As you can guess by looking at that code, this is the code that references the GitHub project you want to use as a dependency. In this case I want to use the source from my SoundFilePlayer project on GitHub.

6) Now use sbt run to compile and run your Scala project:

$ sbt run

You should see sbt do a lot of work on your behalf, and after a lot of work and output related to the magic that it's doing for you, it will run your test program, with the last lines of output looking like this:

[info] Set current project to root (in build file:/Users/al/Projects/Scala/Tests/SbtGithubTest/)
[info] Compiling 1 Scala source to /Users/al/Projects/Scala/Tests/SbtGithubTest/target/scala-2.9.1.final/classes...
[info] Running foobar.TestSbtGithub 
[success] Total time: 15 s, completed Jul 1, 2012 8:28:58 AM

Pretty cool, eh?

Referencing multiple GitHub projects

As you start adding more GitHub projects as library dependencies to your project, your project/Build.scala file will change shape a little bit. Here's mine with two library dependencies:

import sbt._

object MyBuild extends Build {

  lazy val root = Project("root", file("."))
                    .dependsOn(soundPlayerProject)
                    .dependsOn(appleScriptUtils)

  lazy val soundPlayerProject = RootProject(uri("git://github.com/alvinj/SoundFilePlayer.git"))
  lazy val appleScriptUtils   = RootProject(uri("git://github.com/alvinj/AppleScriptUtils.git"))

}

As you can see, you can just keep chaining the "dependsOn" references.

Using this with Eclipse

For my work today I also installed the sbteclipse plugin, and just generated my Eclipse files with that plugin. By looking at the .classpath file that it generates, you can see some of the magic sbt did for you when pulling in the GitHub project:

<classpath>
  <classpathentry output="target/scala-2.9.1/classes" path="src/main/scala" kind="src"></classpathentry>
  <classpathentry output="target/scala-2.9.1/classes" path="src/main/java" kind="src"></classpathentry>
  <classpathentry output="target/scala-2.9.1/test-classes" path="src/test/scala" kind="src"></classpathentry>
  <classpathentry output="target/scala-2.9.1/test-classes" path="src/test/java" kind="src"></classpathentry>
  <classpathentry path="/Users/al/.sbt/staging/4a78a580940b3d38ed80/lib/basicplayer3.0.jar" kind="lib"></classpathentry>
  <classpathentry path="/Users/al/.sbt/staging/4a78a580940b3d38ed80/lib/commons-logging-api.jar" kind="lib"></classpathentry>
  <classpathentry path="/Users/al/.sbt/staging/4a78a580940b3d38ed80/lib/jl1.0.1-orig.jar" kind="lib"></classpathentry>
  <classpathentry path="/Users/al/.sbt/staging/4a78a580940b3d38ed80/lib/kj_dsp1.1.jar" kind="lib"></classpathentry>
  <classpathentry path="/Users/al/.sbt/staging/4a78a580940b3d38ed80/lib/mp3spi1.9.4.jar" kind="lib"></classpathentry>
  <classpathentry path="/Users/al/.sbt/staging/4a78a580940b3d38ed80/lib/tritonus_share.jar" kind="lib"></classpathentry>
  <classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"></classpathentry>
  <classpathentry exported="true" path="/Sound Tests" kind="src" combineaccessrules="false"></classpathentry>
  <classpathentry path="org.eclipse.jdt.launching.JRE_CONTAINER" kind="con"></classpathentry>
  <classpathentry path="bin" kind="output"></classpathentry>
</classpath>

To install the sbteclipse plugin, please see this sbteclipse page.

More information

I'll write more about this as I move more of my own library projects to GitHub, but in the meantime, you can learn more about the GitHub syntax at this sbt documentation page. (Search for "github" on that page for the examples.)