Publishing Play 2 modules on github

Now you’ve doubtless followed my earlier guides on writing modules for Play 2, I imagine you’re keen to show your coding chops to the world. The problem is – and this is quite a big one, I’m afraid – there is no place to publish your modules to. When I first starting writing Play modules, the repository was already set-up and ready to go – however, it used a custom format (state of the art download-and-unzip). This changed to some degree with Play 1.2, but I have to say I’m not a fan of that dependency management mechanism; I’m just too damn old to learn these shiny new things.

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.

Leave a comment ?

15 Comments.

  1. benoit.ponsero

    Great tutorial.

    Do you know if it is possible to have private pages ?

  2. I haven’t looked into that. Maybe if you create a private repository called <your username>.github.com?

  3. Hongtium » 2012-04 Scala新资源 - pingback on May 20, 2012 at 01:30
  4. Hi steve,
    of course me again who has a problem ;-)
    My super-great new PDF module does not resolve from github.
    Attention! I have octopress installed on my github pages so a sinatra server delivers all files. AND I have a CNAME to http://www.joergviola.de.
    Nevertheless I can successfully e.g.
    curl “http://joergviola.github.com/releases/pdf/pdf_2.9.1/0.1/jars/pdf_2.9.1.jar.md5″
    My Build.scala reads:

    import sbt._
    import Keys._
    import PlayProject._
    import de.johoop.jacoco4sbt.JacocoPlugin._

    object ApplicationBuild extends Build {

    val appName = “66and33-web”
    val appVersion = “1.0-SNAPSHOT”

    lazy val s = Defaults.defaultSettings ++ Seq(jacoco.settings:_*)

    val appDependencies = Seq(
    “postgresql” % “postgresql” % “8.4-702.jdbc4″,
    “org.apache.commons” % “commons-email” % “1.2”,
    “org.apache.lucene” % “lucene-core” % “3.6.0”,
    “org.apache.lucene” % “lucene-highlighter” % “3.6.0”,
    “pdf” % “pdf_2.9.1″ % “0.1”,
    “org.jsoup” % “jsoup” % “1.6.2”
    )
    val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA, settings = s).settings(
    resolvers += Resolver.url(“My GitHub Play Repository”, url(“http://www.joergviola.de/releases/”))(Resolver.ivyStylePatterns),
    templatesImport += “util._”,
    parallelExecution in jacoco.Config := false
    )

    }

    Of course, I have a special situation here, but would you care to tell me any ideas you have?

  5. Hmm, it’s working fine for me. The only thing I changed was the quote marks on the code I copied from your comment, so

    “pdf” % “pdf_2.9.1″ % “0.1″
    

    became

    "pdf" % "pdf_2.9.1" % "0.1"
    

    and the same for the resolver declaration, and the result is…

    Here are the resolved dependencies of your application:
    
    +-------------------------------------------------------------------+---------------------------------------------------------+------------------------------+
    | ←[36mModule←[0m                                                            | ←[36mRequired by←[0m                                             | ←[36mNote←[0m                         |
    +-------------------------------------------------------------------+---------------------------------------------------------+------------------------------+
    | ←[32mpdf:pdf_2.9.1:0.1←[0m                                                 | ←[37mpdf-test:pdf-test_2.9.1:1.0-SNAPSHOT←[0m                    | ←[37mAs pdf_2.9.1-0.1.jar←[0m         |
    +-------------------------------------------------------------------+---------------------------------------------------------+------------------------------+
    | ←[32morg.xhtmlrenderer:core-renderer:R8←[0m                                | ←[37mpdf:pdf_2.9.1:0.1←[0m                                       | ←[37mAs core-renderer-R8.jar←[0m      |
    +-------------------------------------------------------------------+---------------------------------------------------------+------------------------------+
    | ←[32mcom.lowagie:itext:2.0.8←[0m                                           | ←[37morg.xhtmlrenderer:core-renderer:R8←[0m                      | ←[37mAs itext-2.0.8.jar←[0m           |
    +-------------------------------------------------------------------+---------------------------------------------------------+------------------------------+
    | ←[32mbouncycastle:bcprov-jdk14:138←[0m                                     | ←[37mcom.lowagie:itext:2.0.8←[0m                                 | ←[37mAs bcprov-jdk14-138.jar←[0m      |
    +-------------------------------------------------------------------+---------------------------------------------------------+------------------------------+
    | ←[32mbouncycastle:bcmail-jdk14:138←[0m                                     | ←[37mcom.lowagie:itext:2.0.8←[0m                                 | ←[37mAs bcmail-jdk14-138.jar←[0m      |
    +-------------------------------------------------------------------+---------------------------------------------------------+------------------------------+
    | ←[32mplay:play_2.9.1:2.0.1←[0m                                             | ←[37mpdf:pdf_2.9.1:0.1←[0m                                       | ←[37mAs play_2.9.1.jar←[0m            |
    | ←[32m←[0m                                                                  | ←[37mpdf-test:pdf-test_2.9.1:1.0-SNAPSHOT←[0m                    | ←[37m←[0m                             |
    +-------------------------------------------------------------------+---------------------------------------------------------+------------------------------+
    
  6. Ah – thanks – my bad.
    After publish-local, in order to try the github download, I only removed the files from repository/local. Turned out that is not sufficient, I had to remove corresponding files from repository/cache also.
    What is the correct way to remove a release from the local repo?

    But – it’s working now – nice solution – thanks a lot!

  7. I normally just use rm -rf, and let the dependency manager handle the rest.

  8. Hi,
    All works for me like a charm when I declared the repo git with my user name (like specified url(“http://.github.com/releases/”))(Resolver.ivyStylePatterns)), but when created a new repository, let’s say http://newRepo.github.com/releases/“….
    the dependency was unresolved.

    Please Steve can you help me,

  9. The GitHub subdomain is based on your username, so it has to match, otherwise there’s no way to resolve exactly which git repo you’re pointing to. If you want to have multiple repositories, I suggest you create a new layer of directories above release, so for example

    username.github.com
     |-repo1
        |-releases
        |-snapshots
     |-repo2
        |-releases
        |-snapshots
     |-...and so on
    

    The URL in Build.scala would then be (as required):

    val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings(
        resolvers += Resolver.url("Repo 1", url("http://username.github.com/repo1/releases/"))(Resolver.ivyStylePatterns),
        resolvers += Resolver.url("Repo 2", url("http://username.github.com/repo2/releases/"))(Resolver.ivyStylePatterns)
    )
    
  10. Thaks Steve :smile:

  11. Contributing a Pusher module to the Play framework - Tindr.co - pingback on October 11, 2012 at 15:23
  12. Thanks, exactly what I needed!

  13. You can publish to the SBT Community Repository. It is free and open to all. Follow the instructions at http://www.scala-sbt.org/release/docs/Community/Community-Plugins.html

    I have published several libraries to it, and have found that it works quite well, and the process is simpler that what is described here. You might want to try using the community repository, and update these instructions after.

  14. trying to add the java r2 deadbolt repository to my Build.scala but keep getting error that dependency missing. browsing to http://schaloner.github.com/releases/ give a 404. Sumfink wrong?

  15. hi,
    thank you so much for this tutorial!
    I’m having a problem with this task:
    “creating a repository called foo.github.com”

    How is it supposed to be done? Github allows me to create only repos with github.com/myname/repo_name

    Thank you!
    pietro

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Trackbacks and Pingbacks: