読者です 読者をやめる 読者になる 読者になる

AndroidStudio 1.5 でNDKを認識させるメモ

android gradle ndk AS

仕事でndk使うことになって、そういえば
ndk plugin 0.4.X系も出てたよな・・・ と思いつつ調べてみた

>結論から言うと

でしかビルドが出来なかった・・。

>原因としては

  • google様推奨のマルチプロジェクト形式ではない
    • ASのデバッガ/logcatはまだ発展途上なので使えない *1 *2
  • 公式サイトのDSL記述が未だに 0.1.Xのまま。。
  • G社様の基本方針が
    • DSLAndroid.mkを作りなおす方針が変わっていない*3



方式1)gradle gradle plugin NDKレガシー対応ベース*4

  • gradle.properties
android.useDeprecatedNdk=true //◎

1.3以降で無効にされているandroid gradle pluginの
ndkの機能を有効化する

>jni.srcDirs = ['jni']を有効にした時点で
◎を指定しないとチェックタスクでエラーになるんだよな。。

  • build.gradle
android {
    sourceSets {
        main {
            jni.srcDirs = ['jni'] //☆
            jniLibs.srcDirs = ['libs'] 
        }
   }
   buildTypes {
        debug {
            debuggable true
        }
        jniDebug {
            initWith(buildTypes.debug)
            jniDebuggable true
        }
   }
}

と記述する方法だが、内部的にはndk-buildを呼んではいるのですが

  • 中途はんぱにDSLを適応しようとしていて
    • DSL定義を元にAndroid.mk/Application.mkを新規で作りなおし
    • 同梱されている既存のAndroid.mk等の存在を無視
    • したがって簡単なもの以外ndk-buildが通らない。。

みたいな謎な現象になっていたりしてたり。。(苦笑

何で素直に

  • ndk-buildを実行+実行済タイムスタンプ記録

するだけにしてくれなかったのか...
と前からツッコミ入れたいと思ってましたね。。



解決した方法:方式2)

上記のURL記事と方式1)をあわせる技

因みに

にも書いてあるけど

  • local.properties に下記のように書くと
sdk.dir=XXX
ndk.dir=XXX
android.sdkDirectory
android.ndkDirectory

で参照可能になるので、パスの決め打ち自体はじつはいらない。

  • build.gradle
apply from:'build_ndk_ext.gradle'

android {
    sourceSets {
        main {
            //ADTで解釈できるプロジェクト形式
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']

            jni.srcDirs = ['jni'] //☆
            //jni.srcDirs = [''] //== 普通改変推奨される箇所==
            jniLibs.srcDirs = ['libs'] //☆☆
        }
   }
}

現状だと
ndk buildだけ通す観点からだと

  • 独自ndk-buildタスクを書く
  • jni.srcDirs = [''] にしろと要求される
    • //== 普通改変推奨される箇所== の箇所

となるわけですが、これだと

したがって
jniフォルダを有効にしつつ、本来のndkレガシータスクを無効化する形に修正する

  • build_ndk_ext.gradle
//ndk レガシータスクを無効にする
tasks.whenTaskAdded { task ->
    //組み込まれているNdkタスクを無効にする
    if (task.name.indexOf("Ndk")!=-1) {
        task.enabled = false
    }
   
    //assembleXXX ビルドをコマンドラインで指定しているときはデフォルトのテスト実行を無効にする(ビルドの高速化)
    def cmd_task = project.gradle.startParameter.taskNames[0]
    if (cmd_task !=null && cmd_task.indexOf("assemble")!=-1) {
        if (task.name.indexOf("test")!=-1) {
            task.enabled = false
        } 
    }

    //assemble x ProductFlavor が非表示になっているので表示させる
    //   see  http://mrhaki.blogspot.jp/2012/06/gradle-goodness-adding-tasks-to.html
    if (task.name.indexOf("assemble")!=-1) {
        task.group = 'build'
    }
}

ext {
    ndkHome = android.ndkDirectory ==null ? "":android.ndkDirectory.getAbsolutePath()
    cpuNum = Runtime.getRuntime().availableProcessors()
}

//debugの時のみNDK_DEBUG=1をつける
tasks.withType(JavaCompile) {task-> 
     if(!new File('jni').exists())return;
     if(task.name.indexOf("Debug")!=-1){
         task.dependsOn ndkBuildDebug
     }
     else{
         task.dependsOn ndkBuildRelease
     }
}

if(new File('jni').exists()){
    clean.dependsOn 'ndkClean'
}

import org.apache.tools.ant.taskdefs.condition.Os
task ndkBuildDebug(type: Exec) {
    println "=== exec ndk-build ==="
     //soファイルが1個でも見つかったらndk-buildをUptodate扱いにしてSkipする
     if (Os.isFamily(Os.FAMILY_WINDOWS)) {
             commandLine "${ndkHome}/ndk-build.cmd","NDK_DEBUG=1","-j${cpuNum}"
     }else{
             commandLine "${ndkHome}/ndk-build","NDK_DEBUG=1","-j${cpuNum}"
     }
}

task ndkBuildRelease(type: Exec) {
    println "=== exec ndk-build release==="
   if (Os.isFamily(Os.FAMILY_WINDOWS)) {
       commandLine "${ndkHome}/ndk-build.cmd","-j${cpuNum}"
   }else{
       commandLine "${ndkHome}/ndk-build","-j${cpuNum}"
   }
}
 
task ndkClean(type: Exec) {
    println "=== exec ndk-build clean ==="
   if (Os.isFamily(Os.FAMILY_WINDOWS)) {
      commandLine "${ndkHome}/ndk-build.cmd", 'clean'
   }else{
      commandLine "${ndkHome}/ndk-build", 'clean'
   }
}

これでADTと同じような感じになるわけですが、
毎回ndk-build走るのはADTが遅い言われる原因の一つなので
ちょっと微妙な気がするわけでは有りますね。。

公式pluginを使うメリットって

  • 実行済みタスクを.gradle/XXXX.bin としてタイムスタンプ記憶してSkipしてくれる
    • 毎回ビルドが発生しないので時間短縮

なところがあるわけで、これを自前でやろうとすると実は大変(苦笑

でもup to-date を出す仕組み自体は興味があるから
後々再度調べてみたいと思う *5

12/24記)

  • UP-TODATEは下記のように書けば良いとのこと。
  • build_ndk_ext.gradle
//gradleで関数を使うには
//def XXX => ext.XXX に変更しないと使えない
// see http://stackoverflow.com/questions/27777591/how-to-define-and-call-custom-methods-in-build-gradle
//

ext.findFirstFile = {rootDir,filter ->
	println "rootDir=$rootDir"
	println "filter=$filter"
	if(rootDir==null || !rootDir.exists()){
		return null
	}
	File result
	rootDir.traverse(
		type : groovy.io.FileType.FILES,
		nameFilter : filter
	) { it -> 
		//println it
		result = it
		groovy.io.FileVisitResult.TERMINATE
	}
	result
}

task ndkBuildDebug(type: Exec) {
	println "=== exec ndk-build debug ==="
	//TODO: onlyIf + println('UP-TO-DATE')
	outputs.upToDateWhen {
		//new File('libs/armeabi-v7a').exists()
		findFirstFile(new File(projectDir,'libs'), ~/.*\.so/)!=null
	}

	if (Os.isFamily(Os.FAMILY_WINDOWS)) {
		commandLine "${ndkHome}/ndk-build.cmd",'NDK_DEBUG=1',"-j${cpuNum}"
	}
	else{
		commandLine "${ndkHome}/ndk-build",'NDK_DEBUG=1',"-j${cpuNum}"
	}
}

これは

との合わせ技ですね。

  • outputs.upToDateWhenの箇所は doLast{ }と組み合わせているサンプル多
    • 条件を満たさない時に「空タスクはNGだよ」とエラーでてはまってた。><



現在最新の方法:方式3)

の記載のものですが、

AS最新版のviewを見てると、右下にAndroid Modelというタブが追加されていて
現在のandroid gradle plugin をこの新しいNDK対応版に作りなおして置き換えようとする意図が見えます。

ただこの android gradle-experimental plugin のアホっぽい糞面倒な所は

  1. 実行gradlewバージョンを限定して、バージョンが違うとエラーにするcheck taskを必須で組んでる
    1. しかも0.1バージョン変更するごとに必要gradleが違うので試すのすら億劫
  2. sourceSetの所がまだ作りこまれていないので、ADTタイプのプロジェクトだとまずビルドが出来ない
  3. 0.4だと0.3とかでよくサンプルで記載されているapiFilter辺りがreadOnlyだといわれてエラーになる
    1. DSLのページ公開して欲しいなーと切に思う。
  4. 本流のandroid gradle pluginの状況を取り込めていないので 通常のjavaレイヤー *6 でエラーでよく引っかかったりする

なスライドも一応公開されているらしい

正直最初は別gradle運用でいんじゃね? とか思ってたのにな・・。
変にandroid pluginと同じようなチェック処理が入っててビルド2回走らせるんかい!
みたいな感覚になる

  • 1個前(現在版?) :収拾がつかなくて放り投げた
  • 最新版:最初から考えなおして作りなおしてみる

みたいな感じですので、これも行き詰まったら
多分bazel にgoogle様も移行するんだろうな・・とか思ってたりする。

だってGAEだって今はjavaじゃなくてGo開発が主流らしいですからね。。
メーリングリスト見てると Gaelyk はまだ使ってるみたいなのは流れてきたりはしてるのですが。。。

*1:コード書くのはASの方がだいぶ慣れたが。。

*2:でもE/A ショートカット対応表ないと作業できんね。。<苦笑

*3:既存の同梱されているAndroid.mkを指定できない。。。

*4:gradle plugin 1.3.X以上が必要

*5:以前も調べていた気がするけど中途半端な対応になっていたと記憶

*6:ライブラリの依存関係絡みとか