Some checks failed
Android CI Build / check (push) Has been cancelled
Android CI Build / package-apks (push) Has been cancelled
Docs Deploy / build (push) Has been cancelled
Module Market Check / Validate modules/ (push) Has been cancelled
Module Market Publish / Generate submissions.json (push) Has been cancelled
Docs Deploy / deploy (push) Has been cancelled
- Package rename com.webtoapp -> br.com.drukstech.codeapp - Product rename WebToApp/web-to-app -> DruksCode/drukscode - Design system Wta* -> Dkc* - applicationId br.com.drukstech.codeapp (.play for gplay flavor) - rootProject.name DruksCode - Fresh git history (upstream kept as remote for sync)
468 lines
16 KiB
Plaintext
468 lines
16 KiB
Plaintext
plugins {
|
|
id("com.android.application")
|
|
id("org.jetbrains.kotlin.android")
|
|
id("org.jetbrains.kotlin.plugin.compose")
|
|
}
|
|
|
|
import org.gradle.api.DefaultTask
|
|
import org.gradle.api.GradleException
|
|
import org.gradle.api.file.DirectoryProperty
|
|
import org.gradle.api.tasks.Input
|
|
import org.gradle.api.tasks.InputDirectory
|
|
import org.gradle.api.tasks.OutputDirectory
|
|
import org.gradle.api.tasks.PathSensitive
|
|
import org.gradle.api.tasks.PathSensitivity
|
|
import org.gradle.api.tasks.TaskAction
|
|
|
|
android {
|
|
namespace = "br.com.drukstech.codeapp"
|
|
compileSdk = 36
|
|
|
|
defaultConfig {
|
|
applicationId = "br.com.drukstech.codeapp"
|
|
minSdk = 23
|
|
|
|
targetSdk = 28
|
|
versionCode = 62
|
|
versionName = "2.6.0"
|
|
|
|
buildConfigField("boolean", "SHELL_RUNTIME_ONLY", "true")
|
|
|
|
// Shell template never ships to Google Play — it is the runtime host for *generated* apps,
|
|
// which always use targetSdk 28 (fork+exec).
|
|
|
|
vectorDrawables {
|
|
useSupportLibrary = true
|
|
}
|
|
|
|
ndk {
|
|
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
|
}
|
|
|
|
externalNativeBuild {
|
|
cmake {
|
|
cppFlags += "-std=c++17"
|
|
arguments += "-DANDROID_STL=c++_shared"
|
|
}
|
|
}
|
|
}
|
|
|
|
externalNativeBuild {
|
|
cmake {
|
|
path = file("../app/src/main/cpp/CMakeLists.txt")
|
|
version = "3.22.1"
|
|
}
|
|
}
|
|
|
|
signingConfigs {
|
|
getByName("debug")
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
isMinifyEnabled = true
|
|
isShrinkResources = true
|
|
signingConfig = signingConfigs.getByName("debug")
|
|
proguardFiles(
|
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
"proguard-rules.pro"
|
|
)
|
|
}
|
|
}
|
|
|
|
sourceSets {
|
|
getByName("main") {
|
|
manifest.srcFile("src/main/AndroidManifest.xml")
|
|
|
|
// Runtime Kotlin is synced from ../app into build/generated (see
|
|
// syncShellRuntimeSources below) and ordered via preBuild deps —
|
|
// never into the source tree, so git stays clean. Plain string
|
|
// paths here: AGP forbids Provider instances in srcDirs.
|
|
java.srcDirs("src/main/java-overrides", "build/generated/shellRuntimeSrc")
|
|
res.srcDirs("../app/src/main/res")
|
|
assets.srcDirs("src/main/assets", "build/generated/shellRuntimeAssets")
|
|
}
|
|
}
|
|
|
|
splits {
|
|
abi {
|
|
isEnable = false
|
|
}
|
|
}
|
|
|
|
bundle {
|
|
language {
|
|
enableSplit = false
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
kotlinOptions {
|
|
jvmTarget = "17"
|
|
}
|
|
|
|
buildFeatures {
|
|
compose = true
|
|
buildConfig = true
|
|
}
|
|
|
|
lint {
|
|
|
|
disable += "NullSafeMutableLiveData"
|
|
|
|
disable += "ExpiredTargetSdkVersion"
|
|
disable += "ExpiringTargetSdkVersion"
|
|
disable += "OldTargetApi"
|
|
abortOnError = false
|
|
}
|
|
|
|
packaging {
|
|
resources {
|
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
|
excludes += "assets/omni.ja"
|
|
excludes += "**/omni.ja"
|
|
excludes += "**/org/bouncycastle/pqc/**"
|
|
}
|
|
jniLibs {
|
|
useLegacyPackaging = true
|
|
|
|
excludes += "**/libxul.so"
|
|
excludes += "**/libmozglue.so"
|
|
excludes += "**/libgeckoffi.so"
|
|
excludes += "**/libmozavutil.so"
|
|
excludes += "**/libmozavcodec.so"
|
|
|
|
excludes += "**/libgkcodecs.so"
|
|
excludes += "**/libminidump_analyzer.so"
|
|
excludes += "**/libnss3.so"
|
|
excludes += "**/libfreebl3.so"
|
|
excludes += "**/libsoftokn3.so"
|
|
excludes += "**/liblgpllibs.so"
|
|
excludes += "**/libplugin-container.so"
|
|
|
|
excludes += "**/libphp.so"
|
|
|
|
// Host-preview-only user-mode exec loader; generated APKs
|
|
// (targetSdk 28) always execve directly and never load it.
|
|
excludes += "**/libstatic_exec.so"
|
|
|
|
// Cronet natives are injected into exported APKs by ApkBuilder when
|
|
// 强制 HTTP/3 is enabled; the template never carries them.
|
|
excludes += "**/libcronet*.so"
|
|
}
|
|
}
|
|
androidResources {
|
|
ignoreAssetsPattern = ""
|
|
|
|
localeFilters += listOf("zh", "en", "ar")
|
|
}
|
|
}
|
|
|
|
val syncShellRuntimeSources by tasks.registering(Sync::class) {
|
|
description = "Sync runtime-only Kotlin sources from app module to shell"
|
|
group = "build"
|
|
|
|
from("../app/src/main/java")
|
|
|
|
include(
|
|
|
|
"**/ui/shell/**",
|
|
"**/ui/theme/**",
|
|
"**/ui/shared/**",
|
|
"**/ui/design/**",
|
|
|
|
"**/core/shell/**",
|
|
"**/core/activation/**",
|
|
"**/core/announcement/**",
|
|
"**/core/adblock/**",
|
|
"**/core/webview/**",
|
|
"**/core/crypto/**",
|
|
"**/core/i18n/**",
|
|
"**/core/logging/**",
|
|
"**/core/dns/**",
|
|
"**/core/floatingwindow/**",
|
|
"**/core/privacy/**",
|
|
"**/core/appearance/**",
|
|
"**/core/perf/**",
|
|
"**/core/port/**",
|
|
"**/core/extension/**",
|
|
"**/core/notification/**",
|
|
"**/core/bgm/**",
|
|
"**/core/engine/**",
|
|
"**/core/scraper/**",
|
|
"**/core/script/**",
|
|
"**/core/ads/**",
|
|
"**/core/network/**",
|
|
"**/core/errorpage/**",
|
|
"**/core/golang/**",
|
|
"**/core/python/**",
|
|
"**/core/nodejs/**",
|
|
"**/core/php/**",
|
|
"**/core/wordpress/**",
|
|
"**/core/autostart/**",
|
|
"**/core/background/**",
|
|
"**/core/linux/**",
|
|
"**/core/download/**",
|
|
"**/core/sample/**",
|
|
"**/core/frontend/**",
|
|
"**/core/kernel/**",
|
|
|
|
"br/com/drukstech/codeapp/data/model/**",
|
|
"br/com/drukstech/codeapp/data/converter/**",
|
|
|
|
"**/ui/components/announcement/AnnouncementTemplates.kt",
|
|
"**/ui/components/PremiumComponents.kt",
|
|
"**/ui/components/EnhancedActivationDialog.kt",
|
|
"**/ui/components/WebSwipeRefreshLayout.kt",
|
|
"**/ui/components/VirtualNavigationBar.kt",
|
|
"**/ui/components/StatusBarBackground.kt",
|
|
"**/ui/components/LongPressMenu.kt",
|
|
"**/ui/components/AutoRefreshCountdownOverlay.kt",
|
|
|
|
"**/util/**"
|
|
)
|
|
|
|
exclude(
|
|
|
|
"**/DruksCodeApplication.kt",
|
|
|
|
"**/core/crypto/EncryptedApkBuilder.kt",
|
|
"**/core/crypto/SecurityInitializer.kt",
|
|
|
|
"**/core/autostart/AutoStartLauncher.kt",
|
|
"**/core/autostart/BootReceiver.kt",
|
|
"**/core/autostart/ScheduledStartReceiver.kt",
|
|
|
|
"**/util/FaviconFetcher.kt",
|
|
"**/util/UrlMetadataFetcher.kt",
|
|
"**/util/MediaStorage.kt",
|
|
"**/util/ZipProjectImporter.kt",
|
|
"**/util/HtmlProjectHelper.kt",
|
|
"**/util/OfflineManager.kt",
|
|
|
|
"**/core/frontend/GitHubRepoFetcher.kt",
|
|
|
|
"**/core/extension/QrCodeUtils.kt",
|
|
"**/core/extension/CodeSnippets.kt",
|
|
"**/core/extension/ModuleTemplates.kt",
|
|
"**/core/extension/DebugTestPages.kt",
|
|
"**/core/extension/ModulePreset.kt"
|
|
)
|
|
|
|
into(layout.buildDirectory.dir("generated/shellRuntimeSrc"))
|
|
}
|
|
|
|
// Explicit wiring (no tasks.matching scan, which breaks configuration cache):
|
|
// generated sources must exist before any compilation.
|
|
tasks.named("preBuild") { dependsOn(syncShellRuntimeSources) }
|
|
|
|
val syncShellRuntimeAssets by tasks.registering(Copy::class) {
|
|
description = "Mirror runtime-only asset files from app module to shell template (single source of truth: app/src/main/assets)."
|
|
group = "build"
|
|
|
|
from("../app/src/main/assets") {
|
|
|
|
include("php_router_server.php")
|
|
|
|
// GeckoViewEngine installs this built-in WebExtension from
|
|
// resource://android/assets/web_extensions/dkc_native_bridge/ when the CORS
|
|
// bypass / private-network bridge is enabled — without the asset in the shell
|
|
// template, exported Gecko apps silently fail to install it (host-only asset).
|
|
include("web_extensions/**")
|
|
}
|
|
|
|
into(layout.buildDirectory.dir("generated/shellRuntimeAssets"))
|
|
}
|
|
|
|
tasks.named("preBuild") { dependsOn(syncShellRuntimeAssets) }
|
|
|
|
// omni.ja arrives via the GeckoView AAR's assets and packaging.resources.excludes
|
|
// does not cover AAR assets, so strip it from the merged dir. Named wiring +
|
|
// execution-time-only access keeps this configuration-cache safe; the merge
|
|
// task stays up-to-date-aware (a skipped merge means outputs already stripped).
|
|
// Lazy exact-name match: the merge task is created after configuration, so
|
|
// named() would fail. The closure touches only task-scoped state.
|
|
tasks.matching { it.name == "mergeReleaseAssets" }.configureEach {
|
|
doLast {
|
|
val mergedAssetsDir = outputs.files.files.firstOrNull { it.isDirectory }
|
|
val omniJa = mergedAssetsDir?.resolve("omni.ja")
|
|
if (omniJa != null && omniJa.exists()) {
|
|
val sizeKb = omniJa.length() / 1024
|
|
if (omniJa.delete()) {
|
|
logger.lifecycle("[shell-slim] Removed bundled GeckoView omni.ja from template assets (${sizeKb} KB)")
|
|
} else {
|
|
logger.warn("[shell-slim] Failed to remove omni.ja from $mergedAssetsDir")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
abstract class SyncNativeExecutableJniLibsTask : DefaultTask() {
|
|
@get:Input
|
|
abstract val variantName: org.gradle.api.provider.Property<String>
|
|
|
|
@get:Input
|
|
abstract val buildTypeName: org.gradle.api.provider.Property<String>
|
|
|
|
@get:Input
|
|
abstract val executableName: org.gradle.api.provider.Property<String>
|
|
|
|
@get:Input
|
|
abstract val packagedLibraryName: org.gradle.api.provider.Property<String>
|
|
|
|
@get:InputDirectory
|
|
@get:PathSensitive(PathSensitivity.RELATIVE)
|
|
abstract val cxxRoot: DirectoryProperty
|
|
|
|
@get:OutputDirectory
|
|
abstract val outputDir: DirectoryProperty
|
|
|
|
@TaskAction
|
|
fun sync() {
|
|
val cxxRootDir = cxxRoot.asFile.get()
|
|
if (!cxxRootDir.exists()) {
|
|
throw GradleException("CXX output not found for ${variantName.get()}: ${cxxRootDir.absolutePath}")
|
|
}
|
|
|
|
val executableTargets = cxxRootDir.walkTopDown()
|
|
.filter { file ->
|
|
file.isFile &&
|
|
file.name == executableName.get() &&
|
|
file.parentFile?.parentFile?.name == "obj"
|
|
}
|
|
.toList()
|
|
|
|
if (executableTargets.isEmpty()) {
|
|
throw GradleException("${executableName.get()} artifacts not found for ${variantName.get()} under ${cxxRootDir.absolutePath}")
|
|
}
|
|
|
|
val outputRoot = outputDir.get().asFile
|
|
outputRoot.deleteRecursively()
|
|
outputRoot.mkdirs()
|
|
|
|
executableTargets.forEach { binary ->
|
|
val abi = binary.parentFile.name
|
|
val destFile = outputRoot.resolve("$abi/${packagedLibraryName.get()}")
|
|
destFile.parentFile.mkdirs()
|
|
binary.copyTo(destFile, overwrite = true)
|
|
destFile.setExecutable(true, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
androidComponents {
|
|
onVariants { variant ->
|
|
val capName = variant.name.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
|
|
val variantBuildTypeName = variant.buildType ?: "release"
|
|
val cxxBuildType = if (variantBuildTypeName.equals("debug", ignoreCase = true)) "Debug" else "RelWithDebInfo"
|
|
val nativeBuildTaskName = "buildCMake$cxxBuildType"
|
|
val syncNodeLauncherTask = tasks.register<SyncNativeExecutableJniLibsTask>("syncNodeLauncherJniLibs$capName") {
|
|
group = "build"
|
|
description = "Copies ABI-specific node launcher executables into generated jniLibs for ${variant.name}."
|
|
variantName.set(variant.name)
|
|
buildTypeName.set(variantBuildTypeName)
|
|
executableName.set("node_launcher")
|
|
packagedLibraryName.set("libnode_launcher.so")
|
|
cxxRoot.set(layout.buildDirectory.dir("intermediates/cxx/$cxxBuildType"))
|
|
outputDir.set(layout.buildDirectory.dir("generated/jniLibs/nodeLauncher/${variant.name}"))
|
|
dependsOn(nativeBuildTaskName)
|
|
}
|
|
|
|
val syncGoLoaderTask = tasks.register<SyncNativeExecutableJniLibsTask>("syncGoExecLoaderJniLibs$capName") {
|
|
group = "build"
|
|
description = "Copies ABI-specific Go exec loader executables into generated jniLibs for ${variant.name}."
|
|
variantName.set(variant.name)
|
|
buildTypeName.set(variantBuildTypeName)
|
|
executableName.set("go_exec_loader")
|
|
packagedLibraryName.set("libgo_exec_loader.so")
|
|
cxxRoot.set(layout.buildDirectory.dir("intermediates/cxx/$cxxBuildType"))
|
|
outputDir.set(layout.buildDirectory.dir("generated/jniLibs/goExecLoader/${variant.name}"))
|
|
dependsOn(nativeBuildTaskName)
|
|
}
|
|
|
|
// NOTE: no explicit merge${capName}NativeLibs wiring — the
|
|
// addGeneratedSourceDirectory calls below already infer task deps.
|
|
|
|
variant.sources.jniLibs?.addGeneratedSourceDirectory(
|
|
syncNodeLauncherTask,
|
|
SyncNativeExecutableJniLibsTask::outputDir
|
|
)
|
|
variant.sources.jniLibs?.addGeneratedSourceDirectory(
|
|
syncGoLoaderTask,
|
|
SyncNativeExecutableJniLibsTask::outputDir
|
|
)
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
|
|
implementation("androidx.core:core-ktx:1.12.0")
|
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
|
implementation("androidx.core:core-splashscreen:1.0.1")
|
|
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
|
implementation("androidx.documentfile:documentfile:1.0.1")
|
|
|
|
// Native Google sign-in through the Jetpack Credential Manager (NativeBridge).
|
|
implementation("androidx.credentials:credentials:1.5.0")
|
|
implementation("androidx.credentials:credentials-play-services-auth:1.5.0")
|
|
implementation("com.google.android.libraries.identity.googleid:googleid:1.1.1")
|
|
|
|
implementation("com.google.android.material:material:1.10.0")
|
|
implementation("androidx.activity:activity-compose:1.8.1")
|
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
|
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")
|
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
|
|
|
|
implementation(platform("androidx.compose:compose-bom:2026.06.01"))
|
|
implementation("androidx.compose.ui:ui")
|
|
implementation("androidx.compose.ui:ui-graphics")
|
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
|
implementation("androidx.compose.material3:material3:1.5.0-alpha17")
|
|
implementation("androidx.compose.material:material-icons-extended:1.7.8")
|
|
implementation("androidx.navigation:navigation-compose:2.7.5")
|
|
|
|
implementation("androidx.room:room-runtime:2.7.2")
|
|
implementation("androidx.room:room-ktx:2.7.2")
|
|
|
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
|
|
|
implementation("io.coil-kt:coil-compose:2.5.0")
|
|
|
|
implementation("com.google.code.gson:gson:2.10.1")
|
|
|
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
|
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.12.0")
|
|
|
|
implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
|
|
implementation("com.google.firebase:firebase-messaging")
|
|
|
|
implementation("org.bouncycastle:bcpkix-jdk15to18:1.78.1")
|
|
implementation("org.bouncycastle:bcprov-jdk15to18:1.78.1")
|
|
|
|
implementation("io.insert-koin:koin-android:3.5.3")
|
|
implementation("io.insert-koin:koin-androidx-compose:3.5.3")
|
|
|
|
implementation("androidx.webkit:webkit:1.9.0")
|
|
|
|
implementation("androidx.datastore:datastore-preferences:1.0.0")
|
|
|
|
implementation("androidx.security:security-crypto:1.1.0-alpha06")
|
|
|
|
implementation("org.apache.commons:commons-compress:1.26.0")
|
|
implementation("org.tukaani:xz:1.9")
|
|
|
|
implementation("org.mozilla.geckoview:geckoview-arm64-v8a:142.0.20250827004350")
|
|
|
|
// Forced HTTP/3 upstream (see app/build.gradle.kts): classes only, natives are
|
|
// injected into exported APKs by ApkBuilder when 强制 HTTP/3 is enabled.
|
|
implementation("org.chromium.net:cronet-embedded:143.7445.0")
|
|
|
|
implementation("androidx.browser:browser:1.8.0")
|
|
|
|
implementation("androidx.media:media:1.7.0")
|
|
}
|