2014年10月7日火曜日

EclipseのプロジェクトをGradleでビルドし、JenkinsでもRobolectricを動くようにする

このエントリーをはてなブックマークに追加 はてなブックマーク - EclipseのプロジェクトをGradleでビルドし、JenkinsでもRobolectricを動くようにする

最近のAndroid開発環境はAndroidStudioがデファクトスタンダードっぽいのですが、関わっているプロジェクトがNDKを利用しており、まだAndroidStudioでは未対応なので、Eclipseを使っております。

そんなプロジェクトなのですが、Robolectricを使ってテストしており、Jenkinsで実行させる為にコマンドラインで実行するまでにした事をメモしておきます。

どうやったか

Antでやる方法もあると思うのですが、Gradleだとそれ用のプラグインがあるのでGradleを使いました。

Github

とりあえず使える状態のサンプルをGithubにおいてあります。

RobolectricGradleSample

READMEに書いてあるような状態であれば以下で実行出来るかと思います。

$gradle clean test

EclipseでRobolectricを実行させる

以下の公式サイトに丁寧に記載があるのでこちらを参照。

Eclipse Quick Start

Gradleのビルドで必要なファイルを作る

Eclipseから簡単にGradleでビルドする時に必要なファイルを出力する事が出来るので、利用します。Eclipseを起動し、以下で完了です。

File→Export→Android→Generate Gradle build files

完了すると色々ファイルが出来ていると思うので、バージョン管理に登録しておきます。

サーバー環境でAndroidをGradleでビルドする

Androidをビルド出来る環境を作る

AndroidSDKをサーバーにインストールします。自分の場合、Linuxだったので対応するSDKをダウンロードして、後は自分のビルドしたいAndoridバージョンのSDKをインストールしておきます。

この時、 AndroidSDK Build-toolsとAndroid Support Repositoryもインストールしておいてください。

AndoridSDK Build-toolsはGradleのビルド時にバージョンの指定が必要なので、インストールしたバージョンも確認しておいて下さい。

なお、CUIでAndoridSDKなどのインストール方法はこちらを参照。

特定のAndroid SDKをCUIでインストールする方法

GradleをGVMを使ってインストールする

サーバーにGradleをインストールしますが、普通にインストールするとGradleのバージョン切り替えが面倒なのでGVMを使います。

GVM

インストールします。

$curl -s get.gvmtool.net | bash

現在、利用可能なGradleのバージョンを確認します。

$gvm list gradle

1.12をインストールして利用します。(Androidをビルドするためのプラグインが1.1x系を要求するため)

$gvm install gradle 1.12
$gvm use gradle 1.12

環境変数ANDROID_HOMEを設定する

GradleのAndroidプラグインにて環境変数のANDROID_HOMEを指定する必要があるので、設定します。

$vi ~/.bashrc

追加します。

# Andorid_HOME
$export ANDROID_HOME=/Applications/adt-bundle-mac-x86_64-20140624/sdk/

適用させます。

$source ~/.bashrc

Jenkinsで実行する場合、Jenkinsの環境変数を設定する箇所で上記を設定してください。

build.gradleを編集する

Eclipseでプロジェクトを新規作成するとappcompat_v7とかもコンパイルしなければいけなかったり、lib配下にandroid-support-v4.jarとかが存在します。

Eclipseでビルドする時は良いのですが、サーバーでビルドする時に上記も用意したりするのは面倒なので、Gradleを使って依存を解決します。

という事で作成されたbuild.gradleを一部編集します。 自分の場合、以下のようにしました。

// Gradle自身の環境を設定
buildscript {

    // mavenCentralリポジトリを利用する
    repositories {
        mavenCentral()
    }

    // 利用するGradleプラグインは0.13を利用
    dependencies {
        classpath 'com.android.tools.build:gradle:0.13.0'
    }
}

// すべてでmavenCentralのリポジトリを追加
allprojects {
    repositories {
        mavenCentral()
    }
}

apply plugin: 'android'

dependencies {

    compile fileTree(dir: 'libs', include: '*.jar')

    // 削除。下記のように記載することでGradleより取得
    //compile project(':appcompat_v7')

    // 追加。Gradleによってappcompatの依存関係を解決。
    // excludeの記述によってlib配下のsupport-v4との競合を避ける。
    compile ('com.android.support:appcompat-v7:18.0+') {
        exclude module: 'support-v4'
    }
}

android {
    compileSdkVersion 20
    buildToolsVersion "20.0.0"

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        // Move the tests to tests/java, tests/res, etc...
        instrumentTest.setRoot('tests')

        // Move the build types to build-types/<type>
        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
        // This moves them out of them default location under src/<type>/... which would
        // conflict with src/ being used by the main source set.
        // Adding new build types or product flavors should be accompanied
        // by a similar customization.
        debug.setRoot('build-types/debug')
        release.setRoot('build-types/release')
    }
}

変更点は以下の通りです。

  • buildscriptを追加(2~13行目)
  • allprojectsを追加(16~20行目)
  • dependenciesの一部を変更(24~36行目)

buildscriptと記述されている場所はGradle自身の依存関係や利用するプラグインの情報を指定します。 今回の指定では以下2点を指定しています。

  1. buildscriptでの依存解決ではmavenCentralリポジトリを利用利用する
  2. com.android.tools.build:gradleの0.13を利用する

allprojectsと記述されている場所では以下を指定しています。

  1. 依存解決ではmavenCentralリポジトリを利用する(buildscript)

depnedenciesでは以下を指定、変更しました。

  1. appcompat_v7プロジェクトのビルドをする、という命令の箇所を削除
  2. コンパイル時に’com.android.support:appcompat-v7:18.0’以上が必要である事を記述
  3. appcompat-v7の依存を解決する時にsupport-v4に関する依存は解決しない

1点目のappcompatについてですが、Eclipseだとプロジェクトを追加した時にappcompat_v7も一緒に作成されて、ビルドが必要となります。ただし、サーバーでは面倒なので、この役目はGradleに任せる事としてコメントアウトしています。

2点目は1点目でコメントアウトしたappcompatについてGradleで依存解決する為の記述です。コンパイルする時にこのプロジェクトはappcompat-v7:18以上に依存しているよという事を記述しています。

3点目ではsupport-v4に関しての依存部分は解決しなくてよい、という事を指定しています。Gradleで依存解決を行う時は対象のものが依存しているものも一緒に依存解決しようとします。 試しにexcludeを消して、gradle dependenciesというコマンドをタイプして各ライブラリの依存関係を表示します。

$gradle dependencies 
・・・・
_debugCompile - ## Internal use, do not manually configure ##
\--- com.android.support:appcompat-v7:18.0+ -> 18.0.0
     \--- com.android.support:support-v4:18.0.0

上記のようにappcompat-v7:18はsupport-v4に依存している事が分かります。

依存解決は通常ありがたいことなのですが、今回作成したプロジェクトではlibsフォルダ配下に既にsupport-v4が存在しています。そのため、excludeしないとビルドエラーとなってしまいます。(Multiple dex files define Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompatとか出ます。)

libs配下のsupport-v4を削除するとEclipseのビルドができなくなってしまいますので、今回は上記のように書いてありますが、Gradleでのビルド前提の場合にはGradleで依存解決は解決する方が妥当だと思います。

AndoridをGradleでビルドする

まず、自分がビルドしたいAndoridプロジェクトをサーバーに任意の場所にcloneします。

そして、build.gradleがある場所(対象のAndroidプロジェクトのRoot)に移動します。

そこで、build.gradleを開き、buildToolsVersionがサーバーでインストールしたバージョンと合っているか確認して下さい。もし、違う場合にはエラーとなるので、書き換えてください。

ANDROID_BUILD_TOOLSというような変数にしておいて、gradle.propertiesなどに設定しておく方法がおすすめです。

さて、ビルドしてみます。

$gradle build 

もし、特にエラーがなければ、build/outputs/apk配下にapkが作成されているはずです。

build.gradldを編集する2

buidl.gradleを編集してRobolectricでテスト出来るようにします。

各変更点は後述しますが、全体で以下のようになりました。

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:0.13.0'
        classpath 'org.robolectric:robolectric-gradle-plugin:0.13.0'
    }
}

allprojects {
    repositories {
        mavenCentral()
    }
}

apply plugin: 'android'
apply plugin: 'robolectric'

android {
    compileSdkVersion 17
    buildToolsVersion "20.0.0"

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }
        androidTest {
            setRoot('test')
        }

        // Move the tests to tests/java, tests/res, etc...
        //instrumentTest.setRoot('tests')

        // Move the build types to build-types/<type>
        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
        // This moves them out of them default location under src/<type>/... which would
        // conflict with src/ being used by the main source set.
        // Adding new build types or product flavors should be accompanied
        // by a similar customization.
        debug.setRoot('build-types/debug')
        //release.setRoot('build-types/release')
    }
}

robolectric {
    include '**/*Test.class'
}

dependencies {

    compile fileTree(dir: 'libs', include: '*.jar')
    compile ('com.android.support:appcompat-v7:18.0+') {
      exclude module: 'support-v4'
    }

    // test
    androidTestCompile('junit:junit:4.11') 
    androidTestCompile('org.robolectric:robolectric:2.3')
}

変更点は以下の通りです。

  • 依存するプラグインとしてrobolectricを追加(8,19行目)
  • テストフォルダの位置を指定(35~37行目)
  • robolectricで実行するテストクラスを指定(53~55行目)
  • テストのコンパイル時に利用するライブラリを記述(65,66行目)

自分がこの中でも特にはまったのがテストフォルダの位置を指定する箇所です。

これについてなのですが、robolectric-gradle-plugin:0.12.0までは本設定をしてもまったく有効化せず、必ずsrc/test/java配下にファイルを配置しないといけませんでした。

ただ、0.13からは任意のディレクトリの指定が可能となりました。ただし、必ず指定したディレクトリ配下にjavaというフォルダがある必要はありますので、注意が必要です。

GradleからRobolectricを実行する

早速やってみます。

$gradle clean test

gradle1.12では下記エラーになってしまいます。。。

* What went wrong:
A problem occurred evaluating root project 'RobolectricGradleSample'.
> Could not create plugin of type 'AppPlugin'.

ただし、Gradle2.1ではうまくできます。

$gvm install gradle 2.1
$gvm use gradle 2.1
$gradle clean test

こんな感じで結果がbuild/test-report/index.htmlに出力されています。

また、build/test-result/にXMLも出力されるのでこれを利用してJenkinsで実行結果のグラフ表示も可能です。

RobolectricのPluginが1.x系に対応してないからなのでしょうか。。。ちょっといまいち理由が分かっていません。知っている人がいらっしゃったら教えて頂けるとありがたいです。

2014年7月2日水曜日

[Android]CLIからインテントの送信、ブロードキャストの送信を行ってみた

このエントリーをはてなブックマークに追加 はてなブックマーク - [Android]CLIからインテントの送信、ブロードキャストの送信を行ってみた

Androidのアプリで違うアプリから自分のアプリがkickされることって良くあると思います。

で、その時の動作を確認する為に、スタブアプリを作ってーみたいな事を今までやっていたのですが、実はCLIでインテントの送信やブロードキャストの送信も出来る事が分かり、とても便利なのでそのメモ。

参考

ターミナルからIntentを投げる

Broadcastをエミュレートする

前提

  • テストするアプリのパッケージ名はcom.example.toshihirock.myapplicationとしています。適宜自分の環境に置き換えて読んでください。
  • MyActivityというActivityが存在するものとします。
  • adb devicesで端末が認識出来る状態にしておきます。

明示的インテント

$adb shell am start -n 'com.example.toshihirock.myapplication/.MyActivity'

com.example.toshihirock.myapplicationというパッケージ名のMyActivityを起動させます。

アプリ終了

$adb shell am force-stop com.example.toshihirock.myapplication

パッケージ名を指定します。

暗黙的インテント

$adb shell am start -a 'android.intent.action.VIEW' -d 'http://google.co.jp'

-dではDATA_URIを指定しています。

ブロードキャストの送信

今回はom.example.toshihirock.testというアクションでtestという文字列の変数を送る事を確認してみます。

以下のようなクラスがあったとしてます。

MyBroadCastReceiver.java

package com.example.toshihirock.myapplication;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

/**
 * Created by toshihirock on 7/2/14.
 */
public class MyBroadCastReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        String str = intent.getExtras().getString("test");
        Log.i("tag",str);
    }

}

また、AndroidManifest.xmlでreceiverに上記クラスを設定します。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.toshihirock.myapplication" >

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MyActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name="MyBroadCastReceiver">
            <intent-filter>
                <action android:name="com.example.toshihirock.test"></action>
            </intent-filter>
        </receiver>
    </application>

</manifest>

上記作成後、以下のコマンドで該当のクラスへのBroadcastが確認出来ます。

$adb shell am broadcast -a 'com.example.toshihirock.test' -e 'test' 'aaaa'

Logcatには以下のように出力されるはずです。

07-01 11:40:37.621    3144-3144/com.example.toshihirock.myapplication I/tag﹕ aaaa

なお、文字列に"などを指定したい場合には、以下のようにエスケープする事で正しく内容が送信出来ます。

$adb shell am broadcast -a 'com.example.toshihirock.test' -e 'test' '\"aaaa\"'

Logcatで確認出来ます。

07-01 12:02:30.007    3144-3144/com.example.toshihirock.myapplication I/tag﹕ "aaaa"

その他

以下のコマンドでヘルプが見れるので詳細はこちらを見ると良いかと思います。

$adb shell am

他にも色々出来るっぽいです。

2014年6月27日金曜日

[AWS]EC2のロードバランサを設定した時のメモ

このエントリーをはてなブックマークに追加 はてなブックマーク - [AWS]EC2のロードバランサを設定した時のメモ

ドットインストールのAmazon Web Services入門を参考にロードバランサの設定をした時のメモ

EC2のインスタンスの準備

  • EC2のインスタンスを作成する
  • 適用させるSecurity Groupsで22と80ポートを解放する。
  • sshログインしてnginxなどのWebサーバーを起動させる
  • Webサイトにアクセス出来る事を確認

AMI(Amazon Machine Image)の作成

  • 作成したインスタンスを複製するのためにAMIを作成
  • 作成までは少し時間が必要
  • sshログイン、Webサイトにアクセス出来る事を確認

ロードバランサの作成

  • 作成時に80ポートを転送するように設定
  • 作成した二つのインスタンスを適用
  • 作成後表示されるDNS NameのURLにアクセス(自分の場合、作成後すぐにアクセスするとエラーとなってしまった。数分後、アクセスすると問題なくサイトが表示された)
  • アクセスログを確認し、それぞれのインスタンスにアクセスが出来ていることを確認

2014年6月25日水曜日

ADT(Android Developer Tools)の更新がNo repository found containingとかで出来ない時の解決法

このエントリーをはてなブックマークに追加 はてなブックマーク - ADT(Android Developer Tools)の更新がNo repository found containingとかで出来ない時の解決法

ADT(Android Developer Tools)の更新がうまく出来ない事があって、その時の対応メモ。

AndroidSDK自体の更新は行ったが、ADTの更新がされていない場合、以下のようなダイアログがEclipse起動の際に表示された。

でもChek for updateをクリックしてもNo updates were foundとか言われます。

手動で更新しようとしてHelp→Install New Software→Android Developer Tools Update Site - http://dl-ssl.google.com/android/eclipse/とか選択して次へ次へとやっていくも最終的にNo repository found containingとかでエラーとなる。

An error occurred while collecting items to be installed
session context was:(profile=profile, phase=org.eclipse.equinox.internal.p2.engine.phases.Collect, operand=, action=).
No repository found containing: osgi.bundle,com.android.ide.eclipse.adt,22.6.3.v201404151837-1123206
No repository found containing: osgi.bundle,com.android.ide.eclipse.adt.package,22.6.3.v201404151837-1123206
No repository found containing: osgi.bundle,com.android.ide.eclipse.base,22.6.3.v201404151837-1123206
No repository found containing: osgi.bundle,com.android.ide.eclipse.ddms,22.6.3.v201404151837-1123206
No repository found containing: osgi.bundle,com.android.ide.eclipse.gldebugger,22.6.3.v201404151837-1123206
No repository found containing: osgi.bundle,com.android.ide.eclipse.hierarchyviewer,22.6.3.v201404151837-1123206
No repository found containing: osgi.bundle,com.android.ide.eclipse.ndk,22.6.3.v201404151837-1123206
No repository found containing: osgi.bundle,com.android.ide.eclipse.traceview,22.6.3.v201404151837-1123206
No repository found containing: osgi.bundle,overlay.com.android.ide.eclipse.adt.overlay,22.6.3.v201404151837-1123206
No repository found containing: org.eclipse.update.feature,com.android.ide.eclipse.adt,22.6.3.v201404151837-1123206
No repository found containing: org.eclipse.update.feature,com.android.ide.eclipse.ddms,22.6.3.v201404151837-1123206
No repository found containing: org.eclipse.update.feature,com.android.ide.eclipse.gldebugger,22.6.3.v201404151837-1123206
No repository found containing: org.eclipse.update.feature,com.android.ide.eclipse.hierarchyviewer,22.6.3.v201404151837-1123206
No repository found containing: org.eclipse.update.feature,com.android.ide.eclipse.ndk,22.6.3.v201404151837-1123206
No repository found containing: org.eclipse.update.feature,com.android.ide.eclipse.traceview,22.6.3.v201404151837-1123206

ここでググってみたら、以下のような同じ事象の記載が。

No repository found error in Installing ADT in eclipse Indigo

ここに記載されているようにHelp→Install New Softwareの時の画面下部のContact all update sites during install to find required softwareのチェックを外したらうまくできました。

ちょっとはまった。。

2014年6月24日火曜日

[AWS]EC2及びローカル環境からRDSのMySQLに接続した時のメモ

このエントリーをはてなブックマークに追加 はてなブックマーク - [AWS]EC2及びローカル環境からRDSのMySQLに接続した時のメモ

EC2及びローカル環境からRDSのMySQLに接続した時のメモ なお、予めEC2は起動済みでSSH接続出来る事が前提。

EC2からRDSへの接続

RDSを以下の内容で作成する。

  • Public AccessibleはNOにする
  • EC2と同じSecurityGroupを設定する。

RDS作成後、以下の情報をメモしておく。

  1. エンドポイント(例:dbtest.hogefuga.ap-northeast–1.rds.amazonaws.comなど)
  2. ポート(例:3306)
  3. DBユーザー名(例:dbuser)
  4. DBパスワード

次に設定したSecurityGroupのInboundを確認する。 もし、DBのポート(上記の場合3306)の設定がされていない場合、以下のように同じSecurityGroup間の3306ポートの接続は可能なように設定する。

設定後、EC2にSSH接続し、以下のようなコマンドを実行。

$mysql -h dbtest.hogefuga.ap-northeast-1.rds.amazonaws.com -P 3306 -u dbuser -p

パスワードが聞かれるので入力すればRDSのmysqlへの接続が出来る。

ローカルマシンからRDSへの接続

  • Public AccessibleはYESにする
  • SecurityGroupの設定でRDSに割り当てているもののInboundにアクセス元のローカルのグローバルIPとDBのポートによる接続許可のルールを追加する。(自分は確認の為にアクセス元を0.0.0.0/0にしましたが、セキュリティ的に通常設定すべきではありません)

ローカルマシンからEC2と同様にmysqlコマンドで接続が可能である。

2014年6月10日火曜日

svnコマンドで証明書を無視する

このエントリーをはてなブックマークに追加 はてなブックマーク - svnコマンドで証明書を無視する

svn exportとかした時にこの証明書信用していいんですか、みたいなのが出て落とせない。

(R)eject, accept (t)emporarily or accept (p)ermanently? 

みたいな感じで。

普通にコマンドを打っている場合にはキーボードから入力すれば良い訳ですが、Chefとか使っている場合だと自動化したいのでどうしようかな、と思ったのですが、オプションを指定したら無視出来ました。

svn  checkout https://hogefuga --non-interactive --trust-server-cert --username "hoge" --password "fuga"

expectコマンドとかyesコマンドとか使うのも手かもしれませんが、こちらの方が楽。

[Chef]expectコマンドを使ってAndroidSDKをインストールするレシピを作成してみた

このエントリーをはてなブックマークに追加 はてなブックマーク - [Chef]expectコマンドを使ってAndroidSDKをインストールするレシピを作成してみた

Chefを利用してAndroidSDK本体のインストール及び特定AndroidSDKインストールを自動化させたいと思っていました。

が、指定したAndroidSDKのインストールの途中で利用許諾の承認が必要であり、単純にexecuteコマンドを利用してインストールが出来ませんでした。

調査するとexpectコマンドを使うと対話形式の入力が必要な処理を自動化出来るとのことで実際に実施する事が出来たいのでそのメモを記載します。

そもそもexpectコマンドとは?

  • telnetやsshなどパスワード入力を求められるような処理を自動化出来る
  • 同様にインストール時に許諾への同意などが必要なものも自動化させる事が出来る

前提

  • Chef、Berkshelfなどはインストール済み
  • ホストはMac OSX 10.9.3
  • test-kitchenを利用。ドライバはvagrantを利用。

Chefやtest-kitchenの詳細は過去のブログを参考の事。

Berkshelf version3+Knife soloでnginx環境をVagrantに作ってみた話

[Chef]Berkshelfを利用したJenkinsクックブックを作成してserverspec+Kitchenでテストした話

参考

こちらのクックブックを参考にさせて頂きました。

gildegoma/chef-android-sdk

Github

置きました。

toshihirock/AndroidSDK

  • Ubuntuのテストはしてません
  • URLやAndroidSDKのバージョンは適宜変更してください。(そのうちattribute化したい。。。)

準備

クックブック作成します。

$knife cookbook create android -o .

AndroidSDKを利用する為にJavaのインストールが必要ですが、サードパーティのクックブックを利用 します。 また、AndroidSDKのダウンロード、展開の為にopscodeのarkというクックブックを利用します。

サードパーティのクックブックを使う為にBerksheflを利用する準備をします。

$berks init .

利用するためにmetadata.rbに追記を行います。

$cd android
$vi metadata.rb

name             'android'
maintainer       'YOUR_COMPANY_NAME'
maintainer_email 'YOUR_EMAIL'
license          'All rights reserved'
description      'Installs/Configures android'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version          '0.1.0'

depends "ark"
depends "java"

また、test-kitchenを使ってプロビジョニングを行うのでその為のファイルを作成します。

$kitchen init

既にBerkshlefで.kitchen.ymlが作成済みの場合にはコンフリクトしますが、yを入力して鵣川書きします。

今回、試験ははCentOSのみに今回はするため、.kitchen.ymlの一部をコメントアウトします。

$vi .kitchen.yml

編集。

platforms:
  #- name: ubuntu-12.04
  - name: centos-6.4

AndroidSDK本体のダウンロード、展開

まずはAndroidSDK本体のダウンロード、展開までのレシピを書きます。 本来はURLやパスはattributesの値とすべきですが、とりあえずベタ書きします。

$vi recipes/default.rb

編集。

include_recipe 'java'

%w{unzip expect}.each do |pkg|
  package pkg do
    action :install
  end
end

ark 'android' do
  url 'http://dl.google.com/android/adt/22.6.2/adt-bundle-linux-x86_64-20140321.zip'
  path '/usr/local/android'
end

URLはAndroidのサイトをみて確認してください。(ビット数に応じてダウンロードするzipが違いますが、今回は判定処理は割愛して64bitで確認)

試験環境の準備

VMの起動。

$kitchen create

VMにChefやBerkshelfのインストール、及びレシピの適用を行います。

$kitchen setup

状態を確認します。

$kitchne list

SSH接続して確認してみます。

$kitchen login
$ll /usr/local
lrwxrwxrwx. 1 root root   20 Jun  9 14:30 android -> /usr/local/android-1

expectコマンドを使ってみる

Chefのコードを書く前に正しく動作出来るかVMでコマンドを実行して確認してみます。

$kitchen login
$sudo su
$cd /usr/local/android/sdk/tools
$vi expectTest.sh

確認用のシェルスクリプトを編集します。

#!/bin/bash

expect -c '

spawn /usr/local/android/sdk/tools/android update sdk --no-ui --filter android-17
set timeout 1800
expect {
  -regexp "Do you accept the license.*" {
    exp_send "y\r"
    exp_continue
  }
}
'

ざっくり説明します。

  • spawn:実際に実行させたいコマンド
  • set timeout:expectの実行を許容する時間。短すぎるとコマンドの途中で強制終了してしまう。
  • regrexp:どの文字列が表示されたら入力を自動的に行うかの正規表現。今回の場合、利用規約のDo you accept …という文字列が表示された時に入力を行う事を示す。
  • exp_send:regrexpで合致する文字列が表示された時に自動入力する文字列。
  • exp_continue:これを指定しないと自動入力したあと復帰出来ないっぽいです。

実行権限を付与して、実行してみます。

$chmod a+x expectTest.sh
$./expectTest.sh

Chefのレシピでexpectコマンドを実行する

成功すればこれをChefのレシピに落とし込みます。

$exit
$vi recipes/default.rb

レシピに追記します。

script 'Install Android SDK' do
  android_version="android-17"
  interpreter 'expect'
  not_if { ::File.exists?("/usr/local/android/sdk/#{android_version}") }
  code <<-EOF
    spawn /usr/local/android/sdk/tools/android update sdk --no-ui --filter #{android_version}
    set timeout 1800
    expect {
      -regexp "Do you accept the license.*" {
        exp_send "y\r"
        exp_continue
      }
    }
  EOF
end

テストも書きます。今回はserverspecを利用します。 Gemfileに追加します。

$echo "gem \'serverspec\'" >> Gemfile

$mkdir -p test/integration/default/serverspec/localhost/
$vi test/integration/default/serverspec/localhost/default_spec.rb

テストを書きます。

require 'serverspec'
include Serverspec::Helper::Exec
include Serverspec::Helper::DetectOS

describe command('which java') do
  it { should return_exit_status 0 }
end

describe command('ls /usr/local/android/sdk/platforms/android-17') do
  it { should return_exit_status 0 }
end

色々実験したので一度、VM環境を削除します。

$kitchen destroy

その後、VMの作成、Chefの適用、serverspecのテストを実施します。

$kitchen test

問題なく完了すればOKです。