この文章は、主にgitbucketやgithubが大好き、俺専用githubを作りたい、という人たちに向けて書いた。
向いてない人はきちんと詳細・原理まで説明しないと使えない人、向いている人はなんか分からないけれどコピペしたら動いたからがんがん機能かくで!という人。
java は知っているけれど scalaは知らない人に向けて書いていますがscalaの説明は最低限しかしないので、別途リファレンスや入門書などを参考にすること。javaもscalaも知らなくてもrubyやphpやjavascriptを知っていれば何とかなるはず。
webアプリケーション開発経験が無いとつらい。
gitbucket とは
gitbucket とは github 上で開発されているオープンソースの github っぽいgitリポジトリサーバです。
star が 6000 もついてる!
開発者は日本人が中心で、日本語のチャットルームで相談できる!
インストールが超簡単。開発も簡単
利用者向けのドキュメントは公式のwiki、開発者用のドキュメントはソースのdocsディレクトリにある。
やってみよう
git のインストール、java/jdk (1.8) のインストール、java, javac にパスを通す(←説明しませんので分からなければググって)。ちなみに私は
SET JAVA_HOME=c:\Program Files\Java\jdk1.8.0_92
SET PATH=%JAVA_HOME%\bin;%PATH%
SET _JAVA_OPTIONS="-Dinput.encoding=Cp1252"
SET JREBEL=c:/bin/jrebel/jrebel.jar
のような環境設定用のバッチファイルを用意している。
できたら、シェル(bash, cmd.exe) から
# ソースを取り込んで
git clone https://github.com/gitbucket/gitbucket.git
# ソースディレクトリに移動して
cd gitbucket
# 実行!
sbt ~jetty:start
# ↑ bash の場合は sbt.sh を実行
すると
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
Listening for transport dt_socket at address: 5005
[info] Loading global plugins from C:\Users\nazoking\.sbt\0.13\plugins
[info] Loading project definition from C:\Users\nazoking\git\gitbucket\project
:
のようなログが流れて、たくさんのライブラリをダウンロードし、たくさんのファイルをscalaのコンパイルする。結構(初回は特に)時間がかかるので、アニメでも見て気を紛らわせましょう。Aパートが終わる頃には
:
2017-12-29 17:27:49.625:WARN:oejsh.RequestLogHandler:main: !RequestLog
2017-12-29 17:27:49.685:INFO:oejs.ServerConnector:main: Started ServerConnector@7c6748bc{HTTP/1.1}{0.0.0.0:8080}
2017-12-29 17:27:49.685:INFO:oejs.Server:main: Started @8625ms
のようなメッセージが見えるはずだ。 http://localhost:8080/
にアクセスしてみよう。gitbucket が走ってる!超簡単。
普通にリポジトリを作ったり、アカウントを発行したりできる。
更に、この状態でサーバは自働でリロードされる。試しに src/main/twirl/gitbucket/core/main.scala.html
のタイトル等を書き換えて、ブラウザをリロードしてみよう。修正が反映されるはずだ。
とりあえず以上で、 gitbucket を修正・改造することが可能だ。sbt ~jetty:start
は ENTER 等で終了することができる。
sbt インタラクティブコンソール
OSのシェルから
sbt ~jetty:start
と打ち込んだとき、 sbt というプログラムが動いている。sbt とは java で言うところの maven + ant 、c で言うところの make +ライブラリ管理機能、ruby で言うところの gems + bundler + rake のようなものだ。
そして、インタラクティブコンソール(シェルのようなもの)も持っている。
コマンドラインからオプションなしで
sbt
とだけ打ち込んでみよう。
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
Listening for transport dt_socket at address: 5005
[info] Loading global plugins from C:\Users\nazoking\.sbt\0.13\plugins
[info] Loading project definition from C:\Users\nazoking\git\gitbucket\project
[info] Set current project to gitbucket (in build file:/C:/Users/nazoking/git/gitbucket/)
>
のような画面になって入力をうながすプロンプトが出るかと思う。これで、 sbt のコンソールに入れた。ここで jetty:start
と打ち込んで ENTER すると、最初にやったようにサーバが起動する。
起動したまま、再びコンソールに戻る(ログに隠れてプロンプトが見にくくなっているが)
jetty:stop
で、サーバを止めることができる。
ここで、 jetty:start
jetty:stop
は、sbtのタスクと呼ばれる。
jetty:start
はjettyサーバを起動するタスクだ(jetty は java 製の軽量webサーバ)
タスクの前に ~
を付けると、「ソースを監視して、に変更があったらもう一度タスクを実行する」のような意味になる。
つまり最初に実行した sbt ~jetty:start
は「sbtから jetty:startタスクを実行する。その後ソースの変更を監視して、変更があれば再度 jetty:start タスクを実行する」のような意味になる( jetty:start は二重起動しようとすると最初に走っている jettey を止めてから新たに起動する)。
sbt のインタラクティブコンソールからは exit
で抜けることができる(このとき jetty は終了していなければ終了する)。
sbt という存在があること、sbt の中でタスクを実行することができること、ファイルを監視してタスクを再実行する機能もあること、を、とりあえず覚えておこう。
sbt については 始める sbt という公式ドキュメントがあるので、知りたくなったら読めばいいけど gitbucket をいじりたい、という上で知る必要のあることは(とりあえずは)それほど無い(既に設定されているので)。
windows 用追加設定
sbt インタラクティブコンソールは 上下矢印キーでヒストリーをさかのぼることができるのだが、 windows だと文字化けする。起動時に -Dinput.encoding=Cp1252
というオプションを指定するとこの文字化けが解消される。最初に紹介した環境変数設定スクリプトで SET _JAVA_OPTIONS="-Dinput.encoding=Cp1252"
と指定してあるのはそのためだ( _JAVA_OPTIONS
があると java.exe が勝手にそれを起動オプションに追加する)。
また、 windows だと css 等を変更し、パッケージングが開始されたときに次のようなエラーが出る。
[trace] Stack trace suppressed: run 'last *:webappPrepare' for the full output.
[error] (*:webappPrepare) java.io.FileNotFoundException: C:\Users\nazoking\git\gitbucket\target\webapp\assets\common\css\gitbucket.css (要求された操作はユーザー マップ セクションで開いたファイルでは実行できません。)
[error] Total time: 3 s, completed Jan 13, 2016 12:07:07 PM
これは jetty がファイルをロックしているからだ。解決策として、 src/main/webapp/WEB-INF/jetty-web.xml
として次のファイルを作ると、リロードできるようになる。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Call name="setInitParameter">
<Arg>org.eclipse.jetty.servlet.Default.useFileMappedBuffer</Arg>
<Arg>false</Arg>
</Call>
</Configure>
参考: http://www.eclipse.org/jetty/documentation/current/troubleshooting-locked-files-on-windows.html
この src/main/webapp/WEB-INF/jetty-web.xml
は、コミットしないように gitbucket/.git/info/exclude
にファイル名を追加しておこう。
高速開発のための追加設定(jrebel)
jrebel というものを入れると、 java vm の再起動なしでクラスのリロードをしてくれる。起動の処理を毎回しないのでとても高速だ。個人ライセンスがあるので、無料で使えるっぽい(たまに jrebel最高! 的な事をつぶやかれる?)
my jrebel からログイン
https://my.jrebel.com/
SNSログインすると自働でアカウントが作られるようだ。
Install and Activate
で、ライセンスキーがもらえる(なんか暗号っぽいやつ)
ダウンロードは https://zeroturnaround.com/software/jrebel/download/#!/have-license から「Download jrebel
」(ライセンスサーバーじゃない方)
zip を適当なところに展開して bin/activate-gui
を実行して、ライセンスキーを入れる(なんか暗号っぽいやつ)
jrebel.jar のパスを環境変数に設定する
SET JREBEL=c:\develop\util\jrebel\jrebel.jar
jrebel を使う場合は、~ jetty:start
ではなく、 jetty:start
と ~webappPrepare
を使う。
sbt
:
その他いろいろログが流れる
:
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
Listening for transport dt_socket at address: 5005
:
その他いろいろログが流れる
:
> jetty:start
[info] Wrote rebel.xml to C:\Users\nazoking\git\gitbucket\target\scala-2.11\resource_managed\main\rebel.xml
[info] Compiling 2 Scala sources to C:\Users\nazoking\git\gitbucket\target\scala-2.11\classes...
[info] Packaging C:\Users\nazoking\git\gitbucket\target\scala-2.11\gitbucket_2.11-3.11.0-SNAPSHOT.jar ...
[info] Done packaging.
[info] starting server ...
[success] Total time: 20 s, completed Jan 13, 2016 12:01:55 PM
:
その他いろいろログが流れる
:
2017-01-13 12:05:18.699:INFO:oejs.ServerConnector:main: Started ServerConnector@28486680{HTTP/1.1}{0.0.0.0:8080}
2017-01-13 12:05:18.700:INFO:oejs.Server:main: Started @19739ms
>
最後まで来たら(ログに隠れて最後のプロンプトが見えないかもしれないが)、今度は ~webappPrepare
と打ってみよう。「ソースを監視して、ファイルが変更されたら webappPrepare
を実行する」くらいの意味になる。
この状態で、例えば src/main/twirl/gitbucket/core/main.scala.html
等を書き換えてブラウザをリロードすると、 ~jetty:start
の時よりも高速に内容が反映されることが分かると思う。
この二つのファイルはコミットされないように .git/info/exclude
に追加しておくとよい。
jRebel では、クラスのリロードはしてくれるが関連するクラスの初期化はされない。例えばコントローラーに新しいルートを追加した場合などは反映されない。
また、たまいろいろな関係でキャッシュされたクラスなどが不正後を起こして、たまに NoClassDefined エラーが出る事がある。
おかしいと思ったら、 ~webappPrepare
を一旦止めて(エンターキーを押すと止まる) jetty:start
と打って、サーバを再起動してみよう。
gitbucket のファイル構成
git clone した直後、ディレクトリは以下の通り。javaの伝統 maven っぽい。sbtがmavenを意識して作られているからだ。
- contrib デーモン化するときの起動スクリプトとかそういうの
- doc 開発者用ドキュメント
- project sbt用の構成管理ファイル
- release gitbucket本体のリリース用のスクリプト。gitbucket本体のリリースをする人以外は関係ない
-
src ソースコード
- test 自働テスト用のソースコード
-
main アプリケーション本体のソースコード
- java java のソースコード。ちょっとだけjavaが混じっているが最初は気にしなくていい
- resources リソースファイル。xml とか アップデート用スクリプトとか
- twirl html テンプレート
- webapp webアプリケーションのリソースファイル。cssとかjava script とか
- scala scala のソースコード
- その他に自動で作成されるファイルとかがあるけどとりあえず気にしない
ソースは主に src/main/scala
以下をいじることになる。 html は src/main/twirl
以下、javascript/css は src/main/webapps/assets/
以下にある。
view をいじってみる。scala テンプレート twirl
gitbucket では twirl というテンプレートエンジンを使っている。これは play フレームワーク で使われているもので、html中に scala を書ける、というものだ。
scala の文法(の基礎)はそれほど難しくはない。 javascript や ruby などを触ったことがあるのであれば、何となく分かると思う。
scala をいじるなら IDE の利用がお勧めだが、 view の変更程度にとどめるなら vim や sublime text のような、「html/javascriptを編集できる程度の能力を持ったエディタ」でも十分である(私はsublime text に ensime プラグインを使っている)。
twirl では、 jsp や erb や php のようにHTMLタグの中に HTMLではない言語を書く。 twirl は @
から後をいい感じに scala 言語として解釈してくれる。ファイルの拡張子は .scala.html
で、src/main/twirl/gitbucket/core
以下に置かれている。
試しに src/main/twirl/gitbucket/core/main.scala.html
等を、 scala っぽい部分を避けて、書き換えてブラウザをリロードしてみよう。内容が反映されることが分かると思う。
-
@
が現れたら、そこからしばらく scala だ。具体的には- 変数名っぽい間
<h1>@value1</h1>
- 関数名 + 引数
<h1>@func1(value1)</h1>
- 関数名 (+ 引数) + ブロック
<ol>@func1(value1){ value2 => <li>@value2</li> }</o1>
- match
<p>@value1 match { case condisiton-expression => <span>@condition</span> }</p>
- 括弧
<h1>@(value1 + value2)</h1>
- などなど
- 変数名っぽい間
@
の後、 ()
の間、 {}
のはじめの =>
まで、あたりが scala である(違う場合もある)。これらの記号に注目しながら試行錯誤してみて欲しい。なお @
自体は @@
で出力できる。
以下、簡単に説明する。
@(title: String)
<h1>@title</h1>
というテンプレートファイルがある。一行目は引数の宣言(コントローラーから受け取る値)である。このテンプレートファイルは
def view(title: String){
return "<h1>" + escape(title) + "</h1>";
}
のような形に変換される(正確には違うが説明のため 1 )。def
はメソッド・関数の宣言キーワードで、javascript だと function
キーワードと同じような意味だ。 title: String
が引数の一つで、:
より前が変数名、後が型(クラス名)になる。
つまりこのテンプレートでは、 title という String 型の変数をコントローラー等から受け取って使うことになる。
実際に変数を使っているのは <h1>@title</h1>
の部分である。ここは php で言うと
<h1><?php echo escape(title) ?></h1>
のような形に変換される。twirl では、変数の型によって自働でHTMLエスケープされる(XSS対策)。
@
の後は変数だけでは無く、関数呼び出しやif、for 等も使える。
@for(order <- orders) {
<li>@order.getTitle()</li>
}
のような例が play の html テンプレートの解説ページ に載っているので、そのあたりを読んで習得してほしい。とはいえそこに、
テンプレートは複雑なロジックを記述する場所ではない事を忘れないで下さい。複雑な Scala コードをここに書く必要はありません。ほとんどの場合は以下のようにモデルオブジェクトからデータにアクセスするだけで済むでしょう。
と書かれているように、そんなに複雑なプログラムは入れてはいけないし、既存のコードを読む時も出てこないはずだ。
gitbucket は bootstrapを使って作られている。javascript は jQuery ベースだ。
これまでで、とりあえず UI を変更したり、ロゴを変更したり、簡単な挙動を変更したりするようにできると思う。
コントローラーをいじってみる。web アプリケーション・フレームワーク scalatra
gitbucket で使われているのは scalatra というwebフレームワークである。 scalatra は ruby の sinatra を参考にして作られた scala の web フレームワークだ。 scala で webフレームワークと言えば play が有名だが、scalatra はそれに対して「java の servletとして動く」という特徴がある(play は servlet として動かしにくい)。tomcatや既存のwebアプリケーション資産を生かしやすく、gitbucketでは jgit の提供するgitサーブレットなど、それを利用して動かしている。
scalatra は、非常に簡単にサーブレットが書けるのも特徴だ。例えばこんなコードになる。
class MyController extends ScalatraServlet with ScalateSupport {
get("/hoge") {
"Hello, world!"
}
}
もう少しjava風に書くと、上のコードは
class MyController extends ScalatraServlet, ScalateSupport {
// コンストラクタ
public MyController(){
Route route1 = new Route("GET", "/hoge")
route1.setAction(new Action(){
pulbic Object apply(){
return "Hello, world!";
}
});
this.addRoute(route1);
}
}
くらいの意味になる(上のコードは全く動かないしクラス名・メソッド名も適当。雰囲気を掴んで!)。webアプリケーションの開発経験があれば、何をしているのか何となく分かると思う。 scala では、クラス定義文がコンストラクタと混在するので、このような DSL が書きやすい。
gitbucket では、このコードは src/main/scala/gitbucket/core/controller/MyController.scala
として置かれる。
src/main/scala
ディレクトリは
- gitbucket/core
- controller コントローラー
- model データベース関係
- service サービス。コントローラーとモデルの間くらい
- ssh ssh関係
- util その他
- view html/twirlで使うヘルパーとか
- servlet サーブレット/フィルタ(コントローラーの前に処理をするとか)
- plugin プラグイン関係
- api api関係
のようになっている。まずリクエストを受け取るのがコントローラーなので、コントローラーを説明する。
コントローラはブラウザからのリクエストを処理して、viewを返す。先ほど view は文字列として返したが、gitbucket では基本的には html テンプレートを返す。
html テンプレートは scala の著名なwebフレームワークである play から独立した twirl というテンプレートエンジンを使っている。twril は html 中にscalaを書くと sbt がscalaにコンパイルしてくれるテンプレートエンジンだ。
具体的には src/twirl/gitbucket/core/hoge.scala.html
というファイルを書くと gitbucket.core.html.hoge
という関数にコンパイルされる(scala には「関数(のように扱えるクラス)」がある&クラス名を全部小文字にすることもある。この例は gitbucket.core.html
パッケージの hoge
関数型クラス(を拡張した何か)が生成される)。
@(message:String)
<div>@message</div>
として
get("/hoge") {
gitbucket.core.html.hoge("Hello World")
}
こんな感じのコードを 例えば src/main/scala/gitbucket/core/IndexController.scala
に書けば、/hoge
にアクセスすると Hellow World
が表示されるはずだ。
twirl は html 中の @
から後をいい感じで scala と見なして、いい感じで出力してくれる(いい感じで=説明が難しいけど何となくうまく動く)。 src/twirl/gitbucket/core
以下はだいたい url に合致しているので、gitbucketを動かしながら気になる画面を見てみよう。
scalatra では単純には次のようにすることでリクエストパラメータを取得できる
get("/hoge/:aaa") {
"aaa="+ parem("aaa")+ " bbb="+parem("bbb")
}
このようにして、 /hoge/xxxx?bbb=yyy
にアクセスすると "aaa=xxxx bbb=yyy" が表示される。
一応ここまでで、リクエストを受け取ってviewを返す事ができるはず。
今日は力尽きたのでここまで。リクエストが多かったりやる気が出てくれば続きを書きます。。。
-
実際に
src/main/twirl/gitbucket/core/main.scala.html
から生成されるのはtarget/scala-2.11/twirl/main/gitbucket/core/html/main.template.scala
なので、悩んだら覗いてみると何解決するかも ↩