2013年6月8日土曜日

githubをMavenリポジトリとしてAndroidライブラリプロジェクト(aar)をデプロイして使用する

序章

Android StudioとGradleのビルドシステム(Plugin)が正式にリリースされ、個人的に気になっていたAndroidライブラリプロジェクトの扱いについて調べてみた。新しいGradleのビルドシステムではライブラリプロジェクトはaar(Android archive)という1つのアーカイブで配布することが可能になった。aarファイルは拡張子によって判別され、フォーマットはzip形式である。(aarファイルについての詳細はまた今度。)従来はライブラリプロジェクトを使用するためにプロジェクト一式が必要だったけど、1つのアーカイブを参照するだけで使用出来るようになるので再利用しやすくなったってこと。

※ちなみにこれまでもandroid-maven-pluginというサードパーティ製のプラグインを使用することで解決されてきたのだけど、個人的には正式にサポートされたというのが大きい。

現時点ではビルドシステムのソースコードを見る限り、aarを参照する方法は"External module dependencies"タイプしかサポートされていない。これはどこかのリポジトリに配置されたモジュールを参照するということ。Gradleでは様々なリポジトリ形式に対応しているので、それらの形式(MavenとかIvyとか)でaarをデプロイする必要がある。

注意したいのはただ単にローカルファイルシステム上に配置されたaarを参照出来ないという点である。以下はNGパターン。

dependencies {
    compile files('path/to/some/a.aar', 'path/to/some/b.aar')
    compile fileTree(dir: 'path/to/some', include: '*.aar')
}

GradleでもMavenリポジトリ形式が一般的っぽいのでとりあえずaarをMavenリポジトリ形式でgithubにアップロードして使用出来るか試してみた。結果何事もなく出来たので手順を公開しておく。

環境

  • Android Studio 0.1.1
  • Mac OS X 10.8.3

デプロイ編

まずはAndroid Studioを使用して新規プロジェクトを作成する。こんな感じ。Eclipseユーザーにはわかりづらいかもしれないけど、Android Studioで新規プロジェクトを作成する場合はプロジェクト=アプリではなく、モジュール=アプリとなる。つまり、1つのプロジェクト内にライブラリモジュールや本体アプリモジュール、テストアプリモジュールを配置していくことになる。慣れればこっちのがわかりやいかもね。ちなみにEclipseでbuild.gradleファイルをエクスポートしてAndroid Studioでインポートする場合は上記の通りではない。

ナビ通り進めていくと以下のようなbuild.gradleを含んだプロジェクトが作成される。

現時点でのAndroid Studioは「Mark this project as a library」にチェックを入れてもライブラリプロジェクトとして扱ってくれないらしい。。なので手動で修正。(ソースコードを見る限りそのうち直ると思う。)

apply plugin: 'android'

の箇所を以下に変更。

apply plugin: 'android-library'

試しにaarが作成されるか、ターミナルを起動してビルドしてみる。aarはデバッグ版はassembleDebug、リリース版はassembleReleaseのGradleタスクで作成される。プロジェクト配下で以下のコマンドを実行。

$ cd <project_home>
$ ./gradlew assembleRelease

以下ファイルが生成されていればOK。

$ find . -name '*.aar'
./library/build/libs/library.aar

続いて、Mavenプラグインを使用してMavenリポジトリ形式でデプロイするためlibrary/build.gradleに追記する。Mavenプラグインについてはここを参照。

// プロジェクトルート配下のrepositoryディレクトリにデプロイする
def deployTo = new File(rootDir, "repository")

// Mavenプラグインの利用を宣言
apply plugin: 'maven'

uploadArchives {
    repositories {
        mavenDeployer {
            repository url: "file://${repos.absolutePath}" // デプロイ先
            pom.version = '0.0.1' // ライブラリバージョン
            pom.groupId = 'com.u1aryz' // グループ名
            pom.artifactId = 'sample-lib' // アーティファクト名
        }
    }
}

ターミナルに移ってプロジェクト配下で以下のコマンドを実行。

$ ./gradlew uploadArchives

プロジェクト配下のrepositoryディレクトリに以下のようにファイル群が出来ていれば成功。

$ ls -laR repository
total 0
drwxr-xr-x   3 u1aryz  staff  102  6  1 02:01 .
drwxr-xr-x  18 u1aryz  staff  612  6  1 02:01 ..
drwxr-xr-x   3 u1aryz  staff  102  6  1 02:01 com

repository/com:
total 0
drwxr-xr-x  3 u1aryz  staff  102  6  1 02:01 .
drwxr-xr-x  3 u1aryz  staff  102  6  1 02:01 ..
drwxr-xr-x  3 u1aryz  staff  102  6  1 02:01 u1aryz

repository/com/u1aryz:
total 0
drwxr-xr-x  3 u1aryz  staff  102  6  1 02:01 .
drwxr-xr-x  3 u1aryz  staff  102  6  1 02:01 ..
drwxr-xr-x  6 u1aryz  staff  204  6  1 02:01 sample-lib

repository/com/u1aryz/sample-lib:
total 24
drwxr-xr-x  6 u1aryz  staff  204  6  1 02:01 .
drwxr-xr-x  3 u1aryz  staff  102  6  1 02:01 ..
drwxr-xr-x  8 u1aryz  staff  272  6  1 02:01 0.0.1
-rw-r--r--  1 u1aryz  staff  298  6  1 02:09 maven-metadata.xml
-rw-r--r--  1 u1aryz  staff   32  6  1 02:09 maven-metadata.xml.md5
-rw-r--r--  1 u1aryz  staff   40  6  1 02:09 maven-metadata.xml.sha1

repository/com/u1aryz/sample-lib/0.0.1:
total 936
drwxr-xr-x  8 u1aryz  staff     272  6  1 02:01 .
drwxr-xr-x  6 u1aryz  staff     204  6  1 02:01 ..
-rw-r--r--  1 u1aryz  staff  457944  6  1 02:09 sample-lib-0.0.1.aar
-rw-r--r--  1 u1aryz  staff      32  6  1 02:09 sample-lib-0.0.1.aar.md5
-rw-r--r--  1 u1aryz  staff      40  6  1 02:09 sample-lib-0.0.1.aar.sha1
-rw-r--r--  1 u1aryz  staff     422  6  1 02:09 sample-lib-0.0.1.pom
-rw-r--r--  1 u1aryz  staff      32  6  1 02:09 sample-lib-0.0.1.pom.md5
-rw-r--r--  1 u1aryz  staff      40  6  1 02:09 sample-lib-0.0.1.pom.sha1

このファイル群を構成そのままでまるまるgh-pagesブランチにコミットしてgithubにプッシュする。以下のようになっていればOK。

ここまで手順通りに進んでいれば以下の記述をbuild.gradleに追記するだけでaarを参照することが出来る。

repositories {
    maven { url 'http://<githubユーザー名>.github.com/<githubリポジトリ名>/repository' }
}

dependencies {
    compile 'com.u1aryz:sample-lib:0.0.1'
}

※Mavenリポジトリと言ってもただのファイル群なので、プロジェクト配下でなくてもいいけど、その場合はmavenのurlは変わる。

ビルド編

今度はgithubにデプロイしたライブラリプロジェクトを参照してビルドしてみる。自分でデプロイしたものを参照したい場合は適宜読み替えて。ここでは俺がデプロイしたやつを使用するでよ。

新規プロジェクトを作成してもいいけど、めんどいのでさっき作成したSampleLibraryプロジェクトにAndroid Applicationモジュールを追加する。

  • Android StudioでFile -> New Module…を選択
  • Android Applicationを選択してNext

ナビ通り進めて作成されたmainモジュールのbuild.gradleにrepositoriesとdependenciesを追記する。

buildscript {
    repositories {
        maven { url 'http://repo1.maven.org/maven2' }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.4'
    }
}

// 追記
repositories {
    maven { url 'http://u1aryz.github.com/Android-NewPopupMenu/repository/release' }
}

apply plugin: 'android'

dependencies {
    compile files('libs/android-support-v4.jar')
    compile 'com.u1aryz:android-newpopupmenu-lib:1.0.1' // 追記
}

android {
    compileSdkVersion 17
    buildToolsVersion "17.0.0"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 16
    }
}

プロジェクトを開き直すとAndroid Studioがbuild.gradleを読みなおして以下のようにExternal Librariesに追加されているはずや。(これはちょっとめんどい)

あとは今まで通りライブラリプロジェクトを使用出来る。

終章

自分で手を入れる必要のないライブラリプロジェクトを使用したい場合は、この方法のがスマートだね。例えば、ActionBarSherlockとかを使用したい場合とか。ちなみにActionBarSherlockは4.3.2でGradle対応するのでmavenCentralにアップされるのが待ち遠しいね〜。

'},ClipboardSwf:null,Version:'1.5.1'}};dp.SyntaxHighlighter=dp.sh;dp.sh.Toolbar.Commands={ExpandSource:{label:'+ expand source',check:function(highlighter){return highlighter.collapse;},func:function(sender,highlighter) {sender.parentNode.removeChild(sender);highlighter.div.className=highlighter.div.className.replace('collapsed','');}},ViewSource:{label:'view plain',func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/'+code+'');wnd.document.close();}},CopyToClipboard:{label:'copy to clipboard',check:function(){return window.clipboardData!=null||dp.sh.ClipboardSwf!=null;},func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(//g,'>').replace(/&/g,'&');if(window.clipboardData) {window.clipboardData.setData('text',code);} else if(dp.sh.ClipboardSwf!=null) {var flashcopier=highlighter.flashCopier;if(flashcopier==null) {flashcopier=document.createElement('div');highlighter.flashCopier=flashcopier;highlighter.div.appendChild(flashcopier);} flashcopier.innerHTML='';} alert('The code is in your clipboard now');}},PrintSource:{label:'print',func:function(sender,highlighter) {var iframe=document.createElement('IFRAME');var doc=null;iframe.style.cssText='position:absolute;width:0px;height:0px;left:-500px;top:-500px;';document.body.appendChild(iframe);doc=iframe.contentWindow.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write('

'+highlighter.div.innerHTML+'

');doc.close();iframe.contentWindow.focus();iframe.contentWindow.print();alert('Printing...');document.body.removeChild(iframe);}},About:{label:'?',func:function(highlighter) {var wnd=window.open('','_blank','dialog,width=300,height=150,scrollbars=0');var doc=wnd.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write(dp.sh.Strings.AboutDialog.replace('{V}',dp.sh.Version));doc.close();wnd.focus();}}};dp.sh.Toolbar.Create=function(highlighter) {var div=document.createElement('DIV');div.className='tools';for(var name in dp.sh.Toolbar.Commands) {var cmd=dp.sh.Toolbar.Commands[name];if(cmd.check!=null&&!cmd.check(highlighter)) continue;div.innerHTML+=''+cmd.label+'';} return div;} dp.sh.Toolbar.Command=function(name,sender) {var n=sender;while(n!=null&&n.className.indexOf('dp-highlighter')==-1) n=n.parentNode;if(n!=null) dp.sh.Toolbar.Commands[name].func(sender,n.highlighter);} dp.sh.Utils.CopyStyles=function(destDoc,sourceDoc) {var links=sourceDoc.getElementsByTagName('link');for(var i=0;i');} dp.sh.Utils.FixForBlogger=function(str) {return(dp.sh.isBloggerMode==true)?str.replace(/
|
/gi,'\n'):str;} dp.sh.RegexLib={MultiLineCComments:new RegExp('/\\*[\\s\\S]*?\\*/','gm'),SingleLineCComments:new RegExp('//.*$','gm'),SingleLinePerlComments:new RegExp('#.*$','gm'),DoubleQuotedString:new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"','g'),SingleQuotedString:new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'",'g')};dp.sh.Match=function(value,index,css) {this.value=value;this.index=index;this.length=value.length;this.css=css;} dp.sh.Highlighter=function() {this.noGutter=false;this.addControls=true;this.collapse=false;this.tabsToSpaces=true;this.wrapColumn=80;this.showColumns=true;} dp.sh.Highlighter.SortCallback=function(m1,m2) {if(m1.indexm2.index) return 1;else {if(m1.lengthm2.length) return 1;} return 0;} dp.sh.Highlighter.prototype.CreateElement=function(name) {var result=document.createElement(name);result.highlighter=this;return result;} dp.sh.Highlighter.prototype.GetMatches=function(regex,css) {var index=0;var match=null;while((match=regex.exec(this.code))!=null) this.matches[this.matches.length]=new dp.sh.Match(match[0],match.index,css);} dp.sh.Highlighter.prototype.AddBit=function(str,css) {if(str==null||str.length==0) return;var span=this.CreateElement('SPAN');str=str.replace(/ /g,' ');str=str.replace(/');if(css!=null) {if((/br/gi).test(str)) {var lines=str.split(' 
');for(var i=0;ic.index)&&(match.index/gi,'\n');var lines=html.split('\n');if(this.addControls==true) this.bar.appendChild(dp.sh.Toolbar.Create(this));if(this.showColumns) {var div=this.CreateElement('div');var columns=this.CreateElement('div');var showEvery=10;var i=1;while(i<=150) {if(i%showEvery==0) {div.innerHTML+=i;i+=(i+'').length;} else {div.innerHTML+='·';i++;}} columns.className='columns';columns.appendChild(div);this.bar.appendChild(columns);} for(var i=0,lineIndex=this.firstLine;i0;i++) {if(Trim(lines[i]).length==0) continue;var matches=regex.exec(lines[i]);if(matches!=null&&matches.length>0) min=Math.min(matches[0].length,min);} if(min>0) for(var i=0;i