2022-02-14 10:19:20 +01:00
/ * *
* Build script for android app .
*
* Besides options below this script may require signing parameters passed as environment variables :
* 'APK_SIGN_ALIAS'
* 'APK_SIGN_STORE_PASS'
* 'APK_SIGN_KEY_PASS'
* 'APK_SIGN_STORE'
* 'ANDROID_HOME'
* /
2024-07-29 11:33:23 +02:00
import { Argument , Option , program } from "commander"
import { runDevBuild } from "./buildSrc/DevBuild.js"
import { prepareMobileBuild } from "./buildSrc/prepareMobileBuild.js"
import { buildWebapp } from "./buildSrc/buildWebapp.js"
import { getTutanotaAppVersion , measure } from "./buildSrc/buildUtils.js"
2023-04-20 17:14:30 +02:00
import path from "node:path"
2024-07-29 11:33:23 +02:00
import { $ , cd } from "zx"
2021-01-29 10:38:21 +01:00
2022-07-15 09:56:16 +02:00
const log = ( ... messages ) => console . log ( chalk . green ( "\nBUILD:" ) , ... messages , "\n" )
2022-02-14 10:19:20 +01:00
2022-04-28 14:36:24 +02:00
await program
2022-12-27 15:37:40 +01:00
. usage ( "[options] [test|prod|local|host <url>] " )
2025-07-14 15:01:28 +02:00
. addArgument (
new Argument ( "stage" , "the server to connect to. test/local/prod are shorthands for using host <url> of the corresponding staging level server" )
. choices ( [ "test" , "prod" , "local" , "host" ] )
. default ( "prod" )
. argOptional ( ) ,
)
2022-04-28 14:36:24 +02:00
. addArgument ( new Argument ( "host" ) . argOptional ( ) )
2024-07-16 16:54:03 +02:00
. addOption ( new Option ( "-a, --app <type>" , "app to build" ) . choices ( [ "mail" , "calendar" ] ) . default ( "mail" ) )
2025-07-14 15:01:28 +02:00
. addOption (
new Option (
"-b, --buildtype <type>" ,
"gradle build type. use debug if you need to debug the app with android studio. release and releaseTest build the same app with different appIds for side-by-side installation" ,
)
. choices ( [ "debug" , "release" , "releaseTest" ] )
. default ( "release" ) ,
)
. addOption ( new Option ( "-i, --install" , "call adb install after build to deploy the app to a device/emulator" ) )
. addOption (
new Option (
"-w --webclient <client>" ,
"choose web client build. make is faster and easier to debug, but dist is what would be running in production. There's usually no reason to use dist during development." ,
)
. choices ( [ "make" , "dist" ] )
. default ( "dist" ) ,
)
. option ( "-e, --existing" , "Use existing prebuilt web client files to skip the lengthy web client build. Use if you're developing the Kotlin code." )
2024-07-29 11:33:23 +02:00
. action ( async ( stage , host , { webclient , buildtype , install , existing , app } ) => {
2022-12-27 15:37:40 +01:00
if ( ( stage === "host" && host == null ) || ( stage !== "host" && host != null ) ) {
2022-04-28 14:36:24 +02:00
program . outputHelp ( )
2021-11-05 17:11:35 +01:00
process . exit ( 1 )
}
2022-07-15 09:56:16 +02:00
const apk = await buildAndroid ( {
2022-12-27 15:37:40 +01:00
stage : stage ? ? "prod" ,
2021-11-05 17:11:35 +01:00
host : host ,
webClient : webclient ,
2022-07-28 11:33:09 +02:00
existing ,
2021-11-05 17:11:35 +01:00
buildType : buildtype ,
2024-07-16 16:54:03 +02:00
app ,
2021-11-05 17:11:35 +01:00
} )
2022-07-15 09:56:16 +02:00
2022-07-21 16:57:21 +02:00
if ( install ) {
2022-07-15 09:56:16 +02:00
await $ ` adb install ${ apk } `
2025-07-14 15:01:28 +02:00
// would be cool to auto-start the app, but needs to figure out the correct app to start:
2022-07-15 09:56:16 +02:00
// await $`adb shell am start -n de.tutao.tutanota/de.tutao.tutanota.MainActivity`
}
2021-11-05 17:11:35 +01:00
} )
2022-04-28 14:36:24 +02:00
. parseAsync ( process . argv )
2019-02-01 11:42:51 +01:00
2024-09-27 09:20:09 +02:00
async function buildCalendarBundle ( { buildType } ) {
const { version } = JSON . parse ( await $ ` cat package.json ` . quiet ( ) )
const bundleName = ` calendar-tutao- ${ buildType } - ${ version } .aab `
const bundlePath = ` app-android/calendar/build/outputs/bundle/tutao ${ buildType . charAt ( 0 ) . toUpperCase ( ) + buildType . slice ( 1 ) } / ${ bundleName } `
const outPath = ` ./build-calendar-app/app-android/ ${ bundleName } `
cd ( "./app-android" )
await $ ` ./gradlew :calendar:bundleTutao ${ buildType } `
cd ( ".." )
await $ ` mkdir -p build-calendar-app/app-android `
await $ ` mv ${ bundlePath } ${ outPath } `
log ( ` Build complete. The AAB is located at: ${ outPath } ` )
return outPath
}
2024-09-30 14:18:59 +02:00
async function buildCalendarApk ( { buildType } ) {
const { version } = JSON . parse ( await $ ` cat package.json ` . quiet ( ) )
2024-10-09 15:06:12 +02:00
const bundleName = ` calendar-tutao- ${ buildType } - ${ version } `
2024-09-30 14:18:59 +02:00
const bundlePath = ` app-android/calendar/build/outputs/apk/tutao/ ${ buildType } / ${ bundleName } `
const outPath = ` ./build-calendar-app/app-android/ ${ bundleName } `
cd ( "./app-android" )
2024-10-09 15:06:12 +02:00
await $ ` if [ -f . ${ outPath } .aab ]; then mkdir ../temp; mv . ${ outPath } .aab ../temp/ ${ bundleName } .aab; fi `
2024-09-30 14:18:59 +02:00
await $ ` ./gradlew :calendar:assembleTutao ${ buildType } `
cd ( ".." )
await $ ` mkdir -p build-calendar-app/app-android `
2024-10-09 15:06:12 +02:00
await $ ` mv ${ bundlePath } .apk ${ outPath } .apk `
await $ ` if [ -f ./temp/ ${ bundleName } .aab ]; then mv ./temp/ ${ bundleName } .aab ${ outPath } .aab; rm -d ./temp; fi `
2024-09-30 14:18:59 +02:00
log ( ` Build complete. The APK is located at: ${ outPath } ` )
return outPath
}
2024-09-27 09:20:09 +02:00
async function buildMailApk ( { buildType } ) {
const { version } = JSON . parse ( await $ ` cat package.json ` . quiet ( ) )
const apkName = ` tutanota-app-tutao- ${ buildType } - ${ version } .apk `
const apkPath = ` app-android/app/build/outputs/apk/tutao/ ${ buildType } / ${ apkName } `
const outPath = ` ./build/app-android/ ${ apkName } `
cd ( "./app-android" )
await $ ` ./gradlew :app:assembleTutao ${ buildType } `
cd ( ".." )
await $ ` mkdir -p build/app-android `
await $ ` mv ${ apkPath } ${ outPath } `
log ( ` Build complete. The APK is located at: ${ outPath } ` )
return outPath
}
2024-07-29 11:33:23 +02:00
async function buildAndroid ( { stage , host , buildType , existing , webClient , app } ) {
2022-04-28 14:36:24 +02:00
log ( ` Starting ${ stage } build with build type: ${ buildType } , webclient: ${ webClient } , host: ${ host } ` )
2025-01-31 14:24:06 +01:00
2022-07-28 11:33:09 +02:00
if ( ! existing ) {
if ( webClient === "make" ) {
await runDevBuild ( {
2022-02-14 10:19:20 +01:00
stage ,
host ,
2022-07-28 11:33:09 +02:00
desktop : false ,
clean : false ,
watch : false ,
2022-12-27 15:37:40 +01:00
serve : false ,
2025-03-10 16:19:11 +01:00
networkDebugging : false ,
2024-07-29 11:33:23 +02:00
app ,
2022-07-28 11:33:09 +02:00
} )
} else {
2023-04-19 13:58:49 +02:00
const version = await getTutanotaAppVersion ( )
2022-12-27 15:37:40 +01:00
await buildWebapp ( {
version ,
stage ,
host ,
minify : true ,
projectDir : path . resolve ( "." ) ,
measure ,
2024-07-29 11:33:23 +02:00
app ,
2025-08-28 16:40:25 +02:00
mobileBuild : true ,
2022-12-27 15:37:40 +01:00
} )
2022-07-28 11:33:09 +02:00
}
} else {
console . log ( "skipped webapp build" )
2021-11-05 17:11:35 +01:00
}
2018-12-11 14:55:06 +01:00
2025-07-21 15:26:41 +02:00
await prepareMobileBuild ( { app } )
2024-07-30 10:57:18 +02:00
const buildDir = app === "mail" ? "build" : "build-calendar-app"
2021-06-28 13:03:54 +02:00
try {
2024-07-30 10:57:18 +02:00
await $ ` rm -r ${ buildDir } /app-android `
2021-06-28 13:03:54 +02:00
} catch ( e ) {
// Ignoring the error if the folder is not there
2018-12-11 14:55:06 +01:00
}
2024-09-27 09:20:09 +02:00
if ( app === "mail" ) {
return await buildMailApk ( { buildType } )
} else {
2024-09-30 14:18:59 +02:00
await buildCalendarBundle ( { buildType } )
return await buildCalendarApk ( { buildType } )
2024-09-27 09:20:09 +02:00
}
2022-12-27 15:37:40 +01:00
}