Android は Github Actions を使用して継続的に統合し、APK を Dandelion アプリの内部テスト配布プラットフォームに自動的にアップロードします (証明書のパスワードの感度解除を含む)

目次

1 はじめに

2. Github Actions の継続的インテグレーション

3.apkをDandelionにアップロードする

4. Gradle は Github Actions のシークレットで使用されます

4.1 Github Actions Secretの設定

4.2 app.gradlede の signedConfigs を変更して、Github Actions Secrets セットを読み取る

5. YAML、YMLオンラインエディター(バリデーター)


1 はじめに

継続的インテグレーションに関する記事の著者は、以前に Jenkins と Travis CI の使用例をいくつか書きました。

Docker+JenkinsでAndroidの継続的統合を実現 (1)

Docker+JenkinsでAndroidの継続的統合を実現 (2)

Travis CI に基づく Android の継続的インテグレーションの実践

Travis CI は当初、有料と無料の 2 つのバージョンに分かれており、プライベート プロジェクトには課金され、すべての無料およびオープン ソース (OSS) プロジェクトには無料のクレジットが提供されます。2019 年 1 月に Travis CI はIdera に買収され、当時の公式保証では引き続きオープンソース プロジェクト向けの無料サービスを提供していましたが、現在はビジネス戦略を変更しています。最新の公式価格体系をご覧ください。

クレジットが限られていて、すぐに不快になってしまいますか?

• 10,000クレジット

• Assembla、Bitbucket、GitHub、GitL abとの互換性

Circle CI と比較すると判断がつきます。

言うまでもなく、ジェンキンスさん、お金と意志があれば自分でサーバーをセットアップできます。

Github Actions が提供する無料の食事を見てみましょう。

サポートされているランナーとハードウェア リソース
Windows および Linux 仮想マシンのハードウェア仕様:

2 コア CPU
7 GB RAM メモリ
14 GB SSD ディスク容量
macOS 仮想マシンのハードウェア仕様:

3 コア CPU
14 GB RAM メモリ
14 GB SSD ディスク容量

上記の情報は、Github Actions の公式ドキュメントから引用されています。

GitHub でホストされるランナーについて - GitHub Docs

高パフォーマンスで無料、Github Actions が良いかどうかだけを聞いてください。

2. Github Actions の継続的インテグレーション

繰り返されるコンテンツを書き直す必要はありません。参考として、より適切に書かれた 2 つの記事を次に示します。

【継続的インテグレーション】 Android は Github Action を使用して Fir.im 内部テストを自動的にパッケージ化してリリースします

Xue XuanGithub Actions ユーザー ガイドと Android 継続的インテグレーションの例

3.apkをDandelionにアップロードする

なぜ fir.im (betaqr.com) ではなくタンポポなのでしょうか。多くは言いませんが、まず android.yml ファイルの全体像を見てください。

name: Android CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: set up JDK 11
      uses: actions/setup-java@v2
      with:
        java-version: '11'
        distribution: 'temurin'
        cache: gradle

    - name: Grant execute permission for gradlew
      run: chmod +x gradlew

    - name: Build with Gradle
      run: ./gradlew app:assembleRelease

    - name: Upload apk to pgyer.com
      run: find ${
   
   { github.workspace }}/app/build/outputs/apk/release/ -name "*.apk*" -type f -exec curl -F "file=@{}" -F "uKey=${
   
   { secrets.PGYER_UKEY }}" -F "_api_key=${
   
   { secrets.PGYER_API_KEY }}" https://upload.pgyer.com/apiv1/app/upload  \;

    - name: Upload apk to artifact
      uses: actions/upload-artifact@master
      if: always()
      with:
        name: lottery_app
        path: ${
   
   { github.workspace }}/app/build/outputs/apk/release/*.apk

上記は、著者のオープンソース プロジェクトからのものです。

https://github.com/xiangang/RecyclerViewLoopScrollAnimation/blob/main/.github/workflows/android.yml

android.yml のその他の部分についてはあまり説明しません。ここでは主にファイル名が不確かな APK をアップロードする方法を紹介します。インターネット上のほとんどの記事では APK 名のこの部分について説明されていますが、実際のプロジェクトでは一般的にAPK 名にチャネル名、バージョン番号、コンパイル時間などを加えたものであるため、APK 名をハードコードするのは明らかに不適切です。作成者は最初に *.apk を使用して一致させようとしましたが、curl はこの方法でのワイルドカードの使用をサポートしていないため、ファイルを開けないというプロンプトが表示され続けました。

何をするか?*.apk が一致しない場合は、まず apk 名を見つけて、find コマンドで完了できます。

次のコマンドを使用すると、apk ファイル名を簡単に照会できます。もちろん、apk が複数ある場合は、より正確に一致する条件を変更する必要があります。

find ./ -name "*.apk*" -type f

ファイル名が見つかったら、それをcurlコマンドで使用するのが当然ではないでしょうか。絶対に。find コマンドの使用法はここでは紹介しませんが、興味があれば man find コマンドを使用してヘルプ ドキュメントを参照してください。

次のコマンドの意味を簡単に説明します。

実行: find ${
    
   { github.workspace }}/app/build/outputs/apk/release/ -name "*.apk*" -type f -execcurl -F "file=@{}" -F "uKey= ${
    
   { Secrets.PGYER_UKEY }}" -F "_api_key=${
    
   { Secrets.PGYER_API_KEY }}" https://upload.pgyer.com/apiv1/app/upload \;

find ${ { github.workspace }}/app/build/outputs/apk/release/ -name "*.apk*" -type f

${ { github.workspace }}/app/build/outputs/apk/release/ ディレクトリ で *.apk* という名前のファイルを見つけます。このうち、-type は指定されたファイルの種類を表し、f は共通のドキュメントの種類を表します。

find xxx -exec xxxx {} \; : 見つかったファイルに対して特定のコマンドを実行することを表し、コマンドは find で見つかったファイルのセットを使用できます。

-exec : xxxx コマンドの実行を表します

{} : find コマンドで見つかった対象となるファイルのコレクションに保存された変数を表します。

\; : find コマンドのスコープは次で終了します。

上記の使用法を理解すると、今後他のビルド製品に遭遇したときに、アップロードしたり電子メールを送信したりすることが非常に簡単になります新スキルゲット!

4. Gradle は Github Actions のシークレットで使用されます

一般的な商業プロジェクトでは、通常、機密保持対策が講じられています。これは、APK のコンパイル時に署名に使用されるパスワードや証明書のその他の機密コンテンツをコード リポジトリにプレーン テキストで直接送信しないことを意味します。継続的統合環境では、通常、環境変数を使用して、公開された平文パスワードを置き換えることができます。もちろん、社内サーバーやその他の承認された統合環境の場合は、平文パスワードを暗号化するための暗号化アルゴリズムを作成することもできます。コンパイルしてから復号化します。

この記事では、環境変数の設定による感度解除の効果のみを紹介します。この部分の内容は「Travis CIによるAndroid継続的インテグレーション実践」でも解説していますので、興味のある方は読んでみてください。

4.1 Github Actions Secretの設定

Github リポジトリ設定ページに入り、左側の [シークレット] メニューをクリックし、右上隅の [新しいリポジトリ シークレット] をクリックし、リダイレクトされたページで対応する名前の値を入力し、[シークレットの追加] ボタンをクリックします。名前は自動的に大文字に変換されることに注意してください。

名前に対応する値を変更することも非常に簡単です。変更するには、リスト内の「更新」をクリックするだけです。変更できるのは値のみで、名前は変更できないことに注意してください。名前が間違っている場合は、削除して再度追加してください。示されているように、作成者は署名に必要な 3 つの Secret を設定しました。

4.2 app.gradlede の signedConfigs を変更して、Github Actions Secrets セットを読み取る

まず、元の証明書のパスワードがどのように読み取られるかを見てください。

static def getAppReleaseTime() {
    return new Date().format("yyyyMMdd_HHmm", TimeZone.getTimeZone("Asia/Shanghai"))
}

// Remove private signing information from your project
// 创建一个名为keystorePropertiesFile的变量,并将其初始化为rootProject文件夹中的keystore.properties文件。
def keystorePropertiesFile = rootProject.file("keystore.properties")
// 初始化一个名为keystoreProperties的新Properties()对象
def keystoreProperties = new Properties()
// 将keystore.properties文件加载到keystoreProperties对象中
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))


android {
    compileSdk 31

    defaultConfig {
        applicationId "com.nxg.app"
        minSdk 21
        targetSdk 31
        versionCode 1
        versionName "1.0.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    signingConfigs {
        release {
            storeFile keystoreProperties['keyAlias'] != null ? file(keystoreProperties['storeFile']) : file('../demo.jks')
            storePassword keystoreProperties['storePassword'] != null ? keystoreProperties['storePassword'] : System.getenv("storePassword")
            keyAlias keystoreProperties['keyAlias'] != null ? keystoreProperties['keyAlias'] : System.getenv("keyAlias")
            keyPassword keystoreProperties['keyPassword'] != null ? keystoreProperties['keyPassword'] : System.getenv("keyPassword")
        }
    }

    buildTypes {
        debug {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            shrinkResources true //是否清理无用资源,依赖于minifyEnabled
            zipAlignEnabled true //是否启用zipAlign压缩
            signingConfig signingConfigs.release
            manifestPlaceholders = [RELEASE_TIME: getAppReleaseTime()]
            multiDexEnabled = true
            versionNameSuffix = ''
        }

        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            shrinkResources true //是否清理无用资源,依赖于minifyEnabled
            zipAlignEnabled true //是否启用zipAlign压缩
            signingConfig signingConfigs.release
            manifestPlaceholders = [RELEASE_TIME: getAppReleaseTime()]
            multiDexEnabled = true
            versionNameSuffix = ''
            // 自定义apk名称
            applicationVariants.all { variant ->
                variant.outputs.all { output ->
                    def fileName = "lucky_cube_app_release_${variant.versionName}_${appReleaseTime}.apk"
                    def outFile = output.outputFile
                    if (outFile != null && outFile.name.endsWith('.apk')) {
                        outputFileName = fileName
                    }
                }
            }
        }

    }
}

プロジェクトのルート ディレクトリには、keystore.propertiesファイルがあります。

# 线上版本持续集成使用环境变量,本地开发时手动填上对应的值
storePassword =
keyAlias = 
keyPassword =
storeFile= 

ローカルでコンパイルする場合、通常は、対応するパスワードと証明書ファイルのパスを keystore.properties に入力します。オンラインコンパイルの場合は、オンラインコンパイル環境で設定された環境変数を使用します。

先ほど、署名に必要な Secret を Github Actions Serects で設定しましたが、どのように使用するのでしょうか? もちろん、環境変数を使用します。Github Actions Serect は実際には Github Actions が提供するサーバーに保存されており、gradle で直接読み取る方法がないため、環境変数を読み取ることで、対応する Github Actions Serect を間接的に読み取ることしかできません。

コードに直接移動します。

static def getAppReleaseTime() {
    return new Date().format("yyyyMMdd_HHmm", TimeZone.getTimeZone("Asia/Shanghai"))
}

// Remove private signing information from your project
// 创建一个名为keystorePropertiesFile的变量,并将其初始化为rootProject文件夹中的keystore.properties文件。
def keystorePropertiesFile = rootProject.file("keystore.properties")
// 初始化一个名为keystoreProperties的新Properties()对象
def keystoreProperties = new Properties()
// 将keystore.properties文件加载到keystoreProperties对象中
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
println "keystoreProperties->$keystoreProperties"

def getStoreFile = {
    def storeFile = keystoreProperties['storeFile']
    if (storeFile == null || storeFile.isEmpty()) {
        storeFile = '../demo.jks'
    }
    return storeFile
}

def getStorePassword = {
    def storePassword = keystoreProperties['storePassword']
    if (storePassword == null || storePassword.isEmpty()) {
        storePassword = System.getenv("storePassword")
    }
    return storePassword
}

def getKeyAlias = {
    def keyAlias = keystoreProperties['keyAlias']
    if (keyAlias == null || keyAlias.isEmpty()) {
        keyAlias = System.getenv("keyAlias")
    }
    return keyAlias
}

def getKeyPassword = {
    def keyPassword = keystoreProperties['keyPassword']
    if (keyPassword == null || keyPassword.isEmpty()) {
        keyPassword = System.getenv("keyPassword")
    }
    return keyPassword
}

println "storePassword->${System.getenv("storePassword")}"
println "keyAlias->${System.getenv("keyAlias")}"
println "keyPassword->${System.getenv("keyPassword")}"

println "getStoreFile->${getStoreFile()}"
println "getStorePassword->${getStorePassword()}"
println "getKeyAlias->${getKeyAlias()}"
println "getKeyPassword->${getKeyPassword()}"

android {
    compileSdk 31

    defaultConfig {
        applicationId "com.nxg.app"
        minSdk 21
        targetSdk 31
        versionCode 1
        versionName "1.0.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    signingConfigs {

        release {
            storeFile file(getStoreFile())
            storePassword getStorePassword()
            keyAlias getKeyAlias()
            keyPassword getKeyPassword()
        }
    }

    buildTypes {
        debug {
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            manifestPlaceholders = [RELEASE_TIME: getAppReleaseTime()]
            multiDexEnabled = true
        }

        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            shrinkResources true //是否清理无用资源,依赖于minifyEnabled
            zipAlignEnabled true //是否启用zipAlign压缩
            signingConfig signingConfigs.release
            manifestPlaceholders = [RELEASE_TIME: getAppReleaseTime()]
            multiDexEnabled = true
            versionNameSuffix = ''
            // 自定义apk名称
            applicationVariants.all { variant ->
                variant.outputs.all { output ->
                    def fileName = "lucky_cube_app_release_${variant.versionName}_${appReleaseTime}.apk"
                    def outFile = output.outputFile
                    if (outFile != null && outFile.name.endsWith('.apk')) {
                        outputFileName = fileName
                    }
                }
            }
        }

    }
}

でENV_NAMEという環境変数の値を取得するためにapp.gradle使用されていることがわかりますが、もちろん別の書き方もありますSystem.getenv("ENV_NAME")System.env.ENV_NAME

ここではstorePasswordを例に挙げますが、コンパイル時にはkeystore.propertiesに定義されているstorePasswordの値が最初に取得され、空の場合は環境変数に定義された値が取得されますので、オンラインコンパイル環境に対応して、通常、keystore.properties で定義された値は送信されません。値はローカルでのみ使用され、keystore.properties さえコード ウェアハウスに送信されません。

def getStorePassword = {
    def storePassword = keystoreProperties['storePassword']
    if (storePassword == null || storePassword.isEmpty()) {
        storePassword = System.getenv("storePassword")
    }
    return storePassword
}

このように、signingConfigs では、対応するメソッドを直接呼び出して環境変数の値を取得できます。

signingConfigs {
    release {
        storeFile file(getStoreFile())
        storePassword getStorePassword()
        keyAlias getKeyAlias()
        keyPassword getKeyPassword()
    }
}

それで終わりですか?もちろんそうではありません。環境変数と Github Actions Serects はまだ関連付けられていないため、この時点で読み取られる環境変数には値がないことを忘れないでください。

環境変数は Github Actions Serects にどのように関連していますか?

exportLinux システムの使用に慣れている学生はすぐにコマンドを思いつくでしょう。exportコマンドを通じて、一時的な環境変数を設定できます。変数の値は、Github Actions Serectsで設定された値に対応します。

主要な android.yml コードは次のとおりです。

name: Android CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: set up JDK 11
      uses: actions/setup-java@v2
      with:
        java-version: '11'
        distribution: 'temurin'
        cache: gradle

    - name: Grant execute permission for gradlew
      run: chmod +x gradlew

    - name: Build with Gradle
      run: |
        export storePassword=${
   
   { secrets.STOREPASSWORD }}
        export keyAlias=${
   
   { secrets.KEYALIAS }}
        export keyPassword=${
   
   { secrets.KEYPASSWORD }}
        ./gradlew app:assembleRelease


    - name: Upload apk to pgyer.com
      run: find ${
   
   { github.workspace }}/app/build/outputs/apk/release/ -name "*.apk*" -type f -exec curl -F "file=@{}" -F "uKey=${
   
   { secrets.PGYER_UKEY }}" -F "_api_key=${
   
   { secrets.PGYER_API_KEY }}" https://upload.pgyer.com/apiv1/app/upload  \;

    - name: Upload apk to artifact
      uses: actions/upload-artifact@master
      if: always()
      with:
        name: lottery_app
        path: ${
   
   { github.workspace }}/app/build/outputs/apk/release/*.apk

Github Actions では、設定されたシークレットがこの形式で取得され、現在実行中のコンパイル環境でシステムに設定されている一時環境変数を読み取るコマンドと${ { secrets.STOREPASSWORD }}連携するだけで済むことがわかります。exportコンパイル終了すると、この一時環境変数は読み取れなくなりますが、いつでもコンパイルして使用することができ、使い切ってもシステムに影響はありません。

apk のコンパイルには AssemblyRelease タスクを使用するため、環境変数を設定するには、apk のコンパイル時に、assembleRelease タスクを実行する前に、System.getenv("ENV_NAME")環境変数に設定された Github Actions Serects を確実に読み取れるようにする必要があります。

これまでのところ、Github Actions Serects と環境変数を介して証明書のパスワードの感度を解除しており、オンライン コンパイル環境のセキュリティがある程度確保されています。新スキルゲット!

5. YAML、YMLオンラインエディター(バリデーター)

YAML、YMLオンラインエディター(書式検証) - BeJSON.com

さらに、android.yml の記述により、スクリプトのコンパイルに誤って問題が発生することがよくありますが、その場合、このオンライン Web サイトで検証できるため、トラブルシューティングに非常に便利です。あなたの時間は非常に貴重であることを知ってください。右。

最後に書いていますが、まずは最後まで読んでいただき、誠にありがとうございます。オリジナルで実践的な記事を書くことにこだわるのは簡単なことではありません。この記事がお役に立ちましたら、「いいね!」していただければ幸いです。あなたの励ましが著者の主張です。記事内に間違いがございましたら修正させていただきますので、またよろしくお願いいたします。

おすすめ

転載: blog.csdn.net/xiangang12202/article/details/122594984