はじめに
BounscaleというオートスケールするHeroku Addonを作っています。
Bounscaleは各プログラミング言語/フレームワーク毎にエージェントを提供しています。
現在、Rails、node.jsなどに対応していますが、実験的にPlay Framework2(Scala)への提供を開始しました。
エージェントのjarはPlayの依存ライブラリの記述を追記するだけでインストールできるようにしたいところです。
今回は、Github PagesをMavenリポジトリ代わりに利用して、sbtで各種ファイルを生成してjarを公開したので下記に手順をメモしておきます。
流れ
- Github Pagesのリポジトリを作ってMaven形式のjarやpomなどを公開する場所を確保します
- Play Framework2をインストールして同梱のsbtを利用できるようにします
- ライブラリをビルドします
- ライブラリをgithub pagesに公開します
- Play Framework2のサンプルアプリケーションを作成し、公開したライブラリを利用してみます
sbt
sbtはSimple Build Toolの略だそうで、Scalaのビルドをするためのツールです。
アプリケーションをコンパイルしたり、依存ライブラリをダウンロードして来たりできます。
位置づけ的にはRubyでいうところのbundlerに当たると言えなくもないです。
ただ、プラグインの機構があって、タスクを追加して機能拡張できるので、そういう意味ではrakeに近い面もあります。
今回はsbtを使ってライブラリをビルド、公開します。
ちなみにPlay Frameworkから提供されるplayコマンドでサーバを立ち上げたり、コンパイルしたり、プロジェクト作ったり色々できるんですが、このplayコマンドはsbtにアドオンを追加する形で実装されています。
サンプル
今回はhello.jarというライブラリを公開する事にします。
内容としては本当にシンプルに、Helloというシングルトンオブジェクトが提供されて、Hello.say とやると標準出力に"Hello!"と出るだけのものとします。
package sample.hello
object Hello {
def say(){
println("Hello!")
}
}
GithubをMavenリポジトリにする
MavenのセントラルリポジトリにJarを公開するのはユーザ登録したりチケット登録したり人手の承認をもらったり、なんだかとっても大変そうです。
ファイルをhttpで取得できれば、非公式リポジトリとして公開する事ができるので、今回はその方法でいきます。
置き場はどこでもいいのですが、Github Pagesは無料ですしgitでバージョン管理できるので便利です。この辺りの手順でリポジトリを作ります。
Github Pagesの作成
まずはブラウザでGithubの新規リポジトリページを開いてMaven用のリポジトリを画面から普通に作成します。
今回はsamplerepoという名前で作ります。
続いて、下記の感じでリポジトリをGithub Pagesに対応させます。(とは言っても肝はgh-pagesというブランチを作るところだけですが)
# git clone https://github.com/bounscale/samplerepo.git
Cloning into 'samplerepo'...
warning: You appear to have cloned an empty repository.
# cd samplerepo/
# git checkout --orphan gh-pages
# rm .gitignore
rm: cannot remove `.gitignore': No such file or directory
# echo "My GitHub Page" > index.html
# git add index.html
# git commit -a -m "First pages commit"
[gh-pages (root-commit) 9700796] First pages commit
1 file changed, 1 insertion(+)
create mode 100644 index.html
# git push origin gh-pages
To https://github.com/bounscale/samplerepo.git
* [new branch] gh-pages -> gh-pages
この特殊なgh-pagesブランチにpushしたファイルは下記に公開されます。
http://[ユーザ名].github.io/[リポジトリ名]/
今回で言うと下記です。
http://bounscale.github.io/samplerepo/
試しに作ったindexページはすぐに公開されるようです。
実際にブラウザやcurlかwgetで開いてみて、通常のHTMLページとして参照できるかどうか確認するといいと思います。
# curl http://bounscale.github.io/samplerepo/
My GitHub Page
Play2のインストール
sbt単体でインストールしてもいいのですが、同梱されているsbtの利用も兼ねられますし、後でサンプルアプリケーションを作るので、ここでPlay2をインストールします。手順はこの辺りを参照してください。
(男らしくインストーラも何もなく、解凍してパス通すだけ)
sbtを使えるようにする
Play2同梱のsbtの実行コマンドを作成します。
$PLAY_HOMEにはplayコマンドがありパスを通しているはずなのでここに置きます。
# cd $PLAY_HOME
# echo 'java -Dsbt.ivy.home=$PLAY_HOME/repository -Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=384M -jar $PLAY_HOME/framework/sbt/sbt-launch.jar "$@"' > sbt
# chmod u+x sbt
# sbt
[info] Set current project to default-483e75 (in build file:...)
> exit
sbtプロジェクトの作成
sbtでビルドをするにあたって、どこか適当にビルド用のディレクトリを掘って、下記のように既定のディレクトリ構成を作成します。
build.sbt
project/
lib/
<ビルドに必要なライブラリ>
src/
main/
resources/
<メインの jar に含むファイル>
scala/
<メインの Scala ソース>
java/
<メインの Java ソース>
test/
resources/
<テストの jar に含むファイル>
scala/
<テストの Scala ソース>
java/
<テストの Java ソース>
ディレクトリ構成を自動で作ってくれてもよさそうなものですが、どうもジェネレートのコマンドが見つからないので、手でディレクトリ掘りました。
# mkdir -p project lib src/main/resources src/main/scala src/main/java test/resources test/scala test/java
# touch build.sbt
また、testにはたぶんScalaTestとかspec的なものを置くんだと思いますが、とりあえず今回は何もおいてないです。
ビルド設定
build.sbtにビルドに関する情報を書きます。とりあえず最低限下記を記載しました。
name := "Hello"
version := "0.0.1"
scalaVersion := "2.10.0"
publishTo := Some(Resolver.file("hello",file("/path/to/github/pages/repository/samplerepo"))(Patterns(true, Resolver.mavenSty
leBasePattern)))
publishToはMavenリポジトリに必要なファイル類のジェネレート先を指定します。ここでは先ほど作成したgithubからcloneしてきたディレクトリを指しておきます。
コンパイルできるか確認
続いて先ほど提示したhello.scalaをsrc/main/scalaの下に配置します。
# cp /tmp/hello.scala src/main/scala
とりあえず普通にコンパイルできるか試しておきます。
# cp /tmp/hello.scala src/main/scala
# sbt compile
[info] Set current project to Hello (in build file:/.../samplerepo_build/)
[info] Updating {file:/.../samplerepo_build/}default-3d8bb9...
[info] Resolving org.scala-lang#scala-library;2.10.0 ...
[info] Done updating.
[info] Compiling 1 Scala source to /.../samplerepo_build/target/scala-2.10/classes...
[success] Total time: 78 s, completed Aug 28, 2013 3:35:13 PM
無事コンパイルできているようです。
なお、ビルドに必要なjarがある場合はlibディレクトリに入れておくと勝手にパスが通ります。
ビルド
Mavenの仕様に則ったファイルをジェネレートします。
# sbt publish
[info] Set current project to Hello (in build file:/.../samplerepo_build/)
[info] Packaging /.../samplerepo_build/target/scala-2.10/hello_2.10-0.0.1-sources.jar ...
[info] Done packaging.
[info] Wrote /.../samplerepo_build/target/scala-2.10/hello_2.10-0.0.1.pom
[info] :: delivering :: hello#hello_2.10;0.0.1 :: 0.0.1 :: release :: Wed Aug 28 15:43:56 JST 2013
[info] delivering ivy file to /.../samplerepo_build/target/scala-2.10/ivy-0.0.1.xml
[info] Generating Scala API documentation for main sources to /.../samplerepo_build/target/scala-2.10/api...
model contains 4 documentable templates
[info] Scala API documentation generation successful.
[info] Packaging /.../samplerepo_build/target/scala-2.10/hello_2.10-0.0.1-javadoc.jar ...
[info] Done packaging.
[info] Packaging /.../samplerepo_build/target/scala-2.10/hello_2.10-0.0.1.jar ...
[info] Done packaging.
[info] published hello_2.10 to /.../samplerepo/hello/hello_2.10/0.0.1/hello_2.10-0.0.1.pom
[info] published hello_2.10 to /.../samplerepo/hello/hello_2.10/0.0.1/hello_2.10-0.0.1.jar
[info] published hello_2.10 to /.../samplerepo/hello/hello_2.10/0.0.1/hello_2.10-0.0.1-sources.jar
[info] published hello_2.10 to /.../samplerepo/hello/hello_2.10/0.0.1/hello_2.10-0.0.1-javadoc.jar
[success] Total time: 96 s, completed Aug 28, 2013 3:45:17 PM
publishToで指定したファイルパスにpomやjarなどのファイルがジェネレートされています。
ディレクトリについてはversionとscalaVersionで指定した内容に基づいて生成されます。
こんな感じです。
# tree
.
├── hello
│ └── hello_2.10
│ └── 0.0.1
│ ├── hello_2.10-0.0.1.jar
│ ├── hello_2.10-0.0.1.jar.md5
│ ├── hello_2.10-0.0.1.jar.sha1
│ ├── hello_2.10-0.0.1-javadoc.jar
│ ├── hello_2.10-0.0.1-javadoc.jar.md5
│ ├── hello_2.10-0.0.1-javadoc.jar.sha1
│ ├── hello_2.10-0.0.1.pom
│ ├── hello_2.10-0.0.1.pom.md5
│ ├── hello_2.10-0.0.1.pom.sha1
│ ├── hello_2.10-0.0.1-sources.jar
│ ├── hello_2.10-0.0.1-sources.jar.md5
│ └── hello_2.10-0.0.1-sources.jar.sha1
└── index.html
リリース
ジェネレートしたファイルをGithub Pagesに公開します。
これは単にgitでpushするだけです。
git add .
git commit -m "Release 0.0.1"
git push origin gh-pages
これでjarが公開されました。
pushした後、ブラウザなどから該当のファイルがダウンロードできるか確認するといいと思います。
# wget http://bounscale.github.io/samplerepo/hello/hello_2.10/0.0.1/hello_2.10-0.0.1.jar
--2013-08-28 06:53:40-- http://bounscale.github.io/samplerepo/hello/hello_2.10/0.0.1/hello_2.10-0.0.1.jar
....
2013-08-28 06:53:40 (173 MB/s) - `hello_2.10-0.0.1.jar' saved [1506/1506]
サンプルPlayプロジェクトの作成
公開したjarをアプリケーションから利用してみましょう。
Play Frameworkのアプリケーションを作成します。名前はplaysampleとします。Scalaアプリケーションとして作ってください。
# play new playsample
_ _
_ __ | | __ _ _ _| |
| '_ \| |/ _' | || |_|
| __/|_|\____|\__ (_)
|_| |__/
play! 2.1.3 (using Java 1.7.0_25 and Scala 2.10.0), http://www.playframework.org
The new application will be created in /root/work/play_work/play_app/playsample
What is the application name? [playsample]
>
Which template do you want to use for this new application?
1 - Create a simple Scala application
2 - Create a simple Java application
> 1
OK, application playsample is created.
Have fun!
dependencyの記述
生成されたPlay Frameworkのプロジェクトに先ほど作成したライブラリの依存関係を記述します。
記述する場所はproject/Build.scalaです。
import sbt._
import Keys._
import play.Project._
object ApplicationBuild extends Build {
val appName = "playsample"
val appVersion = "1.0-SNAPSHOT"
val appDependencies = Seq(
// 依存ライブラリの追加
"hello" % "hello_2.10" % "0.0.1"
)
val main = play.Project(appName, appVersion, appDependencies).settings(
// Mavenリポジトリの追加
resolvers += "Maven Repository on Github" at "http://bounscale.github.io/samplerepo/"
)
}
利用するライブラリの名前の記述と、取得しに行くMavenリポジトリの情報をリゾルバとして追加しています。
アプリケーションの初回起動時には不足している依存ライブラリが自動的にダウンロードされます。
起動してみると下記の感じでGithub上のjarファイルが正常にダウンロードされてとりこまれています。
# play run
...
[info] downloading http://bounscale.github.io/samplerepo/hello/hello_2.10/0.0.1/hello_2.10-0.0.1.jar ...
[info] [SUCCESSFUL ] hello#hello_2.10;0.0.1!hello_2.10.jar (1048ms)
[info] Done updating.
...
ライブラリを使ってみる
Helloクラスを利用する記述をコントローラに書いてみます。
app/controllers/Application.scalaを下記のように更新します。
package controllers
import sample.hello.Hello //追加
import play.api._
import play.api.mvc._
object Application extends Controller {
def index = Action {
Hello.say //追加
Ok(views.html.index("Your new application is ready."))
}
}
Playを起動してアクセスしてみると、Helloクラスからのログが出ていることが分かります。
# play run
...
[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
(Server started, use Ctrl+D to stop and go back to the console...)
[info] Compiling 5 Scala sources and 1 Java source to /root/work/play_work/play_app/playsample/target/scala-2.10/classes...
[info] play - Application started (Dev)
Hello!
おまけ:Herokuでも動くよ
Herokuは色々な言語/フレームワークが動くPaaSです。Play2にも対応しています。
Herokuはよくできているので、Play2っぽいプロジェクトを自動で判断して適切なbuildpackを選択してくれます。
今回のサンプルをpushしたところ、依存ライブラリの情報も勝手に見てくれて、普通にGithub Pagesからダウンロードして利用してくれました。
# play clean compile stage
# echo "web: target/start -Dhttp.port=\$PORT \$JAVA_OPTS" > Procfile
# git init
# git add .
# git commit -m "Initial commit"
# heroku create sample-app-play
# git push heroku master
Counting objects: 28, done.
Compressing objects: 100% (21/21), done.
Writing objects: 100% (28/28), 37.00 KiB, done.
Total 28 (delta 0), reused 0 (delta 0)
-----> Play 2.x - Scala app detected
-----> Installing OpenJDK 1.6...done
-----> Building app with sbt
-----> Running: sbt clean compile stage
...
[info] downloading http://bounscale.github.io/samplerepo/hello/hello_2.10/0.0.1/hello_2.10-0.0.1.jar ...
[info] [SUCCESSFUL ] hello#hello_2.10;0.0.1!hello_2.10.jar (59ms)
...
-----> Launching... done, v6
http://sample-app-play.herokuapp.com deployed to Heroku
...
アクセスしてログを見てみるとたしかにHelloオブジェクトのメソッドがよばれています。
# heroku logs
...
2013-08-28T07:31:26.777738+00:00 app[web.1]: Hello!
...
終わりに
Rubyのgemや、node.jsのnpmに比べて自分でリポジトリ作ったりビルドが必要だったり諸々面倒ではありました。
でもちょっと頑張ればライブラリを自由に公開できます。
Mavenのセントラルリポジトリにもっと自由にあげられればいいのに、と思いますが、色々大人の事情があるのでしょうか。
完全プロ志向のJavaらしいといえばJavaらしいですけどね。