Publishing Play 2 modules on github
With Play 2, things swung around again and the dependency mechanism is compliant with Maven and Ivy. I can hear enterprises up and down the planet rejoicing.
However, this still doesn’t help you achieve your well-deserved glory and fame, unless you happen to host your own Maven/Ivy repo or feel like hacking the auto-generated artifact models that Play generates. Help is hand, until Play 2 modules have an official release mechanism, using GitHub’s Pages feature. Very simply, we’ll use Play’s publish-local task to generate all the files we need in the correct directory structure, and then copy that structure as-is directly into GitHub pages. A publish in this manner can be done in less than a minute, after you’ve done the initial set-up in GitHub.
Creating the repository in GitHub
If your GitHub user name is, for example, foo, then creating a repository called foo.github.com hooks you into Pages – what you commit here will be available at http://foo.github.com. There’s no GitHubby goodness when it comes to displaying files here – what you commit is what’s available in its native form. Commit a HTML file (let’s call it bar.html), and point your browser at http://foo.github.com/bar.html and you’ll see the rendered HTML – not the source of it. This makes it ideal for exposing files that you want represented in their raw form – say, the jar file containing your Play 2 module.
So, go ahead and create a .github.com repository and clone it to your workspace, and cd into it. In here, you can replicate (kind of – see later) a meven repository.
To keep things nice, the first directory you should create is releases. If you also want to add a snapshot directory, do it at this level.
Note: When you first create the repository in GitHub, it can take up to 10 minutes for it to become available. If you haven’t changed your email preferences, you should receive a notification when it is ready. This doesn’t stop you committing to the repository in the meantime.
Publish the files locally
Let’s assume your module name is hurdy as the artifact name. Your Build.scala looks like this:
import sbt._ import Keys._ import PlayProject._ object ApplicationBuild extends Build { val appName = "hurdy" val appVersion = "1.0" val appDependencies = Seq( ) val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings( ) }
(The mainLang value may be something else, depending on your primary language).
In the root of your module, start up Play and publish it locally:
steve@hex:/tmp/hurdy$ play [info] Loading project definition from /tmp/hurdy/project [info] Set current project to hurdy (in build file:/tmp/hurdy/) _ _ _ __ | | __ _ _ _| | | '_ \| |/ _' | || |_| | __/|_|\____|\__ (_) |_| |__/ play! 2.0, http://www.playframework.org > Type "help play" or "license" for more information. > Type "exit" or use Ctrl+D to leave this console. [hurdy] $ clean [success] Total time: 0 s, completed Apr 21, 2012 9:49:00 AM [hurdy] $ compile [info] Updating {file:/tmp/hurdy/}hurdy... [info] Done updating. [info] Compiling 4 Scala sources and 2 Java sources to /tmp/hurdy/target/scala-2.9.1/classes... [success] Total time: 7 s, completed Apr 21, 2012 9:49:08 AM [hurdy] $ publish-local [info] Packaging /tmp/hurdy/target/scala-2.9.1/hurdy_2.9.1-1.0-SNAPSHOT-sources.jar ... [info] Done packaging. [info] Wrote /tmp/hurdy/target/scala-2.9.1/hurdy_2.9.1-1.0-SNAPSHOT.pom [info] :: delivering :: hurdy#hurdy_2.9.1;1.0-SNAPSHOT :: 1.0-SNAPSHOT :: release :: Sat Apr 21 09:49:12 CEST 2012 [info] delivering ivy file to /tmp/hurdy/target/scala-2.9.1/ivy-1.0-SNAPSHOT.xml [info] Generating API documentation for main sources... [info] Packaging /tmp/hurdy/target/scala-2.9.1/hurdy_2.9.1-1.0-SNAPSHOT.jar ... [info] Done packaging. model contains 23 documentable templates [info] API documentation generation successful. [info] Packaging /tmp/hurdy/target/scala-2.9.1/hurdy_2.9.1-1.0-SNAPSHOT-javadoc.jar ... [info] Done packaging. [info] published hurdy_2.9.1 to /home/steve/development/play/play-2.0/framework/../repository/local/hurdy/hurdy_2.9.1/1.0-SNAPSHOT/poms/hurdy_2.9.1.pom [info] published hurdy_2.9.1 to /home/steve/development/play/play-2.0/framework/../repository/local/hurdy/hurdy_2.9.1/1.0-SNAPSHOT/jars/hurdy_2.9.1.jar [info] published hurdy_2.9.1 to /home/steve/development/play/play-2.0/framework/../repository/local/hurdy/hurdy_2.9.1/1.0-SNAPSHOT/srcs/hurdy_2.9.1-sources.jar [info] published hurdy_2.9.1 to /home/steve/development/play/play-2.0/framework/../repository/local/hurdy/hurdy_2.9.1/1.0-SNAPSHOT/docs/hurdy_2.9.1-javadoc.jar [info] published ivy to /home/steve/development/play/play-2.0/framework/../repository/local/hurdy/hurdy_2.9.1/1.0-SNAPSHOT/ivys/ivy.xml [success] Total time: 3 s, completed Apr 21, 2012 9:49:15 AM
Move the published files into GitHub
The files have been published to ${PLAY_HOME}/repository/local, and are contained in the hurdy directory. Move this entire directory to your new git repository
cp -rv ${PLAY_HOME}/repository/local/hurdy <your username>.github.com/releases
Change directory to .github.com , and add all the files to the git repository, commit and push them.
steve@hex:/tmp/schaloner.github.com$ git add . steve@hex:/tmp/schaloner.github.com$ git commit -m "Added release 1.0 to repository" steve@hex:/tmp/schaloner.github.com$ git push -u origin master
Use the dependency
Create a new project (or open up an old one), and add the dependency and its location.
object ApplicationBuild extends Build { val appName = "my-cool-project" val appVersion = "2.1" val appDependencies = Seq( "hurdy" %% "hurdy" % "1.0" ) val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings( resolvers += Resolver.url("My GitHub Play Repository", url("http://<your username>.github.com/releases/"))(Resolver.ivyStylePatterns) ) }
Note: Play has published the files in an Ivy style pattern, so this needs to be specified in your Build.scala
Start Play in your application root, and run the “dependencies” task. If all goes well – and I’m sure you’ll let me know if it doesn’t – your module will be pulled from GitHub and made available to your application.
Specifiying an organisation
Because there’s no organisation specified in Build.scala, the organisation (repository/local/hurdy/hurdy_2.9.1) is taken to be the same as the module name. If you want an organisation, add it in the main definition:
val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings( organization := "com.example" )
Now, when you publish locally the files will be placed in repository/local/com.example/hurdy_2.9.1. In this case, it’s the com.example directory that should copied from the local repository to your GitHub repo. The Build.scala of any application using your dependency will be similarly different:
val appDependencies = Seq( "com.example" %% "hurdy" % "1.0" )
Real-world proof this works
Deadbolt 2 is currently available using this mechanism, using http://schaloner.github.com as the repository. Here, there are release and snapshot versions so the Build scala of the sample applications looks like this:
import sbt._ import Keys._ import PlayProject._ object ApplicationBuild extends Build { val appName = "deadbolt-usage" val appVersion = "1.1.3-SNAPSHOT" val appDependencies = Seq( "be.objectify" %% "deadbolt-2" % "1.1.3-SNAPSHOT" ) val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings( // Change this to point to your local play repository resolvers += Resolver.url("Objectify Play Repository", url("http://schaloner.github.com/releases/"))(Resolver.ivyStylePatterns), resolvers += Resolver.url("Objectify Play Repository", url("http://schaloner.github.com/snapshots/"))(Resolver.ivyStylePatterns) ) }
If I change the required version from 1.1.3-SNAPSHOT (which is in the snapshots/ directory) to 1.1.3 (which will be in the releases/ directory), the dependency will be resolved correctly.
Reference: Publishing Play 2 modules on github from our JCG partner Steve Chaloner at the Objectify blog.