GitHub ActionsでXamarin AndroidをReleaseビルドする方法です。
とりあえずyamlから。解説はその下にあります。
name: CI on Push and Pull Request
on:
push:
tags: 'v*'
workflow_dispatch:
pull_request:
jobs:
Android:
runs-on: macos-latest
env:
SlnPath: Earphone/
AndroidCsprojPath: EarphoneLeftAndRight/EarphoneLeftAndRight.Android/
AndroidCsprojName: EarphoneLeftAndRight.Android.csproj
AndroidAppName: com.github.kurema.earphoneleftandright
steps:
- uses: actions/checkout@v1
# For Windows
# - name: Add msbuild to PATH
# uses: microsoft/setup-msbuild@v1.0.3
- name: write keystore
run: |
echo $KEYSTORE_BASE64 | base64 --decode > ${{ env.SlnPath }}${{ env.AndroidCsprojPath }}github.keystore
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64_ENCODED }}
- name: Get tag
id: tag
if: contains(github.ref, 'tags/v')
uses: dawidd6/action-get-tag@v1
- name: Is tagged version
id: tagged
run: echo '::set-output name=tagged::yes'
#https://pione30.hatenablog.com/entry/2021/02/05/015545
if: contains(github.ref, 'tags/v')
- name: android package format
id: apf
run: |
if [ "${{steps.tagged.outputs.tagged}}" = "yes" ]; then
echo '::set-output name=format::aab'
else
echo '::set-output name=format::apk'
fi
- name: Android
run: |
cd ${{ env.SlnPath }}
nuget restore
msbuild ${{ env.AndroidCsprojPath }}${{ env.AndroidCsprojName }} /verbosity:normal /t:Rebuild /t:PackageForAndroid /t:SignAndroidPackage /p:Configuration=Release /p:AndroidKeyStore=True /p:AndroidSigningKeyStore=github.keystore /p:AndroidSigningStorePass=${{ secrets.KEYSTORE_PASSWORD }} /p:AndroidSigningKeyAlias=github /p:AndroidSigningKeyPass=${{ secrets.KEYSTORE_PASSWORD }} /p:AndroidPackageFormat=${{ steps.apf.outputs.format }} /p:AotAssemblies=true /p:EnableLLVM=true
- name: Build Apk version
run: |
cd ${{ env.SlnPath }}
msbuild ${{ env.AndroidCsprojPath }}${{ env.AndroidCsprojName }} /verbosity:quiet /t:Build /t:PackageForAndroid /t:SignAndroidPackage /p:Configuration=Release /p:AndroidKeyStore=True /p:AndroidSigningKeyStore=github.keystore /p:AndroidSigningStorePass=${{ secrets.KEYSTORE_PASSWORD }} /p:AndroidSigningKeyAlias=github /p:AndroidSigningKeyPass=${{ secrets.KEYSTORE_PASSWORD }} /p:AndroidPackageFormat=apk /p:AotAssemblies=true /p:EnableLLVM=true
if: contains(github.ref, 'tags/v')
- name: delete keystore
run: |
rm ${{ env.SlnPath }}${{ env.AndroidCsprojPath }}github.keystore
if: always()
- uses: actions/upload-artifact@v2
with:
name: Android App
path: ${{ env.SlnPath }}${{ env.AndroidCsprojPath }}bin/Release/${{ env.AndroidAppName }}-Signed.*
if: ${{ !contains(github.ref, 'tags/v') }}
- name: Create release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: contains(github.ref, 'tags/v')
with:
tag_name: ${{ github.ref }}
release_name: ${{steps.tag.outputs.tag}}
draft: false
prerelease: false
- name: Update release asset
uses: actions/upload-release-asset@v1
if: contains(github.ref, 'tags/v')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ env.SlnPath }}${{ env.AndroidCsprojPath }}bin/Release/${{ env.AndroidAppName }}-Signed.aab
asset_name: ${{ env.AndroidAppName }}-Signed.aab
asset_content_type: application/zip
- name: Update release asset Apk
uses: actions/upload-release-asset@v1
if: contains(github.ref, 'tags/v')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ env.SlnPath }}${{ env.AndroidCsprojPath }}bin/Release/${{ env.AndroidAppName }}-Signed.apk
asset_name: ${{ env.AndroidAppName }}-Signed.apk
asset_content_type: application/zip
ちょっとおかしなところもありますが気にしないでください。試行錯誤の結果です。
宣伝
イヤホンの左右をQuick Settings Tileから確認するアプリを最近リリースしました。
これはその時、GitHub Actionsを利用しビルドした時の記録です。
解説
メリット
CI/CDの一般的な話以外に、GitHub ActionsでReleaseビルドをするメリットはいくつかあります。
個人開発で自分しかレポジトリに触らないとしても対応しておくのがおすすめです。
- tagから自動で(GitHubの)Releaseを作成してくれる。
- Releaseタブからapkをインストールできる。
- 世の中にはGoogle Playを利用できないAndroid端末利用者が結構います。そういう人への最低限の対応としてapk直接配布は便利です。
- Visual Studio CommunityなどのユーザーでもEnterprise機能が手軽に使える。
- 特にAOTコンパイルに対応できる!
- 無料なのでお得感がある。
- 本格的な雰囲気が増す。ポートフォリオやアピールとしても、自己満足としても良し。
なぜmacOS?
GitHub Actionsでのビルドは以下の価格になっています。
OS | 料金 / 分 |
---|---|
Linux | $0.008 |
macOS | $0.08 |
Windows | $0.016 |
macOSはLinuxの10倍。クソ高いです。でも公式ドキュメントでもmacOSを使っています。
おそらく理由は以下です。
- iOSアプリはmacOSでしかビルドできない。Xamarin FormsでiOSを含むプラットフォームをビルドするならmacOS一択。
- Windowsでビルドすると、「パス名が長すぎます」系のエラーで落ちることが多い (Xamarin Androidあるある)。
- パブリックレポジトリなら無料。
Xamarin Androidアプリだけならパス名の問題を何とか回避すればWindowsでもビルドできますし、Linuxならパス名の問題は発生しないはずです。
プライベートレポジトリでビルドする方はmacOSは避けた方が節約になるでしょう。
パブリックレポジトリでビルドする方はどうせ無料なのでmacOSを使いましょう。
Windowsでビルドする場合、以下の追加が必要です。
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.3
APIキーなど秘匿したいファイルがある
一応言っておくと、アプリとして公開する以上はガチの秘匿は出来ません。
ですが、とにかく.gitignore
でアップロードしていないファイルを追加するなら以下のようにすればできます。
- name: write secret.cs
run: |
echo $SOURCE> SolutionFolder/ProjectFolder/secret.cs
env:
SOURCE: ${{ secrets.SECRET_CS }}
- name: write binary
run: |
echo $BINARY | base64 --decode > out.bin
env:
BINARY: ${{ secrets.BINARY_BASE64_ENCODED }}
- name: Replace Ad Unit ID
run: |
sed -i -e "s@$ADMOB_OLD@$ADMOB_NEW@g" ${{ env.SlnPath }}${{ env.AndroidCsprojPath }}AdMobBannerRenderer.cs
env:
ADMOB_OLD: ca-app-pub-3940256099942544/6300978111
ADMOB_NEW: ${{ secrets.ADMOB_UNIT_ID_BANNER }}
Xamarin Androidでコンパイル後の情報を秘匿したい場合、難読化も必要です。
ですが難読化も手間を掛ければ、あるいは掛けなくても回避できます。
Microsoft Docsでは以下の手段が示されています。
- 「アセンブリをネイティブ コードにバンドルする」をオンにする。
- Dotfuscatorによるアプリケーションの保護。
XamarinではProGuardやr8は難読化の効果はありません。
「アセンブリをネイティブ コードにバンドルする」も最低限の難読化にしかなりません(後述)。
Dotfuscatorは試したことはないのでよく分かりません。
Enterpriseライセンス機能を使う
Xamarin関係のいくつかの機能はVisual Studio Enterpriseライセンスが必要です。
GitHub Actionsのメリットの一つがそうした機能を手軽に使えることです。
Windowsの場合はVisual Studio Enterprise 2019が入っていますし、macOS版のXamarinでも普通にEnterprise機能が使えるみたいです。
これらの機能を使う一番手っ取り早い手段はcsprojを直接操作することです。
「Enterpriseライセンスが必要」と言うのはUIという話で、直接操作することは普通にできます。
なんならMSBuildを直接叩けばWindowsでEnterpriseライセンスを持っていなくても普通にコンパイルが通りそうな気もしますね。試してませんが。
ですから、csprojを書き換えてGitHub ActionsでビルドすればEnterprise機能は普通に使えます。
ただこれには一つ問題があって、Visual Studioでcsprojを操作すると知らない内に設定が戻っていたりします。
その場合はmsbuild
行のコマンドライン引数で固定することができます。
/p:Configuration=Release
のような形です。
ただし、ローカルのcsprojと違う条件でコンパイルするわけですから、忘れて混乱することがあり得るので気を付けてください。
AOTコンパイル
Enterprise機能で個人的に一番美味しいと思うのはこれです。
AOTコンパイルをすると起動が速くなり、APKのサイズが大きくなります。
Xamarin Androidアプリで気になるのが起動の遅さなのでこれは本気で嬉しい奴です。
msbuildの行に以下を追加してください。
もちろんcsprojのファイルをローカルで操作しても、GitHub Actions上で操作しても構いません。
ここではついでに「LLVM 最適化コンパイラ」もオンにしています(...は省略の意味です)。
msbuild ... /p:AotAssemblies=true /p:EnableLLVM=true
アセンブリをネイティブコードにバンドルする
これは「最低限の難読化」として機能する、つまり気休めにしかならないようです。
ただapkを解凍すればdllファイルがあって逆コンパイラで丸見え、という状況よりは技術的なハードルが上がるかもしれません。
またapkのサイズが小さくなるようです。こちらが主な目的でしょう。
Android App Bundleと同じようなものみたいなので私はオフにしています。
今回作ったXamarin Androidアプリはオープンソースですし、apkサイズはあまり気にしていないので、特にメリットは感じません。
GitHubのIssueが参考になります(英語)。
試していませんが、多分以下の行でオンにできます。
msbuild ... /p:BundleAssemblies=true
Android App Bundleとapk
「2021 年 8 月より、Google Play での新規アプリの公開は Android App Bundle で行う必要があります」とのことで、ストア提出はAndroid App Bundleは必須です。
一方、直接インストールする際はapkを使うので使い分けが必要です。
今回git tagからGitHubのReleaseを生成するようにしていますが、そこにapkとaabの両方を添付しています。後者は自分用です。
msbuild ... /p:AndroidPackageFormat=aab
コードshrinker / リンカープロパティ
プロジェクトの設定内「Androidオプション」で、「コードshrinker」を「r8」に、「リンク中」を「なし」以外にするとapkのサイズが小さくなるようです…がおすすめしません。
ビルド失敗の原因となるので、少なくとも最初は「コードshrinker」を「」に、「リンク中」を「なし」に設定しましょう(Visual Studio上で)。
詳しくはこちらの記事が参考になります。
要は辞めておいた方が良いようです。
2021年に数MBを節約するため苦労するのはおすすめしません。
鍵生成
鍵の生成はkeytoolを用います。
Visual Studio上では「Android adbコマンドプロンプトを開く」内で実行できます。
概ねこんなコマンドです。
(JKSキーストアの場合)パスワードはキーストアに対するものと個別の鍵に対するものがあるようなので、既存のkeystoreのパスワードを変更してSecretsに設定しようとする場合など注意してください(少しハマりました)。
keytool -genkeypair -keyalg RSA -keysize 4096 -validity 36500 -alias github -keystore github.keystore -deststoretype pkcs12
生成した鍵はストレージの破損で失われないよう暗号化してバックアップしておくことをおすすめします。
昔はkeystoreを紛失するとアプリを更新できなくなるようですが、今はそうでもないようです。
でも紛失しないに越したことはありません。
生成した鍵はバイナリ形式なのでSecretsに設定するにはbase64に変換が必要です。
WSLがインストールされている場合は、該当フォルダで以下のようにすれば良いです。
Explorerのアドレスバーにbashと入力するのが速いのでおすすめです。
$ cat *.keystore | base64 > b64.txt
Secretsの設定方法などはググってください。
if: ${{ !contains(github.ref, 'tags/v') }}
yamlの仕様上、if: contains(github.ref, 'tags/v')
は良くてもif: !contains(github.ref, 'tags/v')
はダメなようです。
謎ですね。
おまけ
履歴
GitHub Actionsあるある。
Git Squash
? 何ですかねそれ。
結果
公開後一カ月時点でインストール数 4(自分含む)、広告収入1円未満(0円表示)です。
Google Playは宣伝しないとほぼ全くインストールされないのでやる気が出ません。