CordovaでversionCodeやCFBundleVersionは何になる？


最近、[Ionic](http://ionicframework.com/)でCordova始めました。
いろいろと触ってみてますが、小綺麗なCSSフレームワークが用意されていると、細かい調整をしなくてもなんとなくそれっぽくなるので、結構好きです。

本記事では、バージョン命名をCordovaで運用するときに直感的ではない動きをするところがあったので、調べて分かったことを備忘録がてら紹介します。

#### 読むの面倒くさい人向けのまとめ

* config.xmlのversionに[semver](http://semver.org/)形式のバージョンを指定できる
* versionCodeやCFBundleVersionはバージョンから自動生成される
* versionCodeを自動生成に任せると、1のケタに不思議な数字が入るので気をつけろ
* 自分で指定するオプションもあるので自動生成が気に食わない場合はそちらを使おう

confix.xmlのversionは何を表す
----------

少し話がそれましたが、モバイルアプリケーションでもバージョンを表すパラメータがあります。以下の記事が詳しいです。

* [AndroidとiOSのアプリバージョン周りについてまとめてみた - Qiita](http://qiita.com/tarappo/items/6d20d2f4bc03135ed9c7)

Cordovaのconfig.xmlにも、このバージョンを記述するための場所があります。

```xml:config.xml
<widget
   id="com.example.my-app"
   version="0.1.0"
   xmlns="http://www.w3.org/ns/widgets"
   xmlns:cdv="http://cordova.apache.org/ns/1.0">
```

このversionに指定したものが、AndroidのversionNameとiOSのCFBundleShortVersionStringになります。

では、versionCodeやCFBundleVersionはどのように指定するのでしょうか？

一応、指定しなくても動くようになっています。一度ビルドした後の、各設定を見てみると、このようになっています。

![スクリーンショット_2015-10-20_10_51_33.png](https://qiita-image-store.s3.amazonaws.com/0/2276/7aa22e58-afc1-3f01-b55d-9f0593e19c30.png "スクリーンショット_2015-10-20_10_51_33.png")

![スクリーンショット_2015-10-20_10_38_00.png](https://qiita-image-store.s3.amazonaws.com/0/2276/47386a63-b9ca-8f34-4412-62263abacbb1.png "スクリーンショット_2015-10-20_10_38_00.png")

このへんのルールについては[公式リファレンス](http://cordova.apache.org/docs/en/5.1.1/config_ref/index.html)に記述がある通り、

* AndroidのversionCodeはversionNameが`X.Y.Z`であることを前提にして、`PATCH + MINOR * 100 + MAJOR * 10000`
* iOSのCFBundleVersionはCFBundleShortVersionStringと同じ

になるようです。

ふーん。Androidに関して言えば、よくある生成ルールじゃん。

と思っていたのですが。

versionCodeの生成規則が変
----------

最終的に採用されるであろう、BuildConfig.xmlで設定されているversionCodeを見てみましょう。

```java:platforms/android/build/generated/source/buildConfig/debug/com/example/my_app/BuildConfig.java
package com.example.my_app;

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.example.my_app";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "";
  public static final int VERSION_CODE = 1008;
  public static final String VERSION_NAME = "";
}
```

期待していた値と違いますね。`0 + 1 * 100 + 0 * 10000`、つまり`100`になることを期待していたのですが、右側に1ケタ多くなっています。

まだ慌てる時間ではありません。AndroidManifest.xmlには`100`と書いてあるのですから、きっとPlayストアに上げれば`100`として表示されるかもしれません。

![スクリーンショット_2015-10-20_11_47_35.png](https://qiita-image-store.s3.amazonaws.com/0/2276/3ac3a030-3b26-61c0-2fcc-5744c93c88f4.png "スクリーンショット_2015-10-20_11_47_35.png")

`1008`でした。どうやらこの値で決まりのようです。

何故ケタが変わったのでしょうか。何故`8`が入ったのでしょうか。

### アーキテクチャやバージョンによって変わる1の桁

一度AndroidManifest.xmlに書かれた内容を覆せるやつなんて限られています。そう、build.gradleです。[Cordovaプロジェクトが用意しているAndroidアプリのテンプレート](https://github.com/apache/cordova-android/blob/0429bb0ab8c399cf6f57289a9d5a3bc8b8cbd083/bin/templates/project/build.gradle)がそのまま取り込まれたものということで、なにかやっていないかチェックしてみましょう。

```gradle:platforms/android/build.gradle
android {
    // 略
    defaultConfig {
        versionCode cdvVersionCode ?: Integer.parseInt("" + privateHelpers.extractIntFromManifest("versionCode") + "0")
        if (cdvMinSdkVersion != null) {
            minSdkVersion cdvMinSdkVersion
        }
    }
    // 略
    if (Boolean.valueOf(cdvBuildMultipleApks)) {
        productFlavors {
            armv7 {
                versionCode cdvVersionCode ?: defaultConfig.versionCode + 2
                ndk {
                    abiFilters "armeabi-v7a", ""
                }
            }
            x86 {
                versionCode cdvVersionCode ?: defaultConfig.versionCode + 4
                ndk {
                    abiFilters "x86", ""
                }
            }
            all {
                ndk {
                    abiFilters "all", ""
                }
            }
        }
    } else if (!cdvVersionCode) {
      def minSdkVersion = cdvMinSdkVersion ?: privateHelpers.extractIntFromManifest("minSdkVersion")
      // Vary versionCode by the two most common API levels:
      // 14 is ICS, which is the lowest API level for many apps.
      // 20 is Lollipop, which is the lowest API level for the updatable system webview.
      if (minSdkVersion >= 20) {
        defaultConfig.versionCode += 9
      } else if (minSdkVersion >= 14) {
        defaultConfig.versionCode += 8
      }
    }
// 略
```

簡単に解説すると、

```
versionCode cdvVersionCode ?: Integer.parseInt("" + privateHelpers.extractIntFromManifest("versionCode") + "0")
```

↑でversionCodeを左に1ケタシフトして、そのあとのif文の中でflavorやバージョンに応じて、1ケタ目に新しい数字を放り込んでいるようです。

| 判定          | 1のケタ |
|--------------|--------|
| MultipleAPKをARMv7向けにビルド | 2 |
| MultipleAPKをx86向けにビルド | 4 |
| MultipleAPKをall向けにビルド | そのまま(0) |
| API20(Android 4.4W)以上向けにビルド | 9 |
| API14(Android 4.0)以上向けにビルド | 8 |

統一感ないな！！！　しかも、"20 is Lollipop"って普通にチョンボじゃないか・・・？

ともあれ、これで謎は解けました。

```xml:config.xml
<preference name="android-minSdkVersion" value="16"/>
```

今回扱っているプロジェクトのminSdkVersionは`16`なので、1のケタに`8`が入り、元々のルールと合わせる形で`1008`という値になったのでしょう。

どこかのドキュメントに書いといてくれないかなこれ！！！

自分で指定できる
----------

さて、自動生成されるversionCodeはちょっとアレな感じでした。こうなってくると、自分で設定したくなってくる人も多いと思います。

ということで、自分で設定する方法を紹介して、締めにしたいと思います。

```xml:config.xml
<widget
   id="com.example.my-app"
   version="0.1.0"
   android-versionCode="12345"
   ios-CFBundleVersion="25.20151124"
   xmlns="http://www.w3.org/ns/widgets"
   xmlns:cdv="http://cordova.apache.org/ns/1.0">
```

上記のような形で、android-versionCodeやios-CFBundleVersionを指定しておくと、ビルド時に考慮してもらえます。
こんな感じに。

```java:platforms/android/build/generated/source/buildConfig/debug/com/example/my_app/BuildConfig.java
package com.example.my_app;

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.example.my_app";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "";
  public static final int VERSION_CODE = 12345;
  public static final String VERSION_NAME = "";
}
```

![スクリーンショット_2015-10-20_11_19_25.png](https://qiita-image-store.s3.amazonaws.com/0/2276/2505d3c7-f07c-e5f7-2226-a6233b21bd75.png "スクリーンショット_2015-10-20_11_19_25.png")

![スクリーンショット_2015-10-20_11_19_44.png](https://qiita-image-store.s3.amazonaws.com/0/2276/b3532761-8b5d-ee57-27a8-5ce167ab899b.png "スクリーンショット_2015-10-20_11_19_44.png")

まとめ
----------

versionCodeの自動生成そのものはネイティブ開発でも[ときどき見かける](https://github.com/JakeWharton/u2020/blob/d4a620ba7cd5fc4a9c9be6da252c2b6aa0fb8a4e/build.gradle#L25-L29)[ベストプラクティス](https://github.com/JakeWharton/u2020/blob/d4a620ba7cd5fc4a9c9be6da252c2b6aa0fb8a4e/build.gradle#L71-L72)ですし、いちいち自分で決めるのも面倒なので、いいことだと思います。

ただ、一度PlayストアにリリースしてしまったversionCodeは、加算はできても減算はできない厄介なシロモノになります。自動生成に任せるのはよいですが、生成ルールに気を配って、良いリリースエンジニアリングをしましょう。

宣伝
----------

ウォーターセル株式会社では、「ネイティブアプリ開発はある程度経験したし、そろそろJS覚えてハイブリッドアプリ開発とかしてみたいなー」という挑戦的なエンジニアを募集しています。