diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 29fd051e0b..67d396fbdd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -124,6 +124,16 @@ jobs: runs-on: ${{ matrix.os }} steps: + # https://github.com/easimon/maximize-build-space/blob/fc881a613ad2a34aca9c9624518214ebc21dfc0c/action.yml#L122-L136 + - name: Remove unnecessary tools + if: startsWith(matrix.os, 'ubuntu-') + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo docker image prune --all --force + - uses: actions/checkout@v3 # NOTE: The default sed of macOS is BSD sed. @@ -489,3 +499,147 @@ jobs: dist_electron/nsis-web/out/*.7z.* dist_electron/nsis-web/*.exe target_commitish: ${{ github.sha }} + + build-and-upload-android: + runs-on: ubuntu-20.04 + steps: + - name: Remove unnecessary tools + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo docker image prune --all --force + - uses: actions/checkout@v3 + + - name: Replace package version + run: | + sed -i 's/"version": "999.999.999"/"version": "${{ env.VOICEVOX_EDITOR_VERSION }}"/' package.json + sed -i 's/versionCode 1/versionCode ${{ github.run_number }}/' android/app/build.gradle + sed -E -i 's/versionName ".+"/versionName "${{ env.VOICEVOX_EDITOR_VERSION }}"/' android/app/build.gradle + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: ".node-version" + cache: "npm" + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: "adopt" + java-version: "17" + cache: "gradle" + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Add build-tools to PATH + run: | + versions=($ANDROID_SDK_ROOT/build-tools/*) + build_tools_path=${versions[0]} + for version in "${versions[@]}"; do + if [ echo "$version" | grep -q 'rc' ]; then + continue + fi + build_tools_path=$version + done + + echo "Build tools path: $build_tools_path" + + echo "$build_tools_path" >> $GITHUB_PATH + + - name: Cache gradle + uses: actions/cache@v3 + with: + path: ./android/.gradle/configuration-cache + key: ${{ env.cache-version }}-gradle-configuration-cache + restore-keys: | + ${{ env.cache-version }}-gradle-configuration-cache + + - name: Install dependencies + run: | + npm ci + npm run mobile:install + + - name: Checkout Product Version Resource + uses: actions/checkout@v3 + with: + repository: VOICEVOX/voicevox_resource + ref: ${{ env.VOICEVOX_RESOURCE_VERSION }} + path: resource + + - name: Create and replace software resources + run: | + rm build/README.txt + rm public/policy.md + { + cat resource/editor/README.md + echo + cat resource/editor/ACKNOWLEDGMENTS.md + } \ + > build/README.txt + cp build/README.txt public/policy.md + + cp resource/editor/PRIVACYPOLICY.md public/privacyPolicy.md + + - name: Replace .env.production infomations + run: | + # GTM ID + gtm_id=$(jq -er '.gtm_container_id' resource/editor/metas.json) + sed -i 's/VITE_GTM_CONTAINER_ID=.*/VITE_GTM_CONTAINER_ID='"$gtm_id"'/' .env.production + + # Engine host + sed -i 's|http://127.0.0.1:50021|local|g' .env.production + + - name: Generate public/licenses.json + run: npm run license:generate -- -o public/licenses.json + + - name: Setup Credentials (Debug) + if: github.event.inputs.code_signing == 'false' + run: | + cp android/build-options.debug.json android/build-options.json + + - name: Setup Credentials (Release) + if: github.event.inputs.code_signing == 'true' + run: | + # メモ: + # ANDROID_BUILD_OPTIONSはbuild-options.jsonを、 + # ANDROID_KEYSTOREはkeystore.jksをbase64エンコードしたものを想定 + cat < android/build-options.json + ${{ secrets.ANDROID_BUILD_OPTIONS }} + EOF + cat < android/keystore.jks + ${{ secrets.ANDROID_KEYSTORE }} + EOF + + - name: Build Android + run: | + chmod +x ./android/gradlew + npm run mobile:build:android -- --androidreleasetype APK --signing-type apksigner + npm run mobile:build:android -- --androidreleasetype AAB --signing-type jarsigner + + - name: Rename + run: | + mkdir /tmp/build_artifacts + cp android/app/build/outputs/bundle/release/app-release-signed.aab /tmp/build_artifacts/VOICEVOX-${{ env.VOICEVOX_EDITOR_VERSION }}.aab + cp android/app/build/outputs/apk/release/app-release-signed.apk /tmp/build_artifacts/VOICEVOX-${{ env.VOICEVOX_EDITOR_VERSION }}.apk + + - name: Upload Android binary to Artifacts + if: github.event.inputs.upload_artifact == 'true' + uses: actions/upload-artifact@v3 + with: + name: android-apk + path: |- + /tmp/build_artifacts/VOICEVOX-${{ env.VOICEVOX_EDITOR_VERSION }}.apk + /tmp/build_artifacts/VOICEVOX-${{ env.VOICEVOX_EDITOR_VERSION }}.aab + + - name: Upload Android binary to Release Assets + if: (github.event.release.tag_name || github.event.inputs.version) != '' + uses: softprops/action-gh-release@v1 + with: + prerelease: ${{ github.event.inputs.prerelease }} + tag_name: ${{ env.VOICEVOX_EDITOR_VERSION }} + files: |- + /tmp/build_artifacts/VOICEVOX-${{ env.VOICEVOX_EDITOR_VERSION }}.apk + /tmp/build_artifacts/VOICEVOX-${{ env.VOICEVOX_EDITOR_VERSION }}.aab + target_commitish: ${{ github.sha }} diff --git a/README.md b/README.md index 27761ea7dc..fb22855f06 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Node.js の管理ツール ([nvs](https://github.com/jasongin/nvs)など)を利 ```bash npm ci +npm run mobile:install ``` また、Android・iOS 開発用のツールも必要です。[Capacitor: 環境設定](https://capacitorjs.jp/docs/getting-started/environment-setup)を参照してください。 @@ -26,13 +27,13 @@ npm ci PC のプライベート IP アドレスは自動で取得されますが、手動で設定する場合は`.env` 内で `CAPACITOR_ADDRESS` を指定してください。 ```bash -npm run cap:serve +npm run mobile:serve ``` または、以下のコマンドで Web 側の変更をビルドし、Capacitor のライブリロードなしで反映します。 ```bash -npm run cap:sync +npm run mobile:sync ``` ### Android 版 @@ -40,7 +41,7 @@ npm run cap:sync 以下のコマンドで Android Studio が起動します。起動後、Android Studio で実行ボタンを押してください。 ```bash -npm run cap:open:android +npm run mobile:open:android ``` [Capacitor Android ドキュメンテーション](https://capacitorjs.jp/docs/android) も参照してください。 @@ -53,14 +54,27 @@ TODO ### Android 版 +`apksigner` に PATH が通っている必要があります。 +`android/build-options.json` に keystore の情報を記述してください。 + +```jsonc +{ + // 相対パスはプロジェクトルートから解決されます。 + "keystorePath": "./android/debug.keystore", + "keystorePassword": "android", + "keystoreAlias": "androiddebugkey", + "keystoreAliasPassword": "android" +} +``` + +セットアップが完了したら以下のコマンドでビルドできます。 + ```bash ANDROID_HOME=/path/to/android-sdk -npm run cap:build:android -- \ - --keystorepath /path/to/keystore.jks \ - --keystorepass Password_here \ - --keystorealias TestKey \ - --keystorealiaspass Password_here +npm run mobile:build:android -- \ + --androidreleasetype APK + # --androidreleasetype AAB ``` ### iOS/iPadOS 版 diff --git a/android/.gitignore b/android/.gitignore index 15068d7ee7..cad3ca1f2a 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -103,3 +103,6 @@ app/src/main/res/xml/config.xml # Assets generated by build/ script app/src/main/assets/*.zip app/src/main/assets/*.sha256 + +# Credentials file +build-options.json diff --git a/android/app/build.gradle b/android/app/build.gradle index c2bcc3ce28..989ea50134 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -5,8 +5,9 @@ android { ndkVersion '25.2.9519653' namespace 'jp.hiroshiba.voicevox' - compileSdkVersion rootProject.ext.compileSdkVersion defaultConfig { + compileSdk rootProject.ext.compileSdkVersion + applicationId "jp.hiroshiba.voicevox" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion @@ -36,11 +37,11 @@ android { } } -repositories { - flatDir { - dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' - } -} +// repositories { +// flatDir { +// dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' +// } +// } def urlZipFile = { name, path, url -> File zipFile = new File("$buildDir/download/${name}.zip") zipFile.parentFile.mkdirs() diff --git a/android/build-options.debug.json b/android/build-options.debug.json new file mode 100644 index 0000000000..56e3282dd9 --- /dev/null +++ b/android/build-options.debug.json @@ -0,0 +1,6 @@ +{ + "keystorePath": "./android/debug.keystore", + "keystorePassword": "android", + "keystoreAlias": "androiddebugkey", + "keystoreAliasPassword": "android" +} diff --git a/android/debug.keystore b/android/debug.keystore new file mode 100644 index 0000000000..a268aa2155 Binary files /dev/null and b/android/debug.keystore differ diff --git a/capacitor.config.ts b/capacitor.config.ts index d1cb12f3fe..92aadb72ff 100644 --- a/capacitor.config.ts +++ b/capacitor.config.ts @@ -1,8 +1,24 @@ /// import { networkInterfaces } from "os"; +import fs from "fs"; +import path from "path"; import dotenv from "dotenv"; import { CapacitorConfig } from "@capacitor/cli"; +let keystorePath = ""; +let keystorePassword = ""; +let keystoreAlias = ""; +let keystoreAliasPassword = ""; +if (fs.existsSync("./android/build-options.json")) { + const buildOptions = JSON.parse( + fs.readFileSync("./android/build-options.json", "utf-8") + ); + keystorePath = path.resolve(buildOptions.keystorePath); + keystorePassword = buildOptions.keystorePassword; + keystoreAlias = buildOptions.keystoreAlias; + keystoreAliasPassword = buildOptions.keystoreAliasPassword; +} + const config: CapacitorConfig = { appId: "jp.hiroshiba.voicevox", appName: "VOICEVOX", @@ -12,6 +28,14 @@ const config: CapacitorConfig = { launchAutoHide: false, }, }, + android: { + buildOptions: { + keystorePath, + keystorePassword, + keystoreAlias, + keystoreAliasPassword, + }, + }, }; if (process.env.CAPACITOR_MODE === "serve") { diff --git a/package.json b/package.json index d8c445ca3b..3a3e4a9107 100644 --- a/package.json +++ b/package.json @@ -28,18 +28,19 @@ "electron:build_pnever": "cross-env VITE_TARGET=electron vite build && electron-builder --config electron-builder.config.js --publish never", "electron:build_pnever_prepackaged": "cross-var electron-builder --config electron-builder.config.js --publish never --prepackaged $PREPACKAGED", "electron:serve": "cross-env VITE_TARGET=electron vite", - "postinstall": "electron-builder install-app-deps && node build/download7z.js && ts-node --transpileOnly build/downloadMobileAssets.mts && node build/generateSpeakerInfo.js", + "postinstall": "electron-builder install-app-deps && node build/download7z.js", "browser:serve": "cross-env VITE_TARGET=browser vite", "browser:build": "cross-env VITE_TARGET=browser vite build", "postuninstall": "electron-builder install-app-deps", "license:generate": "node build/generateLicenses.js", "license:merge": "node build/mergeLicenses.js", - "cap:serve": "cross-env CAPACITOR_MODE=serve cap sync && cross-env VITE_TARGET=mobile vite --host 0.0.0.0", - "cap:sync": "cross-env VITE_TARGET=mobile vite build && cross-env CAPACITOR_MODE=build cap sync", - "cap:build:android": "npm run cap:sync && npx cap build android", - "cap:build:ios": "npm run cap:sync && npx cap build ios", - "cap:open:android": "npx cap open android", - "cap:open:ios": "npx cap open ios", + "mobile:install": "ts-node --transpileOnly build/downloadMobileAssets.mts && node build/generateSpeakerInfo.js", + "mobile:serve": "cross-env CAPACITOR_MODE=serve cap sync && cross-env VITE_TARGET=mobile vite --host 0.0.0.0", + "mobile:sync": "cross-env VITE_TARGET=mobile vite build && cross-env CAPACITOR_MODE=build cap sync", + "mobile:build:android": "npm run mobile:sync && npx cap build android", + "mobile:build:ios": "npm run mobile:sync && npx cap build ios", + "mobile:open:android": "npx cap open android", + "mobile:open:ios": "npx cap open ios", "generate-icons": "npx matex -r ./build/matexRecipes.yml -o public" }, "dependencies": { diff --git a/src/backend/mobile/engine/info.ts b/src/backend/mobile/engine/info.ts index 5d8f66df54..e67d25c759 100644 --- a/src/backend/mobile/engine/info.ts +++ b/src/backend/mobile/engine/info.ts @@ -9,8 +9,7 @@ const infoProvider: ApiProvider = ({ corePlugin }) => { return { async versionVersionGet() { - // 何故か""で囲まれているのを再現。直ったら消す。 - return JSON.stringify(corePlugin.getVersion()); + return await corePlugin.getVersion().then((res) => res.value); }, async engineManifestEngineManifestGet() { if (!engineManifest) {