Files
net.aliasvault.app/apps/mobile-app/android/app/build.gradle
T

371 lines
14 KiB
Groovy

apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
apply plugin: "org.jlleitschuh.gradle.ktlint"
apply plugin: "io.gitlab.arturbosch.detekt"
def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
// Use Expo CLI to bundle the app, this ensures the Metro config
// works correctly with Expo projects.
cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
bundleCommand = "export:embed"
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '../..'
// root = file("../../")
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
// reactNativeDir = file("../../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
// codegenDir = file("../../node_modules/@react-native/codegen")
/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
//
// The path to the CLI configuration file. Default is empty.
// bundleConfig = file(../rn-cli.config.js)
//
// The name of the generated asset file containing your JS bundle
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/MyApplication.android.js")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]
/* Autolinking */
autolinkLibrariesWithApp()
}
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean()
/**
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
android {
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion
namespace 'net.aliasvault.app'
defaultConfig {
applicationId 'net.aliasvault.app'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2700100
versionName "0.27.0-alpha"
// Instrumented test configuration
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
release {
if (project.hasProperty('ALIASVAULT_UPLOAD_STORE_FILE')) {
storeFile file(ALIASVAULT_UPLOAD_STORE_FILE)
storePassword ALIASVAULT_UPLOAD_STORE_PASSWORD
keyAlias ALIASVAULT_UPLOAD_KEY_ALIAS
keyPassword ALIASVAULT_UPLOAD_KEY_PASSWORD
}
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
signingConfig signingConfigs.release
shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true)
}
}
packagingOptions {
jniLibs {
useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false)
}
}
androidResources {
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~'
}
lintOptions {
checkReleaseBuilds true
abortOnError true
xmlReport true
htmlReport true
lintConfig file("lint.xml")
// Focus only on app/src and exclude expo modules
disable 'InvalidPackage'
}
testOptions {
unitTests.all {
useJUnitPlatform()
testLogging {
events "passed", "failed", "skipped"
exceptionFormat "full"
showStandardStreams true
}
}
}
}
// Apply static values from `gradle.properties` to the `android.packagingOptions`
// Accepts values in comma delimited lists, example:
// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini
["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop ->
// Split option: 'foo,bar' -> ['foo', 'bar']
def options = (findProperty("android.packagingOptions.$prop") ?: "").split(",");
// Trim all elements in place.
for (i in 0..<options.size()) options[i] = options[i].trim();
// `[] - ""` is essentially `[""].filter(Boolean)` removing all empty strings.
options -= ""
if (options.length > 0) {
println "android.packagingOptions.$prop += $options ($options.length)"
// Ex: android.packagingOptions.pickFirsts += '**/SCCS/**'
options.each {
android.packagingOptions[prop] += it
}
}
}
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
// Add biometric dependency for credential management
implementation("androidx.biometric:biometric:1.1.0")
// Add Credential Manager dependencies for passkey support (Android 14+)
implementation("androidx.credentials:credentials:1.6.0-beta02")
// Add Material Design components for UI
implementation("com.google.android.material:material:1.11.0")
// Add Fragment and ViewModel dependencies for fragment-based UI
implementation("androidx.fragment:fragment-ktx:1.6.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
// Add vector drawable support for SVG
implementation("com.caverock:androidsvg-aar:1.4")
// Add Argon2 library for password key derivation
implementation("com.lambdapioneer.argon2kt:argon2kt:1.6.0")
// Add modern SQLite library with VACUUM INTO and backup API support
implementation("com.github.requery:sqlite-android:3.49.0")
// Add ZXing library for QR code scanning (F-Droid compatible)
implementation("com.journeyapps:zxing-android-embedded:4.3.0")
// Add JNA library for UniFFI native bindings (Android-specific AAR with native libs)
// Version 5.17.0+ required for Android 15 16KB page size support
// See: https://github.com/java-native-access/jna/issues/1647
implementation("net.java.dev.jna:jna:5.17.0") {
artifact {
type = "aar"
}
}
// Test dependencies (unit tests)
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.0.0'
testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0'
testImplementation 'androidx.test:core:1.5.0'
testImplementation 'androidx.test:runner:1.5.2'
testImplementation 'androidx.test:rules:1.5.0'
testImplementation 'androidx.test.ext:junit:1.1.5'
testImplementation 'org.robolectric:robolectric:4.9'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
testImplementation 'org.junit.vintage:junit-vintage-engine:5.9.2'
// Instrumented test dependencies (androidTest)
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
if (isGifEnabled) {
// For animated gif support
implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}")
}
if (isWebpEnabled) {
// For webp support
implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}")
if (isWebpAnimatedEnabled) {
// Animated webp support
implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}")
}
}
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
implementation "org.jetbrains.kotlin:kotlin-test:1.9.25"
}
ktlint {
android = true
verbose = true
outputToConsole = true
ignoreFailures = false
enableExperimentalRules = true
filter {
// Include all Kotlin and Java files first
include("**/kotlin/**")
include("**/java/**")
// Then exclude generated files
exclude("**/generated/**")
exclude("**/rustcore/uniffi/**") // Exclude UniFFI-generated Rust bindings
exclude("**/uniffi/**") // Exclude any uniffi directory
}
// Configure max line length
kotlinScriptAdditionalPaths {
include(fileTree("scripts/"))
}
// Set max line length to 160
version.set("0.50.0")
}
task lintFormat {
description = "Run all auto-fixers (ktlint and Android lint)"
group = "formatting"
dependsOn 'ktlintFormat'
dependsOn 'detekt'
}
task lintCheck {
description = "Run all linting checks (ktlint, Detekt and Android lint)"
group = "formatting"
dependsOn 'ktlintCheck'
dependsOn 'detekt'
}
// Add a task dependency to make ktlint run before build
tasks.named("preBuild") {
dependsOn("lintFormat")
dependsOn("lintCheck")
}
// Ensure codegen completes before ktlint tasks run
afterEvaluate {
tasks.withType(org.jlleitschuh.gradle.ktlint.tasks.KtLintCheckTask).configureEach {
dependsOn("generateCodegenArtifactsFromSchema")
// Exclude UniFFI-generated Rust bindings
exclude("**/rustcore/uniffi/**/*.kt")
exclude("**/uniffi/**/*.kt")
}
tasks.withType(org.jlleitschuh.gradle.ktlint.tasks.KtLintFormatTask).configureEach {
dependsOn("generateCodegenArtifactsFromSchema")
// Exclude UniFFI-generated Rust bindings
exclude("**/rustcore/uniffi/**/*.kt")
exclude("**/uniffi/**/*.kt")
}
}
detekt {
buildUponDefaultConfig = true
config = files("$rootProject.projectDir/detekt.yml")
baseline = file("$rootProject.projectDir/baseline.xml")
source.setFrom(
fileTree("src/main/java") {
exclude "**/uniffi/**"
}
)
reports {
html.required.set(true)
xml.required.set(true)
txt.required.set(false)
sarif.required.set(false)
}
}
// Rust Core build task - ensures native libraries and Kotlin bindings are up to date
// Uses checksum-based incremental builds to skip if sources haven't changed
task buildRustCore(type: Exec) {
description = "Build Rust Core native libraries and Kotlin bindings"
group = "build"
workingDir = file("${rootProject.projectDir}/scripts")
// Set up environment for cargo
environment "PATH", "${System.getProperty('user.home')}/.cargo/bin:/usr/local/bin:/opt/homebrew/bin:${System.getenv('PATH')}"
// Determine build mode based on variant
def isRelease = gradle.startParameter.taskNames.any { it.toLowerCase().contains('release') }
def buildArgs = isRelease ? ['./build-rust-core.sh', '--release'] : ['./build-rust-core.sh', '--debug']
commandLine buildArgs
// Only log if there are actual changes
ignoreExitValue = false
}
// Make Rust Core build run before Kotlin compilation
tasks.matching { it.name.startsWith('compile') && it.name.contains('Kotlin') }.configureEach {
dependsOn buildRustCore
}