久々の更新が移転のお知らせで恐縮です。
octopressでブログを更新していたのですが、
少し放置していたら最新版の取り込みがかなり苦痛なことになり、
メンテ出来なくなってブログの更新をずっと放置してしまっておりました。
潔く最新版にするのは諦め Medium の方でブログを書くようにしましたので、
引き続きよろしくお願いします。
https://medium.com/@kkoudev/
Dockerを使ってWindowsとMacで共通設定ファイルを利用した開発環境を構築する
Dockerはバージョン1.8よりDocker Toolboxと呼ばれるものが公式に配布されるようになり、 WindowsおよびMacの双方でDockerクライアントは然ることながら、docker-machineが利用できるようになりました。 従来の boot2docker が docker-machine に置き換わることになります。 WindowsでもDockerクライアントが利用できるようになったということで、以前紹介した「SSHを使ってWindowsからDockerコンテナ内のコマンドを直接実行する」のような小細工をする必要もなくなったということになります。
さて、突然ですがDockerの最大の売りとは何でしょうか。 ポータビリティに優れている点、プラットフォームに依存しないアプリケーションの実行、といった点が代表的な利点かと思いますが、 この利点を普段の開発環境構築において利用してみようというのが今回のテーマです。
私の職場では、Windowsマシンを利用している方とMacを利用している方の両方が存在します。 どちらか一方に寄せてくれれば大変ありがたいのですが、プロジェクトによってはWindows用のローカル開発環境しか用意されていないこともあるので、 どちらか一方に寄せるというのも少々難しい問題となっています。 大抵のケースではMacにParallelsを導入して回避しているのですが、できれば各OS上で動かせると開発はしやすいですよね。
そこでDockerの出番ということになります。 今回はWindowsとMacで共通の設定ファイルを利用した開発環境を構築する上で留意するべきポイントやテクニックを紹介します。
1. コンテナ化する部分とコンテナ化しない部分を決定する
Dockerを利用するのであればすべてコンテナ化したいところなのですが、 IDEでデバッグする利便性を考慮すると、Tomcatサーバのようなアプリケーションサーバ部分はそのままにしておきたいです。 そうなると、HTTPサーバ(Apache、Nginxなど)とデータベース(MySQL、PostgreSQLなど)のみコンテナ化する必要があります。
2. ディレクトリ構成は同じにしておく
WindowsとMacで共通の開発環境を構築するためには、ディレクトリ構成を同じにしておく必要があります。 例えば私の場合は、以下のように各ユーザディレクトリ配下に共通のディレクトリ構成を作成しています。
1 2 3 |
|
とはいえ、ユーザディレクトリ名はユーザごとに異なるため、
絶対パスを記述しなくてはいけない場合に $HOME
の環境変数を直接取得することができないと少々困ります。
そういう場合は、WindowsとMacでそれぞれ全ユーザの共有ディレクトリが存在するため、そこへシンボリックリンクを作成し、
その共有ディレクトリ経由のパスを記述します。以下に例を記述します。
1 2 3 4 5 6 7 |
|
実はVista以降のWindowsとMacはドライブレターの有無の違いはあれど、 上記のとおり殆ど同じディレクトリ構成を取ることが可能なのです。 違いがあるのは、共有ディレクトリの名称くらいです。
3. Private Docker Repositoryを構築する
こちらは特に重要なポイントとなります。 Private Docker Repositoryがないと長時間のビルドを利用者に強要してしまうことになるのと、msysGitの絶対パス問題(後述で紹介します)が災いして、共通のDockerfileを利用できないケースが出てきてしまうため、可能な限りPrivate Docker Repositoryを用意してください。 Private Docker Repositoryの構築についてはクラスメソッドさんのブログで詳しく解説されています。
1つ注意としては、Dockerはプライベートネットワーク上に存在する他のサーバに対して直接アクセスすることができません。
(やろうと思えばできるかもしれませんが、docker-machine上のOSのIPアドレスを同一ネットワーク上に属させたり、ルーティングの設定をしたりという結構面倒な作業が必要になるかと思いますのでオススメはしません)
そのため、社内の開発機に構築したところで、その開発機がグローバルからアクセスできないようだと直接利用することができないため注意です。
グローバルからアクセス可能な箇所にPrivate Docker Repositoryサーバを構築し、サーバ側にIP制限やBASIC認証を掛けて利用しましょう。
4. シェルスクリプトを利用してWindows用とMac用の処理を記述する
共通のディレクトリ構成を作成したところで、どうしても回避できない環境差異は存在してしまいます。
そのため、シェルスクリプトを利用してWindows用とMac用の処理を関数内でラップしてしまいましょう。
(ちなみに、Windowsでシェルスクリプトを実行する場合はGit for Windowsに付属してくるGit Bashを利用します。Docker Toolboxをインストールするときに合わせてインストールされます)
具体的にWindowsとMacで処理を分けなければならないのは以下の点です。
4-1. ユーザ共有ディレクトリの取得
こちらは前述のとおり、ユーザ共有ディレクトリの名称が異なるため、 WindowsとMacで返却する名称を変更する必要があります。
4-2. シンボリックリンクの作成
Windowsで本物のシンボリックリンクを作成するためにはmklinkコマンドを利用する必要があります。 Git Bashのlnではシンボリックリンクの作成は行わず、擬似的にコピーを作成するだけになるためです。 また、シンボリックリンクと似たような機能にジャンクションポイントというものがありますが、 こちらはDockerコンテナ内から参照できないため利用不可です。
4-3. 絶対パスの記述がWindowsの場合は異なる
通常だと「/Users」のように絶対パスは記述しますが、Git BashにおいてはmsysGitの仕様上、「//Users」と記述をしなくてはいけません。 もし「/Users」とGit Bashで記述すると、Git for Windowsのインストールディレクトリからの相対パス(デフォルトだと/Program Files/Git/Users)になってしまいます。
4-4. ホストOS側のIPアドレスの取得
ここでいうホストOSというのは、WindowsローカルまたはMacローカルのマシンのIPアドレスのことです。(docker-machine ip で取得できるIPアドレスとは異なります) このIPアドレスは、Dockerコンテナ内からホストOS側で起動しているTomcat等と連携する場合に必要になります。 WindowsとMacではネットワークコマンドが根本的に異なるため、こちらもシェルスクリプトで処理を分ける必要があります。
4-5. Docker環境変数の設定方法
WindowsのGit Bashで eval $(docker-machine env マシン名)
を実行すると、
「On Windows, please specify either ‘cmd’ or ‘powershell’ with the —shell flag.」
というように怒られてしまいます。こちらについても対処を行う必要があります。
4-6. Windowsでシェルスクリプトをプログラム内から呼び出す
Macでは特に問題ありませんが、WindowsだとGit Bashを経由しないとシェルスクリプトは実行できません。 関連付けの設定を敢えて用意するのでもいいですが、いちいちそれを利用者に行わせるのは面倒なので、 専用のバッチファイルを用意して対処します。
それでは、上記の問題に対処したラッパー関数を紹介します。
OS判別用共通関数 (この関数は全スクリプトで参照されていることにする)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
4-1. ユーザ共有ディレクトリの取得
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
4-2. シンボリックリンクの作成・削除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
|
紹介している関数mklinkではlnコマンドと引数が逆なので、
分かりづらい場合は上記関数を修正してみてください。
また、1点Windowsの問題として、mklinkコマンドは「管理者として実行」を行わないと実行できません。 Git Bashで管理者として実行を行うためにはどうすればいいのでしょうか。 簡単な方法としては、以下のような「管理者として実行」を行うためのスクリプト(sudo.sh)を用意します。
シェルスクリプトを管理者として実行するためのスクリプト (sudo.sh)
1 2 3 4 5 6 7 8 9 10 11 12 |
|
上記スクリプトを利用して、以下のように実行します。
1
|
|
こうすることで、mklinkコマンドをシェルスクリプト内で実行することが可能になります。 (実行時に管理者として実行するかどうかの確認ダイアログが表示されます)
4-3. 絶対パスの記述を変換する関数
以下のような関数を使って、引数に含まれる絶対パスを修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
絶対ファイルパスを利用する箇所で以下のように利用します。
1
|
|
-vのように、途中にスペースが入らない場合は先頭のパスだけが対象になります。
また、docker execなどで外部から引数を渡してコマンドを実行したい場合、 どの部分が絶対パスかコマンドによるので判らないかと思いますが、以下のように指定することで引数に含まれた絶対パスを全て変換してくれます。
1
|
|
4-4. ホストOS側のIPアドレスの取得
ホストOS側のIPが複数ある場合は、最初にヒットするIPアドレスを返却するようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
4-5. Docker環境変数の設定方法
実はこちらは —shell オプションを明示すれば特別処理を分ける必要はありません。
以下のように指定します。
1
|
|
4-6. Windowsでシェルスクリプトをプログラム内から呼び出す
以下のようなバッチファイル(bash.bat)を用意します。
1 2 3 |
|
シェルスクリプトをプログラム内から実行する場合は、以下のコマンドを実行します。
1
|
|
以上、紹介した関数があれば大抵のプラットフォーム差異は吸収することが可能です。
5. 設定ファイルはホスト側にあるソースファイルリポジトリから参照する
各コンテナ起動時に、ホスト側のボリュームをマウントし、ソースファイルリポジトリで管理されている
Apacheの設定ファイル等を参照できるようにしておきます。
このとき、Apacheの設定ファイル内でログ出力先をマウントしたホスト側ボリュームとしておけば、
わざわざコンテナ内に入らなくてもログを閲覧できます。
以下はコンテナ起動時のコマンドとApacheの設定ファイルにおける記述例です。
(ここまでに紹介した関数を利用しています)
<起動コマンド>
1 2 3 4 |
|
<Apacheの設定ファイルの一部分>
1 2 3 |
|
※(Project Name)には任意の名称を入れてください
こちらは最初に紹介したディレクトリ構成が元となっています。 /(Project Name) でコンテナ内からプロジェクト配下のディレクトリを全てアクセスできるようにしておくことで ホスト側からソースファイルリポジトリで管理されている設定ファイルを読み込んでサーバを起動することができます。
また、—add-hostを利用することで、IPアドレスではなくホスト名で設定ファイル内からアクセスを行うことが可能になります。 Dockerコンテナ内のApacheからホスト側で起動しているTomcatへ連携する場合は、 ここで定義した名前をApache設定ファイル内に記述して連携します。
まとめ
上記で紹介したテクニックとポイントを抑えておけば
共通の設定ファイルを利用してWindowsとMacの開発環境を構築することが可能になります。
WindowsとMacが混在しているような現場では大いに役立つと思いますので、是非活用してみてください。
SSHを使ってWindowsからDockerコンテナ内のコマンドを直接実行する
Dockerを使い始めて最初にひっかかるポイントとしては「WindowsにはDockerクライアントがない」という点です。 それは boot2docker を導入したところで同じで、Windows上では docker コマンドを結局直接実行することができません。 どの記事をみても、Windowsで利用する場合は boot2docker ssh してゲストOS(dockerコマンドを実行するための軽量Linux OS)から実行するようなことが紹介されています。 (次期Windows Serverに docker.exe が付属することが最近発表されましたが、まだいつになるかわかりません)
これではせっかくdockerを導入しようにも「Macを利用しないといけないのか」というお話になり、 (Macにはdockerクライアントがあるので、dockerコマンドを直接OSX上で実行出来ます) 大多数いるWindowsユーザを考慮すると、結局開発環境が構築できないから利用をあきらめる、みたいになっている人も多いかもしれません。
もちろん、ApacheやNginxのようなサーバを立ち上げるという用途であるなら、
boot2docker sshしてゲストOS上でコマンドを叩いてサーバを立ち上げるような用途でも問題なくそのサーバへアクセス出来ますが、
問題はコンテナ上のコマンドを直接実行したい場合です。
例えば以前記事にさせて頂いた「Imlib2」は、Linux上でしか現状利用できません。
このようなミドルウェアを利用したいケースをカバーできるとしたら docker なわけですが、
Windows上でdockerコマンドを直接実行できなければWindowsでは利用出来ないのも同じです。
では、そのようなケースをカバーするにはどうすればいいのか。 それについてご紹介します。
boot2dockerのインストールとコマンド実行用イメージの作成
まずは boot2docker のインストールです。
インストールは公式サイトのとおり、セットアップ用のexeファイルをダウンロード&実行してインストールします。
インストール後に、コマンドプロンプトを立ち上げ、以下のコマンドを実行してゲストOSへログインします。
1 2 3 |
|
ゲストOSへログインしましたら、イメージを作成します。
この辺りは詳しくは紹介しませんが、
例として以下のようなDockerfileを記述しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
最新の boot2docker (現時点ではv1.3.1)であれば最初から C:\Users
配下のディレクトリがゲストOSにマウントされており、
/c/Users
といったパスでWindows側のファイルへアクセスすることが可能です。
そのため、今回は Dockerfileを作成した後に、C:\Users\Public\Temp
のようなディレクトリを作成しておき、
そこにDockerfileを置いておきます。
その上で、以下のコマンドをゲストOSで実行して「test/command」というイメージを作成してみます。
1 2 |
|
これで新しいイメージ「test/command」を作成できました。
SSHサーバを立ち上げる
Dockerクライアントが無いのであればどうすればいいのか、
といいますと、実は単純な話で、SSHサーバをコンテナ内で立ち上げてWindows側からリモートでコンテナに対してコマンドを実行すればいいわけです。
これであれば、Dockerクライアントがなくてもコマンドを実行できます。
WindowsでもGit for Windowsをインストールすると ssh のコマンドが付いてきますので、これを利用します。
01. SSH公開鍵と秘密鍵を作成する
まず、Git for Windowsをインストールします。
こちらも公式サイトからダウンロードし、全てデフォルトの設定のままインストールします。
インストール後、C:\Program Files (x86)\Git\bin
を環境変数PATHへ登録しておきます。
PATHへ登録後、以下のコマンドをコマンドプロンプト上で実行します。
1 2 |
|
パスワードは全て無しで公開鍵と秘密鍵を作成します。
すると、%USERPROFILE%¥.ssh ディレクトリに「id_rsa」と「id_rsa.pub」ファイルが作成されます。
02. SSH公開鍵の登録とSSHサーバの起動
SSH公開鍵を作成したあとは、作成した鍵をコンテナ内へインストールしてあげる必要があります。
そこで、まずは以下のコマンドをゲストOSで実行し、コンテナ内へログインします。
1
|
|
上記のコマンドでログインすると、コンテナ内でも /c/Users へアクセスできるようになっていますので、
%USERPROFILE%¥.ssh¥id_rsa.pub
を以下のコマンドでインストールします。(%USERPROFILE%はWindowsの環境変数のため、当然コンテナ内からは使えないので以下のように直接ユーザディレクトリを指定します)
1 2 3 4 |
|
コンテナから exit でログアウトし、作業した内容を反映するために再度イメージを作り直します。
1
|
|
これで作成した公開鍵をインストールしたイメージが作成されました。 準備は完了です。
コンテナ内でSSHサーバを立ち上げるには、以下のようにゲストOSにてdockerコマンドを実行します。
1
|
|
これで、コンテナ内の22番ポートが、Windows側の49153以上のポート番号へ割り当てられます。 実際にどのポート番号へ割り当てられたかは docker ps をしてみると、 「0.0.0.0:49153->22/tcp」と表示されるので、ここで判断します。
Windows側からコンテナに対してコマンドを実行してみる
いよいよ本番です。
Windows側からアクセスするには、
上記の手順で確認した割り当て先ポート番号(49153)と
ゲストOSのIPが必要になります。
ゲストOSのIPは以下のコマンドをコマンドプロンプトで実行することで確認できます。
1
|
|
特にバッティング等していなければ 192.168.59.103 が返却されるかと思います。
そして、以下のようなバッチファイルを作成します。
(ここではファイル名を container_command.bat
と仮定します)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
これでコンテナに対してコマンドを実行する準備が出来ました。
試しにImageMagickを実行してみたいと思います。
ImageMagickはいわずもがな画像変換を行うツールですが、
コンテナ内で画像を変換してどのようにWindows側へ画像を返すのでしょうか。
そのためには、上記のSSHサーバ起動時にマウントした /c/Users/Public/Temp:/HostTemp
のディレクトリを利用します。
つまり、C:\Users\Public\Temp
に変換したい画像ファイルを予めコピーしておき、
コンテナ内では /HostTemp
というパスで参照および書き込みを行うことで、変換結果をWindows側へ返すことが可能になるわけです。
このことを考慮した上で、Windows側で実際にコンテナ内のコマンドを実行してみます。
例として、Windows側のマウントディレクトリであるC:\Users\Public\Temp
に sample.jpg を配置し、
これをコンテナ内のImageMagickを利用して PNG へ変換します。
さきほど作成した container_command.bat
を使用し、以下のコマンドをWindows側のコマンドプロンプトで実行してみましょう。
1
|
|
実行してみると、C:\Users\Public\Temp
に sample.png が作成されていることがわかるかと思います。
当然ImageMagick以外のコマンドも実行することが可能です。
まとめ
このように、Windowsでも少し工夫をすることでコンテナ内のコマンドを簡単に実行できるようになります。 そのためこの方法を使えばLinuxでしか動かないImlib2のようなミドルウェアをWindowsで実行することが出来るようになります。
また、実際にこの方法を利用してプロジェクト等の開発環境を構築する場合は手動で行うと大変面倒なので、
接続に使用するSSH公開鍵をインストール済みのコンテナを用意し、
イメージの作成とSSHサーバの起動を自動的に行うスクリプトを作成することをオススメします。
一点注意として、SSHでリモートコマンドを実行している関係上、どうしても動作速度はネイティブでコマンドを実行するよりも遅くなってしまいます。 あくまで本番環境同等の機能をWindowsでも実現したい場合に利用する手段の1つという程度の認識にしておいた方が無難かと思われます。
Intellij IDEA 13.1.4においてGradleのWebリソースディレクトリがクリアされてしまう不具合
Intellij IDEA 13.1.4においてGradleプラグインのデグレが発生しました。
内容としては、マルチプロジェクトを利用している場合にWARプロジェクトのWebリソースディレクトリ(ex. src/main/webapp) が
1つのプロジェクトを除いて全てクリアされてしまうというものです。
13.1がリリースされたばかりのときに同じようにArtifactsが 1 つのプロジェクト分しか作成されないという不具合がありましたが、
今回はこれのWebリソースディレクトリ版の不具合といったところです。
手動で設定すれば動くようになるけど、再度Gradleプロジェクトのリフレッシュをするとまたしてもクリアされてしまうという酷いもの。 さすがにこれでは使い物にならないので、バグ報告をしたところ数週間経ってようやく修正されました。 13.1.5にて修正版が反映されるようですが、13.1.4の状態でも以下のファイルを反映することでこの不具合が解消されます。
<修正パッチ>
ここからダウンロード
このファイルをダウンロードし、
1
|
|
上記ディレクトリへコピーすることで解消されます。
これで問題なく動作することを確認できましたが、 どうもIntellij IDEAのGradle機能はデグレが多いのと、マルチプロジェクトについては毎回動作確認をしていないと思わせるような印象があります…。 今後はこのようなクリティカルな不具合がないことを期待したいところです。
Gradle 1.xから2.0へ移行する際に気をつけるべき変更点
7/1にGradle 2.0がリリースされました。
複数の非推奨メソッドの削除が行われたため、
1.xで動作していたものが、2.0から動かなくなるケースが出てきました。
全てのケースを紹介しているとキリがないので、
今回は特に大きいと思われる 2 点の変更点のみ紹介させて頂きます。
変数の直接宣言の非サポート
これは1.10くらいのときから警告が出るようになっていましたが、 具体的には以下のように変数を宣言するのが無効になりました。
1 2 |
|
これを解決するには、 extの名前空間で囲むか、defキーワードを指定してローカル変数として宣言します。
1 2 3 4 5 6 7 |
|
EclipseとIntellij IDEAでprovidedな依存関係指定時の変更
provided指定の方法は以前の記事でも何度か紹介させて頂きましたが、 以前までは以下のように指定していました。
1 2 |
|
1.xまではこのままでよかったのですが、 2.0からは += 演算子で configurations の指定を追加する場合、 以下のように指定します。
1 2 |
|
1.xでもこの指定で動作するので、1.xをまだ利用している場合でもこちらの指定に変更するべきでしょう。 これについては、2.0ではタスク実行時に「You can’t change configuration ‘(追加変数名)’ because it is already resolved!」 といったようなエラーメッセージが表示されるようになりましたので、 このエラーが表示されたら上記記載に変更してみましょう。
まとめ
今回は1.xから2.0へ移行する際にハマるであろうポイントのみ紹介させて頂きました。
リリースノートにはもっと詳細に紹介されていますので、
詳しい内容についてはこちらをご覧ください。
Imlib2とImage::Imlib2のJPEGヒント対応版を作成しました
久々に作ったものをGithubへ公開しました。
元ネタは、よくImageMagickと比較される Imlib2 です。
これのJPEGヒント対応版が欲しくなったので探したのですが、どこにも見当たらなかったのでいっそのこと自分で作りました。
(対応についてはSmallLightのソースを参考にさせて頂きました)
JPEGヒント(scale denom)とは
まずJPEGヒントとは何かというと、ImageMagickやImlib2で利用しているlibjpeg ver.7以降で利用することの出来る scale_denom というスケーリング手法の俗称です。
これは簡単に言うと予めリサイズ後のサイズがわかっている場合に、元画像ロード時にメモリを効率よく確保しつつリサイズを高速化する手法です。
ImageMagickではこの機能をサポートしており、「-define jpeg:size=640x480」といったオプションをつけることで利用することができます。
よく「ImageMagickは遅い」と言われるのですが、このJPEGヒントを利用することで10倍近くの高速化が測れます。
ところが、Imlib2にはこのオプションが存在せず、APIにもそれをサポートしたものがありません。
APIの設計上、JPEGのみに特化した設計にはなっていないというのも理由の1つかも知れませんが、
JPEGヒントがないImlib2は単純なリサイズですとJPEGヒントを利用したImageMagickと同等か少し遅くなってしまうため、ベースの速度が速いのにこのままでは非常に勿体ないわけです。
JPEGヒントをサポートしたImlib2の利用
そこで今回、Imlib2を拡張してJPEGヒントが利用できるように実装しました。
実際に利用する場合はPerlのライブラリであるImage::Imlib2を経由して利用します。
このImage::Imlib2もJPEGヒントに対応させるために少し拡張しております。
実際の利用イメージとしては以下のような感じです。
1 2 3 4 5 6 7 |
|
「load_scale」というメソッドを追加しており、これを利用することでロード時にリサイズ後のサイズを指定します。 あとは普通にリサイズして書き出してみます。 これだけで、このサイズの場合は元のJPEGヒントを利用しない場合に比べて 1.5 倍ほど高速化しております。
チューニング対応したImageMagickとの比較
それでは、ImageMagickと速度比較してみます。
対等に比較を行うために、まずImageMagickの高速化を図ります。
具体的には、以下のオプションを指定して予めコンパイルしておきます。
01. 高速化のためのImageMagickのコンパイルオプション
01. —disable-openmp を指定してOpenMPを無効にする
→これが有効になっていると、マルチスレッド実行時に異常にメモリを消費して重くなる
02. —with-quantum-depth=8 を指定してRPG分解能を 8 bitにする。
→通常はこれで十分に24/32 bitの画像を処理できます。この指定をしなくても、convert 実行時に -depth 8 をオプションとして渡してもOK
特に 01 が重要になります。
画像のサムネイル作成を高速化する場合、最初に出てくる発想としてはマルチスレッドで実行することがあげられますが、
OpenMPはそのマルチスレッドによる実行時に異常な負荷をあげる原因となっているからです。
02は -depth 8 を convert 実行時に指定しても良いと思いますが、コマンド実行時に特に意識させずに高速化したいのであればコンパイル時に指定してしまいましょう。
02. libjpeg-turboの導入
ImageMagickもImlib2も通常はlibjpegを利用しますが、このlibjpegよりも更に高速に処理ができる libjpeg-turbo というライブラリがあります。 これを利用することで更に 1.3 倍ほど高速になります。 導入方法は簡単で、こちらより最新版のRPMをダウンロードしてインストールします。 すると、/opt 配下へインストールされますので、以下のようにして共有ライブラリファイルとして認識させます。
1 2 |
|
これでインストール済みのImageMagickとImlib2が libjpeg-turbo の方を利用するようになります。
03. 実効速度の比較
準備が完了したところで、実効速度を比較してみます。
今回の検証では 1920x1200 のJPEG画像を 640x480 へ 1000 回リサイズした場合の速度差を比較しています。
具体的には以下の内容のシェルスクリプトを作成して速度比較してみました。
<ImageMagick>
1 2 3 4 5 6 7 8 |
|
<Imlib2>
シェルスクリプトから呼び出すPerlスクリプト (resize.pm とします)
1 2 3 4 5 6 7 8 9 |
|
実行するシェルスクリプト
1 2 3 4 5 6 7 8 |
|
結果
<ImageMagick>
1 2 3 |
|
<Imlib2>
1 2 3 |
|
チューニングしたImageMagickと比較しても、JPEGヒントを利用したImlib2はかなり速いことがわかります。 画質についても素人目には違いが判りません。
まとめ
Imlib2はこのように速度が非常に速いことが利点ですが、 欠点としてはWindowsやMacへ導入するとなると少々面倒くさいので、開発時のデバッグが容易に出来ません。(その点、ImageMagickは簡単に導入できます) また、GIF画像の書き出しもサポートしていないので、GIF画像を扱う必要がある場合はImageMagickと併用するなどして対応する必要があります。 そのため、GIF以外の画像(特にJPEG画像)を多く扱い、速度を追求する必要が有る場合はImlib2の利用をお勧めします。
開発時にWindowsやMacで利用する場合は頑張ってImlib2をインストールするのもありですが、 どちらかといえばImageMagickとImlib2を同じインターフェースで利用出来るクラスを作成して、環境ごとに切り替えする方がいいかもしれません。 これについてはJavaのクラスとして近々作成予定ですので、完成次第展開いたします。
今回作成したJPEGヒント対応版のImlib2とImage::Imlib2については以下よりダウンロードしてください。
Gradleでリソースファイルをフィルタリングする場合の注意点
Gradleでリソースファイルのフィルタリングを行う例は検索サイトで調べると結構多くヒットするかと思います。 ですが、その殆どが今回紹介する注意点について考慮されていないスクリプト記述をしています。 具体的に何を注意しなければならないのでしょうか。 以下にリソースフィルタリングの例を紹介します。
問題となるスクリプト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
上記のスクリプトは、baseプロジェクトに含まれるリソースファイル(JARやWARに含まれるソースコード以外のファイル)に対して
「@version@」と「@db_ip@」の指定が含まれていた場合にそれぞれを指定の値に置換するという指定です。
一見これは上手く動作しますし、問題ないケースもあります。
しかし、この指定は見ての通りリソースファイル全てに対して行われれます。
これはどういうことなのかというと、リソースファイルがテキストファイルであれば何ら問題はないのですが、
バイナリファイルが含まれているとそれすらも置換対象となってしまうのです!
例えばWebサイトシステムでは、ExcelファイルのテンプレートとなるExcelを予めJARやWARに含めて、 それをプログラム内で利用するケースがよくあるかと思います。 その場合、リソースファイルであるExcelファイルすらも置換対象となり、ファイルが盛大に壊れます。 Mavenでフィルタリングを行うときはバイナリファイルは対象外となるため、それを期待して利用するととんでもない目に遭うわけです。
解決方法
解決策としては、以下のように対象となる拡張子を指定することで回避します。 gradle.propertiesに拡張子を定義しておくと管理しやすいです。
[gradle.properties]
1 2 |
|
[build.gradle]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
filesMatchingを利用するのがミソになります。 拡張子を build.gradle へ直接記載する場合は以下のような書き方でも動作します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
ただ、この書き方は推奨いたしません。 なぜなら、上記の拡張子指定を動的記述すると上手く動作しないためです。 せっかくなので紹介しておきます。
悪い例
※ replace.extensions が gradle.properties に定義されていることとする
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
これだと同じ拡張子を指定しているにも関わらず、上手く動作しません。
どうやら processResources において from 指定を行う場合、
同じパスに対して include と exclude の指定が被ると上手く動作せず、リソースファイルが全てJARやWARから消失します。
(ただし、直接記述すると何故か期待通りに動作する)
おそらく動的記述した場合と静的記述した場合でスクリプトの解釈順に違いが出るのだと思いますが、
無理に考えても仕方ないのでこの書き方はやめましょう。
まとめ
Gradleは便利ですが、Mavenのときと同じ挙動を期待するとこのような思わぬ落とし穴があります。 processResourcesでフィルタリングを行う場合は filesMatching、またはfilesNotMatching を利用し、対象ファイルの拡張子を限定してからフィルタリングするようにしましょう。
Gradleで陥りやすい問題点の解決策TIPS集
今回はGradleでよくハマるであろうポイントを集めたTIPSを11個紹介します。
01. 依存関係のバージョンが指定されたものにならない
MavenからGradleへ移行した場合、おそらく誰もが最初に陥る問題かと思います。 端的に言うと、Mavenの pom.xml で指定したままの依存関係の設定をそのまま build.gradle へ移したとしても 最終的に取得される依存関係は殆どのケースで同じにはなりません。 これは「推移的依存関係」によって同じライブラリが存在した場合に優先されるバージョンがMavenとGradleでは異なるために起こります。
推移的依存関係とは簡単に説明すると、ある依存関係がさらに依存する関係のことを言います。 MavenでもGradleでもそれらを自動的に取得しようとしますが、 それらの中で使っている依存関係のgroupIdもartifactIdも同じだがバージョンが異なる場合、 Javaでは同じライブラリの異なるバージョンを同じアプリ内へ共存させることが出来ませんから、 どちらかを優先させる必要が出てきます。
Mavenの場合は推移的依存関係における階層が高い方が優先されます。 しかしGradleでは、バージョンが一番高いものが優先されます。 そのため、Gradleでは利用しない依存関係をひたすら exclude 指定で除外していく必要が出てきます。
MavenからGradleへ移行する場合に依存関係のバージョンを含めて全く同じにしたい場合は、 以下のように現在の依存関係の階層構造を出力してそれを比較して合わせていけばよいです。
<Mavenで依存関係階層構造を出力>
1 2 |
|
<Gradleで依存関係階層構造を出力>
1 2 |
|
とはいえ、これがとにかく面倒です。 例えば自身で dependencies ブロックへ指定した依存関係が他の依存関係の推移的依存関係になっている場合、 exclude指定を行っても一見除去されないように見えたり(or 指定したバージョンにならなかったり)と、結構ハマります。 根気よく exclude 指定で不要なバージョンを除去して合わせていきましょう。
02. EclipseのWTPで起動した場合に他のプロジェクトを参照しているとその参照しているプロジェクトの依存関係を読み込まない
これも人によってはハマるポイントかと思います。 Eclipse上ではコンパイルエラーが出ていないのに、WTPでTomcatを起動すると参照しているプロジェクトの依存関係が無いと言われてしまう(ClassNotFoundExceptionが発生してしまう)現象です。 これは eclipse-wtp のプラグインの読み込み指定を行っていない場合に起こります。 どういうことなのかと言いますと、この例が起こるのは以下のように build.gradle を記述したときに起こります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
|
上記の例では、base プロジェクトに eclipse-wtp の指定がないことがわかります。 EclipseのWTPで起動するのは WAR プロジェクトのみです。 そのため、base プロジェクトのような通常のJavaプロジェクトは eclipse-wtp とは関係ないだろうと思いがちなのですが、 ここが落とし穴で、依存関係となるプロジェクトも含めて eclipse-wtp のプラグイン指定を行わないと、 たとえEclipse上でコンパイルエラーが出ていなくてもWTP起動時に読み込みの対象とならないのです。 なので、EclipseのWTPで起動させる場合は必ず WAR プロジェクト以外のプロジェクトにも eclipse-wtp のプラグインを読み込むよう指定してください。 (subprojects のブロックで指定すれば各プロジェクトごとにいちいち指定する必要がなくなるので記述量が減ります)
03. プロパティの参照が出来ない場合がある
主にGradleで利用するプロパティは gradle.properties に記述する方法や、 コマンド実行時に -P オプションで指定するのが一般的です。 そのプロパティをスクリプト内で参照する場合、以下の 2 つの参照方法があります。
1. プロパティ名の先頭に $ をつけてダブルクォーテーションで囲む
2. getPropertyメソッドにプロパティ名を指定して取得する
記述が短いので $ をつけて参照する方法を選んでしまいがちですが、 以下のようにドットやハイフンを含むプロパティ名の場合は参照できません。
× java.version=1.6 × java-version=1.6 ○ java_version=1.6
これは Bash をはじめとしたシェルスクリプトの変数と同じ仕様です。 Javaのpropertiesファイルは慣例的にドット区切りでプロパティ名を記述する傾向があります。 なので少なからずこの問題に陥る方もいらっしゃるかと思います。
じゃあ、ドットやハイフンを使った名前は利用できないのか?というと、そういうわけではありません。 上記の 2 の方法である getPropertyメソッドを利用すればドットやハイフンを含んだプロパティ名を参照できます。
そのため、私が普段スクリプトを記述する場合は、 コマンド実行時に -P オプションで指定したプロパティは 1 の方法で参照するようにし、 gradle.properties で指定されたプロパティは 2 の方法で参照するように区別しています。 その方がスクリプトを見たときにどこから指定されたプロパティを参照しようとしているのかが判断しやすいです。
04. hasPropertyメソッドが常に false を返してしまう
これもGradle初心者は陥りやすいかと思います。 -P オプションでプロパティを指定している場合で、 かつそのプロパティが必須ではない場合、プロパティの有無をスクリプト内で判断しないとエラーになってしまいます。 そこで、以下のように書いてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
この場合、常に hasProperty メソッドは失敗してしまいます。 hasPropertyメソッドを何も指定せずに呼び出すと TaskのhasPropertyメソッドがコールされてしまい、 実際にプロジェクトに設定されたプロパティを判断できなくなる場合があります。 そこで、以下のように指定すると正常に判定することが出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
上記のように「project.hasProperty」をコールすることで正常にプロパティ有無を判定することが出来るようになります。
05. 環境ごとにリソースファイルを切り替えたい
Mavenでは使用するリソースファイルを複数指定することが可能で、 更にはビルド時にプロファイルを指定することでそれを環境ごとに切り替えることが出来ます。 これをGradleで実現するには様々な方法がありますが、ここでは私が普段利用しているスクリプト例を紹介します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
上記の「sourceSets」のブロック内がリソースを環境ごとに切り替える処理になります。 この build.gradle のプロジェクト設定は前回も紹介したとおりIDEでも利用される設定となります。 そのため、ここでは環境指定が無い場合はローカル環境の設定であるものと見なしています。
では、他の環境指定を行うにはいつ行えばいいのかというと、これはビルド時に指定します。 WARやJARのビルドを行うときに -P オプションでプロパティを与えて読み込む環境を指定します。
1
|
|
こうすることで、「src/main/resources」「src/profile.development/resources」ディレクトリにあるリソースファイルが WARに追加されるようになります。
06. testCompileで依存関係となるプロジェクトを指定した場合にそのプロジェクトの testCompile を参照してくれない
これはどうやら仕様(?)のようで、 compileの場合は「compile project(‘:base’)」といったようにプロジェクトを指定するとその指定したプロジェクトの依存関係を継承しますが、 testCompileの場合は「testCompile project(‘:base’)」と指定しても、どういうわけか継承しないようです。 この問題に対するスマートな解決方法というのは現時点ではあまりないのですが、 強いてあげるとすれば、以下のように各プロジェクトごとに testCompile の依存関係を指定してあげることで ある程度管理しやすくなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
このように、拡張プロパティでテストの依存関係をまとめた変数を宣言し、 そこに利用する依存関係を全て記述すれば比較的管理のしやすい記述ができます。 これについてはもっとスマートな記述方法が今後登場しましたらそのときに再度紹介させていただきます。
07. EclipseのWTPを利用している場合に provided や providedCompile の指定が効かない
これは以前紹介させて頂いたEclipseのGradleプラグインである「Gradle Integration for Eclipse」のプラグインの仕様のようで、 このプラグインは自前で作成した provided の指定やWARプラグインを指定した場合のみ利用できる providedCompile の指定をしたとしても、 WTPでAPサーバを起動したときに WEB-INF/lib のディレクトリへ強制的に全ての依存関係ライブラリがコピーされてしまいます。要は provided であることを考慮してくれません。 この問題に対する公式な解決方法としては、Eclipseの設定から「Gradle」→「WTP」を選択し、そこに除去するプラグインを記述すれば WEB-INF/lib ディレクトリへコピーされなくなる、とのことです。 せっかくGradleを利用しているのに手動で設定するという点からもなんとも腑に落ちないですが…現状このプラグインを利用している以上はこうするしかないようです。
もう1つの解決策としては、「Gradle Integration for Eclipse」プラグインでインポートせず、 「gradle eclipse」コマンドを自前で実行してEclipseのプロジェクト設定ファイルを作成し、 作成後のプロジェクトをインポートするという方法です。 この方法であれば provided も考慮してプロジェクト設定を作成してくれます。 ただし、手動でコマンドを実行しなければならないという点がなんともスマートではないことと、 build.gradle を変更した場合(依存関係などを追加した場合)に再度コマンドを実行し直さなければなりません。 「Gradle Integration for Eclipse」プラグインを利用している場合はこのあたりを自動でやってくれます。
provided で除去するのは殆どの場合 servlet-api や jsp-api ですので、 腑に落ちない気持ちはありつつも、「Gradle Integration for Eclipse」プラグインを使用し、 Eclipseの設定で除去してしまう方法で利用していく方が無難かと思われます。
08. 複数のMavenリポジトリを定義する
参照するMavenリポジトリは repositories ブロック内で以下のように定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
任意のリポジトリを複数定義する場合は、mavenブロックに url パラメータを複数記述するかと思いがちですが、 そのような記述は出来ません。 以下のように記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
上記のように、mavenブロックを複数宣言することでURLを複数指定できます。
09. アーカイブタスクで exclude 指定が効かない
これも人によってはAntやMavenのときの記述に慣れてしまっているが故に勘違いしてしまいがちなのですが、 Zipなどのアーカイブタスクで exclude 指定をした場合に効かないように見えてしまう場合が有ります。
例として、前回紹介したbatchのパッケージを作成するタスクにおいて一部のファイルをZIPファイルへ含めないようにしたいと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
上記では、binディレクトリの中身にある test.sh のコピーを除去する想定で記述しました。 しかし、実際にこの状態でアーカイブを作成すると、 test.sh ファイルはZIPの中に含まれてしまいます。 AntやMavenの場合だと、exclude の指定を行えばそれだけで対象となるパスのファイルが除去されましたが、 Gradleの場合だと、以下のように指定する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
上記のように記述することで bin/test.sh ファイルをコピーしないようになります。 Gradleのアーカイブタスクにおける exclude 指定は、このようにコピーする対象となるディレクトリの from ブロック内に exclude 指定を記述する必要があります。
10. ブランチ切り替えを頻繁に行った場合に他のブランチのクラスファイルが残る場合がある
これは主にJenkinsでJavaビルドを行う場合に起こることが多いのですが、JenkinsではジョブのSCMのブランチ切り替えを頻繁に行うかと思います。 そうなると、例えばあるブランチにはあって、あるブランチにはないクラスファイルというものが当然のように出てきます。 そして、そのクラスファイルの一部が残ることでビルドエラーが発生する場合があります。 これを回避するには、以下のように「clean」タスクをビルド前に実行するように指定することで回避することが出来ます。(尤も、これはGradleに限らずMavenでも同じです)
1
|
|
clean タスクを実行すると更新する必要がないクラスも再コンパイルするようになってしまいますので、 ブランチ切り替えを行わないような例では開発スピードをあげるために、あえて clean を指定しないというのも手かと思います。 とはいえ、本番環境へリリースするWARやJARをビルドする場合は、万が一のことを考えて clean タスクを指定してビルドすることをお勧めします。
11. 突然動作がおかしくなった場合にキャッシュをクリアする方法
例えば、MavenリポジトリのURLを変更したなど、 さまざまな理由でキャッシュが残った場合に動作がおかしくなってエラーが発生する場合が有ります。 その場合、Gradleの作業用ディレクトリを削除すれば正常に動作する場合が有ります。 ユーザのホームディレクトリにある「.gradle」というディレクトリを削除することでキャッシュをクリアすることが出来ます。 ただしこの場合、Mavenの依存関係を一から取得し直しになるために、初回のビルド時に時間がかかります。 あくまで原因不明のエラー回復のための最終手段と思ってください。
まとめ
今回は私がGradleを利用した上でハマるであろうと考えたポイントを 11 個紹介させていただきました。 AntやMavenに慣れた人たちからすると、Gradleは最初のうちは少々取っ付きにくいものであるため、 このようにいくつかハマってしまうポイントが発生します。 Gradleをこれから使ってみようと思う皆様の参考になれば幸いです。
Gradleによるビルドとパッケージ作成
前回の続きです。 Gradleのプロジェクトの設定が終わっただけでは当然ながら終わりません。 次はビルドが必要になります。 プロジェクト構成は前回の例をそのまま踏襲するとして、 実際にビルドをするときのコマンドを紹介します。
WARとJARのビルド
WARのビルドは以下のコマンドで行います。 (build.gradle ファイルのあるディレクトリへ移動してコマンドを実行します)
1
|
|
なんとこれだけでWARのビルドが出来ます。 非常に簡潔です。 Gradleコマンドは立ち上がりが遅いのですが、上記のように —daemon というオプションを指定することでバックグラウンドに常駐するようになり、2回目以降のコマンドの立ち上がりが早くなります。
さて、前回のプロジェクト構成ですと、WARプロジェクトではないbatchプロジェクトが存在していたかと思います。 batchの場合はWARではなくJARとして作成する必要がありますので、上記のコマンドでは当然ビルドされません。 以下のコマンドでビルドします。
1
|
|
「:batch:jar」という指定は batch プロジェクトの jar タスクを実行するという指定になります。 WARのビルドの場合、build.gradle に記述されているWARプロジェクトを全て自動的に走査してビルドしてくれますが、 JARの指定を行う場合、WARプロジェクト自体もJARビルドが出来てしまうため、「gradle —daemon jar」としてしまうと 不要なプロジェクトに対してまでJARビルドが走ってしまいます。そのため、上記のようにプロジェクトを指定してビルドしています。
また、gradleの実行タスクはひとまとめにすることが出来ますので、WARビルドとbatchプロジェクトのJARビルドを合わせると以下のようになります。
1
|
|
リリース用パッケージの作成
次はリリース用のパッケージの作成についてです。 デプロイ時はAPサーバにそのままWARをコピーすれば良いのですが、 必要なファイルを全てひとまとめに圧縮して対象サーバへ転送し、ファイルを展開するような例も多いかと思います。 また、batchファイルについてはWARと違い、依存関係となるライブラリも全てコピーしてあげなくてはいけません。
Mavenだと maven-assembly-plugin というXMLに記述した構成でパッケージを作成するプラグインがありましたが、 これもXMLファイルに設定を記述していくので記述量が増えがちで若干面倒な感じがありました。 GradleではWARやJARの他にZIPやTARを生成するタスクも用意されており、 これらを組み合わせることで maven-assembly-plugin と同等の機能を実現できます。
具体的にbatchパッケージを作成する例を紹介しますと、batchプロジェクトの設定に以下のようにタスクを定義します。 (設定は前回のものを踏襲)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
上記にある distribution というタスクが新しく追加したタスクになります。 「type: Zip」という指定からもわかるように、このタスクではZIPファイルを作成します。 次に、dependsOn という指定がありますが、これは distribution タスクの実行が指定されたときに distributionタスク実行前に実行するタスクの指定になります。 ここでは jar タスクが指定されていますので batchプロジェクトのjar ビルドが先に実行されるということになります。 そして、distribution タスク内に記述されたディレクトリ構成でZIPファイルが作成されるという仕組みです。 into というブロックの引数に ZIP ファイルのルートからのパスを記述し、 そのブロック内に from という指定でコピーするファイルを指定するという感じです。
上記の例で実際に作成されるZIPファイルとその中身は以下のようになります。
batch - bin - (batch/binに含まれているファイル。シェルスクリプト等)
libs - batch.jar
batchプロジェクトの依存関係となるJARファイル全て
(configurations.compile 変数にコンパイル時に全ての依存関係ファイルへのパスが含まれている)
ちなみに、build/libs/batch.jar ファイルは jar タスクでビルド後に作成されるJARファイルになります。 (デフォルトだとbatchプロジェクトのディレクトリからの相対パスで build/libs/batch.jar として作成されます) 見ても判る通り、maven-assembly-pluginに比べて記述量が遥かに少ないことが判ります。 このようにしてリリース用パッケージも簡単に作ることが出来ます。
以上をまとめると、最終的にWARとJARおよびbatchパッケージを作成するコマンドは以下のようになります。
1
|
|
まとめ
このように、Gradleだとビルドとパッケージの作成も非常に簡単にできます。 ここまでの話ですとMavenを利用する場合に比べての優位性というのは有るには有るが、敢えて移行する必要まであるか?と感じられている方は多いかも知れません。 実際Mavenでも十分な例は多いです。
ただ、MavenとGradleが決定的に違うところは、 前者は「設定ファイルを記述する」ビルドツールであり、 後者は「スクリプトを記述する」ビルドツールであることです。 つまり、後者は println を使って途中経過を出力することが可能なので、デバッグも容易です。 Mavenのように正しい設定ありきで内部の処理の動きを追えないツールと比べると大いに勝っている利点であると私は考えています。
また、ここまで紹介していませんでしたが、 GradleはGradle自体をインストールしていない環境でもJavaさえあれば実行できる gradlew という仕組みもあります。 これも Maven にはない大きな利点です。
次回はこれらの点の紹介も含めて、Gradleを利用する際によく利用するであろうTIPS集をいくつか紹介していきたいと思います。
GradleでEclipseとIntellij IDEAの開発環境を作る
ちょっと前まではMavenでプロジェクトを構築している人も多かったですが、 Android StudioがGradleを採用してからはGradleによるプロジェクト構築にシフトしている人も増えてきました。 とはいえ、Web業界で古いシステムを保守・運用している方だとまだまだMavenメインの利用者が多いかと思いますので、 Mavenからの移行も含めた観点で記載して行きます。
Mavenと比較したGradleの利点
Mavenと比較したGradleの利点を簡単にまとめると以下のような感じです。
(この記事を執筆時のGradleの最新バージョンは 1.11 です)
- 記述量が圧倒的に少ない
- 記述言語がGroovyなのでJavaも使える。そのため比較的なんでも記述できる
- AntやMavenの機能も利用できる。特にAntはほぼすべての機能を利用可能。MavenはMavenプラグイン以外は殆ど利用可能
- IDE(EclipseやIntellij IDEA)の設定も細かく記述でき、そこからIDEの設定ファイルを生成できる
まだまだありますが、代表的な利点はこんな感じです。
プロジェクトの準備
では早速ですが、各種設定をGradleで記述するための準備をしていきます。 今回の例では、以下のようなプロジェクト構成とします。
プロジェクト名 | 説明 | コンテキストパス |
---|---|---|
base | ベースロジックを含んだJavaプロジェクト | なし |
batch | バッチシステム。baseプロジェクトを依存関係とする | なし |
admin | 管理サイト。baseプロジェクトを依存関係とする | /admin |
front | ユーザが閲覧するサイト。baseプロジェクトを依存関係とする | / |
また、各プロジェクトはMavenプロジェクトと同じで、以下のような一般的なWebアプリの構成にしておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
このディレクトリ構成にしておくと、余計な設定を記述する必要がないので便利です。
そして、Gradleにおいて設定ファイルを記述する場合、「build.gradle」というファイル名で設定ファイルを作成し、
この例のように複数プロジェクトがある場合は、Gradleのサブプロジェクトという機能を利用します。
サブプロジェクト機能を利用するには、「settings.gradle」というファイルを更に用意し、
build.gradleから定数値を外部ファイルから読み込みたい場合は、gradle.propertiesというファイルを用意します。
以上を踏まえた上で、ファイルとディレクトリ構成としては以下のようになります。
1 2 3 4 5 6 7 |
|
settings.gradle の記述
まず、settings.gralde を記述していきます。
1 2 3 4 |
|
これは project ディレクトリをメインのプロジェクトとしており、 その配下にある include で指定したディレクトリをサブプロジェクトとして扱うという指定になります。 以下のように指定することも可能です。
1
|
|
gradle.properties の記述
以下の定数値を用意します。
1 2 3 4 5 6 7 8 |
|
今回はこれらの値のみ外部ファイルへ記述します。
build.gradle の記述
それでは、メインとなる build.gradle の記述をしていきます。
以下のような内容になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
|
基本はこれだけでOKです。 Mavenの pom.xml と比較すると記述量が大分少なくなっていることがわかります。 また、Mavenの場合は、各プロジェクトのディレクトリ配下に pom.xml を記述する必要がありましたが、 Gradleでは1つのスクリプト内にすべての設定を記述できます。(敢えて分けて記述することも出来ます)
では、1つ1つ解説していきます。
サブプロジェクト全体の設定
各サブプロジェクト全体の共通設定は subprojects というブロック内で指定します。 ここから1つ1つ見て行きましょう。
01. プラグインの指定
先頭に apply plugin という指定がありますが、 これはどのプラグインを利用するかという指定です。 「Javaのimportのように単純に使いたいメソッドやクラスとかを呼び出すために定義しているだけだろう」と思われる方もいるかもしれませんが、 Gradleにおいては少し意味合いが違って、apply pluginを記述した段階でビルド動作に影響を与えます。
1 2 3 4 |
|
この4つの指定では、各サブプロジェクトが
・「Javaプロジェクトである」
・「Eclipseプロジェクトである」
・「Eclipse WTPプロジェクトである」
・「Intellij IDEAプロジェクトである」
ということをGradle側へ伝えています。
この記述をすることで、各サブプロジェクトごとにEclipseやIntellij IDEAの設定ファイルを生成することが出来るようになります。
02. providedコンパイルの設定
「configurations」というブロックがあります。ここではプロジェクトで利用する設定パラメータを定義できます。 後述する依存関係の記述に関係しますが、providedコンパイルを実現するために、ここで provided というパラメータを宣言しています。 実は、Gradleでは Maven のような provided コンパイルの動作を標準ではサポートしていません。 とはいえ、簡単に同様の処理を実現することが出来ます。そのための準備として、ここにパラメータを宣言します。
03. Javaのバージョンの設定
「sourceCompatibility」「targetCompatibility」という記述がありますが、 これはビルド時に使用されるJavaのバージョン指定になります。 これを記述しない場合、インストールされているJDKのバージョンでビルドされます。 また、ここで指定したバージョンは、EclipseのJavaのバージョン指定にも利用されます。 基本的には環境依存がないようにバージョン指定をしておくことをオススメします。
04. Eclipseでコンパイルしたクラスファイルの出力先の設定
「eclipse」というブロック内に、「classpath.defaultOutputDir」というプロパティがあります。 ここに指定されたディレクトリにコンパイルしたクラスファイルが出力されます。 指定しない場合は、各プロジェクトのディレクトリ直下に「bin」というディレクトリが作成され、そこに出力されます。
依存関係の記述
今回の例では、baseプロジェクトに利用する依存関係をすべて定義し、 各サブプロジェクトにおいては base プロジェクトそのものを依存関係として定義しています。 これはGradleに詳しくない方でも上記スクリプトを見ても大体予想がついているかと思いますが、 「dependencies」という部分で定義します。 dependenciesではMavenリポジトリにあるライブラリを指定することが出来ます。 指定の形式としては、以下のようになります。
1
|
|
groupId、artifactId、versionをコロン(:)区切りで指定します。 しかし、Servlet APIのJARなど、中にはWARビルド時にWAR内へ含めたくないライブラリもあります。 その場合は以下のような指定が必要になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
このような指定をすることで、 依存関係をリポジトリから取得しつつも、WARには含めないようにすることが出来ます。 今回は base プロジェクトに宣言された provided な依存関係を base プロジェクトを依存関係とする各プロジェクトへ追加するため 「project(‘:base’).configurations.provided」を追加していますが、 そのプロジェクトのみで provided の依存関係を定義する場合は「configurations.provided」を各クラスパスへ += で追加するようにしてください。
WARの設定
今回はTomcatを利用することを前提としていますので、 その場合の設定です。 まず、EclipseでWTPを利用する場合の設定が以下になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
各ブロック名を見れば勘のいい方は大体理解できるかと思います。 Eclipseの動的Webプロジェクトの必要な設定を上記の設定で記述しています。 また、実際のWARファイル名は以下の指定になります。
1 2 3 4 5 6 7 8 9 10 11 |
|
ここで指定された名前がWARファイル名になります。(拡張子は不要)
Tomcatの場合だと、server.xmlに記述をしない場合はWARファイル名がそのままコンテキストパスになりますので
(ROOTという名前は例外として / になる) ここでWARファイル名を指定します。
一見するとEclipseの方で設定しているからWARファイル名は勝手に設定してくれればいいと思う方もいるかもしれませんが、 Eclipseの設定はあくまでEclipseにおける設定なので、 実際にWARを作成時に使われる設定値ではありません。
実際にIDEで読み込んでみる
あとは実際にIDEでGradle Projectとしてインポートすれば完成です。
Gradle Projectとしてインポートするためには、Eclipseの場合はEclipse Marketplaceにある「Gradle Integration for Eclipse」プラグインをインストールしてください。
このプラグインを利用すると、リモートリポジトリから取得した依存関係の参照パスを
ローカルPCのパスとしてEclipse設定ファイルへ記述しなくなるため
Eclipseプロジェクトの設定ファイルを複数人で共有するプロジェクトにおいては最適です。
Gradle Integration for Eclipseプラグインをインストールしたら
このプラグインのEclipse設定からソースファイルのエンコーディングの設定(-Dfile.encoding=UTF-8)を追加するのを忘れないでください。
そのままだとWindowsのデフォルトエンコーディングがMS932のため問題が出ます。
Intellij IDEAにおいては、12以上のバージョンを利用している場合はJetGradleというIDEのエンジンが
build.gradleの内容を解析してプロジェクトの作成をしてくれます。
ただし、Intellij IDEAのartifactsやfacetの設定はすべてを自動ではやってくれないので一部手動で行う必要があるのと、
上記で記述した provided 指定したライブラリについては、JetGradle側で export 設定を何故か行わないため
ここも手動でチェックをつけてあげる必要が出てきます。
13.1からは自動でartifactsやfacetも生成してくれますが、artifactsは複数プロジェクトに対応していないのかROOTコンテキストのプロジェクト分しか作成されません。
Gradle側のIntellij IDEA対応はあまり熱心ではないみたいなのでこの辺はいずれ対応してくれるであろうことを期待しましょう。
【2014/04/28追記】 Intellij IDEAでartifactsが作成されない不具合がEAP版の13.1 135.760で修正されました。 早々に試したい方はこちらからダウンロードできます。
まとめ
以上、早足で説明しましたが、 導入としてはこれくらいの知識があれば利用できます。 次回は実際にデプロイを行うときの手順を Mavenからの移行の観点を交えて説明していきたいと思います。