1. Qiita
  2. 投稿
  3. Scala

gitbucket に参加しよう

  • 0
    いいね
  • 0
    コメント

    この文章は、主にgitbucketやgithubが大好き、俺専用githubを作りたい、という人たちに向けて書いた。
    向いてない人はきちんと詳細・原理まで説明しないと使えない人、向いている人はなんか分からないけれどコピペしたら動いたからがんがん機能かくで!という人。
    java は知っているけれど scalaは知らない人に向けて書いていますがscalaの説明は最低限しかしないので、別途リファレンスや入門書などを参考にすること。javaもscalaも知らなくてもrubyやphpやjavascriptを知っていれば何とかなるはず。
    webアプリケーション開発経験が無いとつらい。

    gitbucket とは

    gitbucket とは github 上で開発されているオープンソースの github っぽいgitリポジトリサーバです。

    https://github.com/gitbucket/gitbucket

    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
    

    すると

    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を編集できる程度の能力を持ったエディタ」でも十分である。

    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>";
    }
    

    のような形に変換される(正確には違うが説明のため)。def はメソッド・関数の宣言キーワードで、javascript だと function キーワードと同じような意味だ。 title: String が引数の一つで、: より前が変数名、後が型(クラス名)になる。
    つまりこのテンプレートでは、 title という String 型の変数をコントローラー等から受け取って使うことになる。

    実際に変数を使っているのは <h1>@title</h1> の部分である。@title は 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 with 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 には「関数(のように扱えるクラス)」がある)にコンパイルされる。

    @(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" が表示される。


    今日は力尽きたのでここまで。リクエストが多かったりやる気が出てくれば続きを書きます。。。