agvtool の使い方

# はじめに

Xcode プロジェクトのバージョン番号を操作できる `agvtool` というものがあります。iOSアプリの開発では TestFlight にアップロードする際にビルド番号を毎回更新する必要があるため、このツールを直接、もしくは [fastlane の increment_build_number](https://github.com/fastlane/fastlane/blob/master/fastlane/lib/fastlane/actions/increment_build_number.rb) 経由で使用することもあると思います。

以前からこのツールの存在走っていましたが、なぜこんな特定用途のコマンドが提供されているのか気になっていました。どのような機能がるのか調べていきたいと思います。

# とりあえず help を表示

ヘルプを表示すると以下のようになりました。

```shell
$ agvtool help

agvtool - Apple-generic versioning tool for Xcode projects
  usage:
    agvtool help
    agvtool what-version | vers [-terse]
    agvtool [-noscm | -usecvs | -usesvn] next-version | bump [-all]
    agvtool [-noscm | -usecvs | -usesvn] new-version [-all] <versNum>
    agvtool [-noscm | -usecvs | -usesvn] tag [-force | -F] [-noupdatecheck | -Q] [-baseurlfortag]
    agvtool what-marketing-version | mvers [-terse | -terse1]
    agvtool [-noscm | -usecvs | -usesvn] new-marketing-version <versString>


For more information type "man agvtool".
```

[man ページ](http://www.manpagez.com/man/1/agvtool/)があるようなのでそちらも見ていきます。

> agvtool helps speed up common operations for Xcode projects that use the Apple Generic Versioning system.
> You enable versioning support by setting up some build settings in your project.

Apple Generic Versioning system に則った Xcode プロジェクトのバージョニングを操作できるコマンドとのことです。

説明には以下のように、

>To enable Apple Generic Versioning, then, you must set up at least the VERSIONING_SYSTEM and CURRENT_PROJECT_VERSION project build settings for each project you want to be versioned.

agvtool を使うためには `VERSIONING_SYSTEM` and `CURRENT_PROJECT_VERSION` とプロジェクトで設定しておく必要があると記述されていますが、`VERSIONING_SYSTEM` の方は Xcode のプロジェクトのファイルや、Info.plist を見ても記述がありませんでした。まあ、厳密に設定されていなくても使えるということなのかもしれません。

……と思ったのですが、あらためて Build Settings を見てみると Versioning System の設定項目がありますね。デフォルトは None のようですが。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49589/403be26d-d38b-a8ab-b9f8-b1a7ef4d1a91.png)


man ページには CVS や SVN の記述があることから、かなり古くからあるツールのようですね（最近の人は CVS なんて触ったことないでしょう）。

agvtool の各コマンドを試してみます。

# 各コマンド

## what-version

> what-version | vers [-terse]
>   Prints out the current version number of the project.
>   The -terse option can be used to limit the output to the version number only.

プロジェクトのバージョン番号を表示するコマンドです。`-terse` オプションを付けるとバージョン番号のみを表示します。

とりあえず新規に作成した iOS アプリのプロジェクトで実行してみたところ以下のエラーとなりました。

```shell
$ agvtool what-version

There does not seem to be a CURRENT_PROJECT_VERSION key set for this project.  Add this key to your target's expert build settings.
```

プロジェクトを新規作成した状態では、`CURRENT_PROJECT_VERSION` が設定されていないためエラーになっているようです。

Xcode 上でターゲットにビルド番号 2 を設定したところ、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49589/8ac37304-a59f-541e-c91c-5aad288eb6ea.png)

diff で `CURRENT_PROJECT_VERSION` が設定されていることが確認できました。

```diff
git diff
diff --git a/SwiftUISampleApp.xcodeproj/project.pbxproj b/SwiftUISampleApp.xcodeproj/project.pbxproj
index da3c51e..d652161 100644
--- a/SwiftUISampleApp.xcodeproj/project.pbxproj
+++ b/SwiftUISampleApp.xcodeproj/project.pbxproj
@@ -436,6 +436,7 @@
                                ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                CODE_SIGN_IDENTITY = "iPhone Distribution";
                                CODE_SIGN_STYLE = Manual;
+                               CURRENT_PROJECT_VERSION = 2;
                                DEVELOPMENT_ASSET_PATHS = "SwiftUISampleApp/Preview\\ Content";
                                DEVELOPMENT_TEAM = 466KZQRSS8;
                                ENABLE_PREVIEWS = YES;
@@ -458,6 +459,7 @@
                                ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                CODE_SIGN_IDENTITY = "iPhone Distribution";
                                CODE_SIGN_STYLE = Manual;
+                               CURRENT_PROJECT_VERSION = 2;
                                DEVELOPMENT_ASSET_PATHS = "SwiftUISampleApp/Preview\\ Content";
                                DEVELOPMENT_TEAM = 466KZQRSS8;
                                ENABLE_PREVIEWS = YES;
diff --git a/SwiftUISampleApp/Info.plist b/SwiftUISampleApp/Info.plist
index e8bb4c6..f5594ea 100644
--- a/SwiftUISampleApp/Info.plist
+++ b/SwiftUISampleApp/Info.plist
@@ -17,7 +17,7 @@
        <key>CFBundleShortVersionString</key>
        <string>1.0</string>
        <key>CFBundleVersion</key>
-       <string>1</string>
+       <string>$(CURRENT_PROJECT_VERSION)</string>
        <key>LSRequiresIPhoneOS</key>
        <true/>
        <key>UIApplicationSceneManifest</key>
```

これで `what-version` を実行すると、この 2 が取得できます。

```shell
$ agvtool what-version
Current version of project SwiftUISampleApp is:
    2
```

Xcode のプロジェクトでは複数のターゲットを追加でき、バージョン番号はターゲット毎に設定できます。その場合このコマンドの結果はどうなるのでしょうか。ターゲットを一つ追加し、バージョン番号 3 を設定し試してみると、

```shell
$ agvtool what-version
Current version of project SwiftUISampleApp is:
    2
3
```
となりました。（インデントがおかしい気がしますが）２つバージョン番号が表示されます。

```
`-terse` を付けると

```shell
$ agvtool what-version -terse
2
3
```
となります。


## next-version 

> next-version | bump [-all]
>
>  Increments the version numbers of all versioned targets to the next highest integral value.  For exam-ple, 54 will change to 55 and 234.6 will change to 235.

>  The CURRENT_PROJECT_VERSION and the DYLIB_CURRENT_VERSION will be updated.  The -all option will also update the CFBundleVersion Info.plist key.


すべてのターゲットのバージョン番号をインクリメントしてくれるオプションです。バージョン番号を複数ターゲットを追加し、それぞれのビルド番号が異なる状態で実行してみます。

```shell
$ agvtool what-version -terse
2
3

$ agvtool next-version
Setting version of project SwiftUISampleApp to:
    3.

$ agvtool what-version -terse
3
```

どちらのターゲットも 3 になりました。ちなみに、何回か試したのですが、インクリメントのもととなる番号がどちらのターゲットから選ばれるのかはいまいちはっきりしませんでした。同じ番号に揃えておくと混乱がないでしょうね。


ソースの diff としては以下のようにプロジェクトファイルの `CURRENT_PROJECT_VERSION` が変更されていました。

```diff
git diff
diff --git a/SwiftUISampleApp.xcodeproj/project.pbxproj b/SwiftUISampleApp.xcodeproj/project.pbxproj
index 179d25a..b557adc 100644
--- a/SwiftUISampleApp.xcodeproj/project.pbxproj
+++ b/SwiftUISampleApp.xcodeproj/project.pbxproj
@@ -582,7 +582,7 @@
                                ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                CODE_SIGN_IDENTITY = "iPhone Distribution";
                                CODE_SIGN_STYLE = Manual;
-                               CURRENT_PROJECT_VERSION = 2;
+                               CURRENT_PROJECT_VERSION = 3;
                                DEVELOPMENT_ASSET_PATHS = "SwiftUISampleApp/Preview\\ Content";
                                DEVELOPMENT_TEAM = 466KZQRSS8;
                                ENABLE_PREVIEWS = YES;
@@ -606,7 +606,7 @@
                                ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                CODE_SIGN_IDENTITY = "iPhone Distribution";
                                CODE_SIGN_STYLE = Manual;
-                               CURRENT_PROJECT_VERSION = 2;
+                               CURRENT_PROJECT_VERSION = 3;
                                DEVELOPMENT_ASSET_PATHS = "SwiftUISampleApp/Preview\\ Content";
                                DEVELOPMENT_TEAM = 466KZQRSS8;
                                ENABLE_PREVIEWS = YES;
```


`-all` オプションを付けると Info.plist も更新するとのことです。実行してみると diff は以下の幼羽になりました。

```diff
git diff
diff --git a/Ext0/Info.plist b/Ext0/Info.plist
index e26fe77..f17a902 100644
--- a/Ext0/Info.plist
+++ b/Ext0/Info.plist
@@ -19,7 +19,7 @@
        <key>CFBundleShortVersionString</key>
        <string>1.0</string>
        <key>CFBundleVersion</key>
-       <string>$(CURRENT_PROJECT_VERSION)</string>
+       <string>3</string>
        <key>NSExtension</key>
        <dict>
                <key>NSExtensionAttributes</key>
diff --git a/SwiftUISampleApp.xcodeproj/project.pbxproj b/SwiftUISampleApp.xcodeproj/project.pbxproj
index 179d25a..b557adc 100644
--- a/SwiftUISampleApp.xcodeproj/project.pbxproj
+++ b/SwiftUISampleApp.xcodeproj/project.pbxproj
@@ -582,7 +582,7 @@
                                ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                CODE_SIGN_IDENTITY = "iPhone Distribution";
                                CODE_SIGN_STYLE = Manual;
-                               CURRENT_PROJECT_VERSION = 2;
+                               CURRENT_PROJECT_VERSION = 3;
                                DEVELOPMENT_ASSET_PATHS = "SwiftUISampleApp/Preview\\ Content";
                                DEVELOPMENT_TEAM = 466KZQRSS8;
                                ENABLE_PREVIEWS = YES;
@@ -606,7 +606,7 @@
                                ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                CODE_SIGN_IDENTITY = "iPhone Distribution";
                                CODE_SIGN_STYLE = Manual;
-                               CURRENT_PROJECT_VERSION = 2;
+                               CURRENT_PROJECT_VERSION = 3;
                                DEVELOPMENT_ASSET_PATHS = "SwiftUISampleApp/Preview\\ Content";
                                DEVELOPMENT_TEAM = 466KZQRSS8;
                                ENABLE_PREVIEWS = YES;
diff --git a/SwiftUISampleApp/Info.plist b/SwiftUISampleApp/Info.plist
index f5594ea..c31ac97 100644
--- a/SwiftUISampleApp/Info.plist
+++ b/SwiftUISampleApp/Info.plist
@@ -17,7 +17,7 @@
        <key>CFBundleShortVersionString</key>
        <string>1.0</string>
        <key>CFBundleVersion</key>
-       <string>$(CURRENT_PROJECT_VERSION)</string>
+       <string>3</string>
        <key>LSRequiresIPhoneOS</key>
        <true/>
        <key>UIApplicationSceneManifest</key>
diff --git a/SwiftUISampleAppTests/Info.plist b/SwiftUISampleAppTests/Info.plist
index 64d65ca..f870055 100644
--- a/SwiftUISampleAppTests/Info.plist
+++ b/SwiftUISampleAppTests/Info.plist
@@ -17,6 +17,6 @@
        <key>CFBundleShortVersionString</key>
        <string>1.0</string>
        <key>CFBundleVersion</key>
-       <string>1</string>
+       <string>3</string>
 </dict>
 </plist>
diff --git a/SwiftUISampleAppUITests/Info.plist b/SwiftUISampleAppUITests/Info.plist
index 64d65ca..f870055 100644
--- a/SwiftUISampleAppUITests/Info.plist
+++ b/SwiftUISampleAppUITests/Info.plist
@@ -17,6 +17,6 @@
        <key>CFBundleShortVersionString</key>
        <string>1.0</string>
        <key>CFBundleVersion</key>
-       <string>1</string>
+       <string>3</string>
 </dict>
 </plist>
```

Info.plist が更新されています。元々 `$(CURRENT_PROJECT_VERSION)` でプロジェクトの設定を参照する記述になっていた部分や、リリースするパッケージには関係のないテストターゲットの Info.plist も更新されています。この diff を見ると `-all` オプションを付ける必要はないかもしれません。

またこのコマンドは `1.1.1` というバージョン番号はインクリメントすると `1.1.2` ではなく、`2` とします。マイナーバージョンを自動で上げたい、感覚で使用すると意図と異なる動作になるかもしれないため注意が必要そうです。

## new-version

> new-version [-all] <version>
>
>  Sets the version numbers of all targets to the given version.
>  The CURRENT_PROJECT_VERSION and the DYLIB_CURRENT_VERSION will be updated.  The -all option will also update the CFBundleVersion Info.plist key.

`next-version` と動作はほぼ同じですが、バージョン番号はインクリメントされるのではなく明示的にオプションで指定します。

## tag 

バージョン管理システムに tag を設定するコマンドのようですが、CVS と SVN のみサポートしており、Git はサポートしていません。そのため使うことはなさそうです。

> Note: This command will only function if CVS or Subversion support is enabled.

## submit

プロジェクトのリリースやビルドの情報を登録するためのコマンドのようなのですが、

> Note: This command is relevant only for Apple employees.

とあり、Apple 社員のみ使用する（現在使用しているのかは不明ですが）コマンドのようです。社員専用のコマンドまで含んでいるなんて面白いですね。

## what-marketing-version

> what-marketing-version | mvers [-terse | -terse1]
> 
>   Prints the current marketing version of the project. For native targets, a marketing version is listed for each Info.plist file found. For Jambase targets a marketing version is shown if a common value is found.
> 
>   The marketing version is the CFBundleShortVersionString Info.plist key. This is often a totally differ-ent version determined by product marketing folks.
> 
> The -terse option will limit the output to the version number only when displaying version numbers for Jambase targets.
> 
> The -terse1 option will limit the output to the first version number found, and only display the version number.

`what-version` のバージョン番号はいわゆる「ビルド番号」と言われるものでしたが、marketing version は アプリの利用者に見せるバージョン番号のことを指します。`what-marketing-version` コマンドはそのバージョン番号を表示するコマンドです。

プロジェクトでは Info.plist の `CFBundleShortVersionString` に設定する値ですね。

実行してみると以下のようになりました。各 Info.plist に設定されている値が表示されます。
`-terse1` では最初のバージョン番号のみ表示します。

```shell
$ agvtool what-marketing-version
No marketing version number (CFBundleShortVersionString) found for Jambase targets.

Looking for marketing version in native targets...
Looking for marketing version (CFBundleShortVersionString) in native targets...

Found CFBundleShortVersionString of "1.0" in "SwiftUISampleApp.xcodeproj/../SwiftUISampleApp/Info.plist"
Found CFBundleShortVersionString of "1.0" in "SwiftUISampleApp.xcodeproj/../SwiftUISampleAppTests/Info.plist"
Found CFBundleShortVersionString of "1.0" in "SwiftUISampleApp.xcodeproj/../SwiftUISampleAppUITests/Info.plist"


$ agvtool what-marketing-version -terse
"SwiftUISampleApp.xcodeproj/../SwiftUISampleApp/Info.plist"=1.0
"SwiftUISampleApp.xcodeproj/../SwiftUISampleAppTests/Info.plist"=1.0
"SwiftUISampleApp.xcodeproj/../SwiftUISampleAppUITests/Info.plist"=1.0

$ agvtool what-marketing-version -terse1
1.0
```

## new-marketing-version

> new-marketing-version version
>
>   Sets the marketing version numbers of all versioned targets to the given version number.
>   The marketing version is the CFBundleShortVersionString Info.plist key. This is often a totally different version determined by product marketing folks.
>


`CFBundleShortVersionString` のバージョン番号を設定するコマンドです。実行したところ以下のようになり、各 Info.plist が変更されます。

```shell
$ agvtool new-marketing-version 200.0
Setting CFBundleShortVersionString of project SwiftUISampleApp to:
    200.0.

Updating CFBundleShortVersionString in Info.plist(s)...

Updated CFBundleShortVersionString in "SwiftUISampleApp.xcodeproj/../SwiftUISampleApp/Info.plist" to 200.0
Updated CFBundleShortVersionString in "SwiftUISampleApp.xcodeproj/../SwiftUISampleAppTests/Info.plist" to 200.0
Updated CFBundleShortVersionString in "SwiftUISampleApp.xcodeproj/../SwiftUISampleAppUITests/Info.plist" to 200.0
```

```diff
$ git diff
diff --git a/SwiftUISampleApp/Info.plist b/SwiftUISampleApp/Info.plist
index e8bb4c6..9029b21 100644
--- a/SwiftUISampleApp/Info.plist
+++ b/SwiftUISampleApp/Info.plist
@@ -15,7 +15,7 @@
        <key>CFBundlePackageType</key>
        <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
        <key>CFBundleShortVersionString</key>
-       <string>1.0</string>
+       <string>200.0</string>
        <key>CFBundleVersion</key>
        <string>1</string>
        <key>LSRequiresIPhoneOS</key>
diff --git a/SwiftUISampleAppTests/Info.plist b/SwiftUISampleAppTests/Info.plist
index 64d65ca..597d891 100644
--- a/SwiftUISampleAppTests/Info.plist
+++ b/SwiftUISampleAppTests/Info.plist
@@ -15,7 +15,7 @@
        <key>CFBundlePackageType</key>
        <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
        <key>CFBundleShortVersionString</key>
-       <string>1.0</string>
+       <string>200.0</string>
        <key>CFBundleVersion</key>
        <string>1</string>
 </dict>
diff --git a/SwiftUISampleAppUITests/Info.plist b/SwiftUISampleAppUITests/Info.plist
index 64d65ca..597d891 100644
--- a/SwiftUISampleAppUITests/Info.plist
+++ b/SwiftUISampleAppUITests/Info.plist
@@ -15,7 +15,7 @@
        <key>CFBundlePackageType</key>
        <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
        <key>CFBundleShortVersionString</key>
-       <string>1.0</string>
+       <string>200.0</string>
        <key>CFBundleVersion</key>
        <string>1</string>
 </dict>

```

# まとめ

agvtool の使い方を一通り見てみました。バージョン番号やビルド番号の更新は開発中定期的に行う作業のため、このコマンドを手元で使ったり、CIに組み込んで使うと良さそうですね。fastlane の `increment_build_number` も内部的にこのコマンドを利用しています。