Gradle による Android アプリのビルド
週末は Gradle と Android アプリのビルドツールと戯れていたので、得られた知見などを書き残しておく。
Gradle について
- Gradle 公式サイト: Gradle - Build Automation Evolved
Java 周辺のプロジェクト管理ツール (ビルドシステム?) といえば Ant や Maven があるけど、最近 (?) 注目を集めているのが Gradle。 XML で記述される Ant や Maven とは違い、Gradle のビルドファイルは Groovy による DSL で記述される。 Ruby におけるビルドツールである Rake と似たような雰囲気。 記述の容易さはもちろんのこと、Ant のタスクをそのまま使えたり、依存関係解決に Maven リポジトリを使えたりする など、良さそうな雰囲気を醸し出している。
今のところの難点としては、Maven を使っている場合と比べて依存関係の解決が甘い (?) ことがあること。
ただ、これは Android アプリ開発時には問題にはならないと思う。
Gradle と Android アプリのビルド
Android アプリのビルドツールとして公式的には Ant が採用されているが、最近 Gradle を使った新しいビルドシステムを開発しているらしい (Android Studio に合わせて公開されたのかな)。
Gradle のプラグインとして android プラグインが提供されている。 このプラグインは Maven のセントラルリポジトリで公開されているので、build.gradle に次のように記述するだけで Android アプリのビルド用のあれこれが使えるようになる。
buildscript { repositories { mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:0.7.1+' } } apply plugin: 'android' android { compileSdkVersion 19 }
buildscript
の部分では、ビルドスクリプト自体の依存関係を解決している (参考: 第59章 ビルドロジックの体系化)。 android
の部分が Android アプリのビルドのための設定を記述する場所。
サンプルプロジェクトを作ってみた
- 作ってみたサンプルプロジェクト: nobuoka/android-gradle-build-system-sample · GitHub
実際に試してみないとわからないのでサンプルプロジェクトを作ってみた。 ビルド方法などは README に書いてある。
Gradle ラッパーについて
サンプルプロジェクトに含まれている次のファイルは Gradle ラッパーのためのもの。
- gradlew ファイル
- gradlew.bat ファイル
- gradle/ ディレクトリ
Gradle ラッパー (gradlew) というのは gradle コマンドの代わりに使用されるもので、(既にシステムにインストール済みで) gradle コマンドが使用できるならば *1 それに処理を委譲し、使用可能でなければインターネット経由で gradle を取得してローカルディレクトリにインストールしてそれを使用する、というものである。
- 日本語ドキュメント: 第61章 Gradleラッパー
Android Studio で新しくプロジェクトを作るとこれらのファイルが含まれているので、バージョン管理する必要があるのかどうか悩むところではあるが、あると便利といえば便利 (特にバージョンが固定されるのが良い) なので、特に理由がなければバージョン管理すれば良さそう。 (Gradle コミュニティの文化としては、Gralde ラッパーはバージョン管理するものであるようだ。)
マルチプロジェクトについて
上に書いたサンプルプロジェクトでは、アプリ本体と、ライブラリ Volley をそれぞれサブプロジェクトとしており、マルチプロジェクト構成になっている。 ライブラリは基本的に AAR や JAR で扱えるのが一番良い *2 と思うのだけど、Volley みたいに公式的な JAR がない場合や、自社で開発しているライブラリを使用しながらどんどん変更を加えていきたい場合などは、ライブラリをサブプロジェクトとしてプロジェクトに追加するのが良さそうだと思う。
- Android のビルドツールのマルチプロジェクト構成のドキュメント: Gradle Plugin User Guide - Android Tools Project Site
- Gradle 全般のマルチプロジェクト構成のドキュメント: Chapter 56. Multi-project Builds
マルチプロジェクト構成は次のような感じになるっぽい (間違ってるかも)。
- 親プロジェクトに settings.gradle というファイルがあって、サブプロジェクトのディレクトリがどれかを指し示す
- 各サブプロジェクトは親プロジェクト内の好きな場所 (?) に置ける
- 親プロジェクトも各サブプロジェクトもそれぞれ独自の build.gradle を持つことができる
- 親プロジェクトで Gradle のタスクを実行すると、すべてのサブプロジェクトの同じタスクが実行される
サブプロジェクトのビルドスクリプトを親プロジェクトの build.gradle に記述できる
結構面白いと思ったのが、サブプロジェクトのビルドスクリプトを、そのサブプロジェクトの build.gradle ではなく、別の build.gradle (例えば親プロジェクトの build.gradle) に記述できるということ。
例えば今回のサンプルプロジェクトだと、サブプロジェクトとして Volley を使っているわけだけど、リポジトリは本家のものを使用しているのでサブプロジェクト内の build.gradle を変更することができない。 なので親プロジェクトの build.gradle で Volley 用のビルド設定を変更する、なんてことをやってみた。
また、昔の Volley は build.gradle を持っていなかったので、それこそビルドスクリプトを全て親プロジェクトに書いたりしていた。
基本的にはサブプロジェクトのビルドスクリプトはそのリポジトリの中に入れておきたいところではある。
ディレクトリ構成
Gradle の Java プラグインでのデフォルトのディレクトリ構成は、Maven と同じで src/main/java 以下に Java のソースコードを配置する、というものらしい (参考: Chapter 23. The Java Plugin)。 Android アプリ用のプラグインでも同じようなディレクトリ構成が採用されている。
もちろんデフォルトのディレクトリ構成じゃないプロジェクトでも簡単に対応することができる。 サンプルプロジェクトでも、Volley のディレクトリ構成に合わせてビルドスクリプトを記述した。
ソースコードのエンコーディング指定
javac コマンドは、ソースコードのエンコーディング設定としてデフォルトではシステムのエンコーディングを使用する。 システムのエンコーディングが UTF-8 になっている環境 (多くの *iux 系環境ではそうだと思う) では問題になることは少ないが、Windows を使用していると痛い目に合うことが多い。 Gradle で Java のコンパイル時のエンコーディング指定をするのは次のように書くのが今のところ最良のような気がする。
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
コマンドで PC でビルドした Android アプリのパッケージを Android 端末にインストールする
Android Gradle プラグインは、Android 端末にアプリのパッケージをインストールするタスクも提供している。 installXxxx
というタスクがそれである。 (./gradlew tasks
で確認できる。) 例えば、Debug ビルドのパッケージをインストールする場合は、次のようにすればよい。
./gradlew installDebug
すると、接続している Android 端末に、debug ビルドのパッケージがインストールされる。
古い内容 (自分でタスクを起動する処理を書いてみた)
Gradle のタスクとしてアプリを起動するタスクを書いておけば便利。 android プラグインで提供されてるような気もするけど見つけられなかった。 → 上で書いたように、パッケージをインストールするタスクは提供されている。 起動までするタスクはないようだけど。
// Android SDK Tools に含まれる adb コマンドのパスを文字列で返す def getAdbCmdPath = { def androidPlugin = project.plugins.getPlugin('android') // プラグインオブジェクトの取得方法: http://www.gradle.org/docs/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:plugins def sdkDir = androidPlugin.sdkDirectory // android プラグインのソースコード: https://android.googlesource.com/platform/tools/build/ return new File(sdkDir, 'platform-tools/adb').toString() } task installToDevice(type:Exec) { // apk ファイル名 (TODO 自動で取得できるように) def apkPackageName = 'GradleBuildSystemSample-debug-unaligned.apk' // デバイスへのインストール def installCmd = getAdbCmdPath() + ' -d install -r ' + buildDir + '\\apk\\' + apkPackageName // コマンド実行 (Windows 用) commandLine 'cmd', '/c', installCmd } task runOnDevice(type:Exec) { // Main Activity のパッケージ名とアクティビティクラス名 (TODO 自動で取得できるように) def mainPackage = 'info.vividcode.android.app.gradlebuildsystemsample' def mainActivity = 'MainActivity' // コマンド実行 (Windows 用) commandLine 'cmd', '/c', getAdbCmdPath() + ' shell am start -a android.intent.action.MAIN -n ' + mainPackage + '/.' + mainActivity }
起動する Activity のパッケージ名などを動的に取得できるようにしたいところ。
Eclipse 対応
ビルドシステムとして Gradle を使えば Android Studio にいい感じにインポートできるが、Eclipse へのインポートはそう簡単にはいかない。
Gradle のデフォルトのディレクトリ構成では、Eclipse の Android プラグイン (com.android.ide.eclipse.adt) が対応できないっぽい。 プラグインのソースコードを読んだ感じだと、res ディレクトリの位置などはプロジェクト直下にあることが想定されていて、外部から変更できない感じだった *3。
Eclipse 上で Gradle のタスクを実行すること自体は難しくなくて、Gradle Integration for Eclipse というプラグインを使用すればよい。 今回のサンプルプロジェクトは主に Eclipse 上で開発したのだが、その際もこのプラグインを使用して Gradle のビルドを実行するという感じで開発していた。 *4
Gradle をビルドシステムとして使用するプロジェクトで、Eclipse でも開発できるようにするには、とりあえずは次のようにするのが良いのかなーと思った。
まとめ
Building and Testing With Gradle
- 作者: Tim Berglund,Matthew Mccullough,Hans Dockter
- 出版社/メーカー: Oreilly & Associates Inc
- 発売日: 2011/07/13
- メディア: ペーパーバック
- クリック: 7回
- この商品を含むブログ (7件) を見る
- Gradle 便利
- Groovy 便利
- 今後は Android アプリのビルドも Gradle で管理するのが良さそう
- マルチプロジェクト構成とかディレクトリ構成とかプラグイン周りのこととか、基本的に Gradle を勉強しなきゃなー、という感じ
CodePlex からの git fetch に失敗する問題とその解決策、あるいは Ubuntu 12.04 で Git 1.8 をインストールする方法
CodePlex からの git fetch でエラーが発生する
CodePlex から TypeScript のソースコードを git fetch しようとしたところ、以下のようなエラーが発生した。
error: RPC failed; result=56, HTTP code = 200
なんなんだろうなー、と思って調べてみたら、Git のバグだという情報があった。
- CodePlex Information and Discussion - View Issue #26133: error: RPC failed; result=56, HTTP code = 200
- CodePlex Information and Discussion - View Issue #26501: 'git clone https://git01.codeplex.com/z3' fails on Linux machine
最新の Git だと直ってるらしい。 ちなみに私が使用していた Git のバージョンは 1.7.9.5 であった。
Ubuntu 12.04 で Git 1.8 を使う
最新の Git を使えばいいということなので、Ubuntu 12.04 の Git のバージョンを上げることにした。 Ubuntu デフォルトの apt のリポジトリだと最新バージョンが 1.7.9.5 なのだけど、“Ubuntu Git Maintainers” team の人たちが Ubuntu 用に最新の Git の stable バージョンを提供するリポジトリを公開してくれているので、それを使わせてもらった。
Synaptic パッケージマネージャの 「ソフトウェアソース」 ウィンドウの 「他のソフトウェア」 タブの 「追加」 ボタンを押して、「APT ライン」 欄に “ppa:git-core/ppa” って入力して 「ソースを追加」 ってクリックすればそれだけで追加される。 リポジトリ追加後にはパッケージ情報の更新が必要なので、「再読込」 する。
コマンドラインで apt-get 使ってる人なら以下のような感じかな。
# リポジトリ追加 $ sudo apt-add-repository ppa:git-core/ppa # 再読み込み $ sudo apt-get update
あとは apt で Git のアップデートをすればよし。
JavaScript 向け IntelliSense でアンダースコア (_) で始まるプロパティ名を候補として表示する
Visual Studio 2012 で JavaScript を書くとき (Windows ストアアプリの開発とか) の話です。
昨日書いた ように、Visual Studio には IntelliSense とよばれる入力支援機構があり、JavaScript を書く時にもプロパティ名の補完などをしてくれます。 Visual Studio 2012 のデフォルトの設定では、アンダースコア (_) で始まるプロパティ名は原則として "this" のプロパティ名の補完の場合にのみ表示されるようになっています。 アンダースコアで始まるプロパティ名はプライベートであることを示すという慣例のためにこのようになっているのだと思います。
この機能は便利とはいえ、"this" 以外のプロパティの候補としてもアンダースコアで始まるプロパティ名を表示してほしいことはままあります。 例えば、以下のようにあるメソッドの中でコールバックとして関数式を書いている場合。 コールバックの関数式の中では "this" は外側のオブジェクトを指すわけではないので、"that" とか "self" とかの識別子をもつ変数に this を結びつけることが多いと思います。
Something.prototype.method = function () { var that = this; return WinJS.Promise.timeout(200).then(function () { // ここで this は Something オブジェクトを指すわけではないので, that を使う that._doPrivateMethod(); }); };
このとき、"that" という識別子の場合にもアンダースコアで始まるプロパティも補完候補に出してほしいわけですね。
で、そのためにどうすればいいかというと、Visual Studio 2012 のインストールディレクトリにある JavaScript\References\underscorefilter.js というファイルに書かれている処理を書き換えてやればよいです。
intellisense.addEventListener('statementcompletion', function (event) { if (event.targetName === "this") return;
ファイルを見ると上のようになっていて、対象の識別子が "this" の場合だけは補完候補のフィルタリングをしないようになっています。 なので、例えば "this" だけでなく "that" の場合もフィルタリングしないようにしたければ以下のように書き換えます。
intellisense.addEventListener('statementcompletion', function (event) { var selfReferencedNames = { "this": true, "that": true }; if (selfReferencedNames[event.targetName]) return;
他にも色々と IntelliSense のカスタマイズをできるようですね。 詳細は下記ドキュメントをご覧ください。
追記: 「アクセスが拒否されました。」 というエラーが出る
underscorefilter.js を編集して保存しようとしたら 「アクセスが拒否されました。」 というエラーが出ました。 このエラーが出る場合はアクセス権限がないのが理由なようなので、ファイルのプロパティからアクセス権限を変更する必要があります。
WinJS.Promise のような非同期コールバックに IntelliSense を提供する方法
Windows ストアアプリを JavaScript で開発する際の、Visual Studio における入力支援機構 (IntelliSense) の話です。
IntelliSense と XML ドキュメントコメントについて
Visual Studio には、IntelliSense とよばれる入力支援機能が搭載されています。 JavaScript 用の補完機能等もあり (Visual Studio 2012 で確認)、Windows ストアアプリを JavaScript で書く場合には大いに助けになります。
ただ、JavaScript は静的型付け言語ではないので、やはりコードを書くだけでは補完が十分でないことが多いです。 そのため、XML ドキュメントコメントを用いて、オブジェクトのプロパティの情報や関数の返り値の情報を IntelliSense に与えることができるようになっています。
例えば、ある関数が文字列を返す場合は、以下のように returns タグで返り値の情報を与えることができます。
function doSomething () { /// <returns type="String">何らかの処理によって生成された文字列</returns> // 何らかの処理... return "..."; }
詳細は下記ページをご覧ください。
WinJS.Promise のような非同期コールバックのための returns の書き方
上に書いた例では文字列を XML ドキュメントコメントとして returns タグを書いて文字列を返すことを明示していますが、単に文字列を返すような単純な関数の場合は、XML ドキュメントコメントを書かなくても文字列を返す関数であることを IntelliSense はちゃんと認識してくれます。
しかし、WinJS.Promise で非同期コールバックをするような複雑な関数の場合は自動的には解析してくれないので、XML ドキュメントコメントを書く必要があります。 具体的には、XML ドキュメントコメントの return 要素に value 属性を与えてやり、その中に返り値に相当する値を生成する式を書いてやればよいです。
value 属性については、以下のように説明があります。
- value
- 省略可能。 関数コードではなく、IntelliSense によって評価自体に対して使用する必要のあるコードを指定します。 たとえば、Promiseのような非同期コールバックに IntelliSense を提供するために、この属性を使用できます。 <returns> の要素で value の属性を使用して、長いコードの実行をバイパスすることにより、IntelliSense のパフォーマンスが向上します。
<returns> の説明 (MSDN)
例
例えば、WinJS.Promise でラップされた TemporaryCredentials オブジェクトを返す requestTemporaryCredentials という関数を考えてみます。 XML ドキュメントコメントの returns 要素の value 属性には、WinJS.Promise でラップされた TemporaryCredentials オブジェクト を生成する式を与えてやればよいです。 すなわち、WinJS.Promise.wrap( new TemporaryCredentials() )
という式です。
var TemporaryCredentials = WinJS.Class.define(function (token, secret) { /// <field name="token" type="String">The temporary credentials identifier</field> /// <field name="secret" type="String">The temporary credentials shared-secret</field> this.token = token; this.secret = secret; }); function requestTemporaryCredentials() { /// <returns value='WinJS.Promise.wrap(new TemporaryCredentials())'>取得した OAuth の Request Token の情報</returns> return WinJS.Promise.wrap(). then(function () { // ... }). then(function () { // ... }). then(function (credentialsInfo) { // 最終的に TemporaryCredentials オブジェクトを返す return new TemporaryCredentials(credentialsInfo.token, credentialsInfo.secret) }); } requestTemporaryCredentials().then(function (creds) { // XML ドキュメントコメントにより creds が TemporaryCredentials オブジェクトであることを // IntelliSense が認識するので, creds のプロパティの補完候補として "token" や "secret" が出現する });
実際に動かしてみると、以下のように補完候補が表示されます。
WinJS.Promise を使う機会は結構あるので、補完が働かなくて不便だと思ったときには XML ドキュメントコメントをちょこちょこ入れていくと良さそうだと思いました。