6.7.0 - Alpha6 - 模块化 Gradle 脚本, 将共享构建逻辑迁移至 buildSrc 并抽象为约定插件
This commit is contained in:
1 parent
038f4a5ad9
commit
4778794e0e
31 files changed
+1893
-1235
No files matched your search
@@ -36,7 +36,8 @@
|
|||||||
"崩溃报告页面支持双指缩放调整字体大小并添加常用功能按钮",
|
"崩溃报告页面支持双指缩放调整字体大小并添加常用功能按钮",
|
||||||
"应用启动器图标支持自适应图标特性 _[`issue #405`](http://issues.autojs6.com/405)_",
|
"应用启动器图标支持自适应图标特性 _[`issue #405`](http://issues.autojs6.com/405)_",
|
||||||
"Gradle 构建脚本提升 7z 格式文件的解压效率",
|
"Gradle 构建脚本提升 7z 格式文件的解压效率",
|
||||||
"使用版本目录 (Version Catalogs) 集中管理 Gradle 依赖和插件版本"
|
"使用版本目录 (Version Catalogs) 集中管理 Gradle 依赖和插件版本",
|
||||||
|
"模块化 Gradle 脚本, 将共享构建逻辑迁移至 buildSrc 并抽象为约定插件"
|
||||||
],
|
],
|
||||||
"dependency": [
|
"dependency": [
|
||||||
"本地化 Root Shell 版本 1.6",
|
"本地化 Root Shell 版本 1.6",
|
||||||
|
|||||||
+97
-362
@@ -3,18 +3,19 @@
|
|||||||
import com.android.build.gradle.internal.api.ApplicationVariantImpl
|
import com.android.build.gradle.internal.api.ApplicationVariantImpl
|
||||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||||
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
|
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import java.util.Locale.getDefault
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
|
||||||
import java.util.zip.CRC32
|
|
||||||
import kotlin.text.RegexOption.IGNORE_CASE
|
import kotlin.text.RegexOption.IGNORE_CASE
|
||||||
|
|
||||||
val globalApplicationId = "org.autojs.autojs6"
|
plugins {
|
||||||
|
id("org.autojs.build.utils")
|
||||||
|
id("org.autojs.build.versions")
|
||||||
|
id("org.autojs.build.signs")
|
||||||
|
id("com.android.application")
|
||||||
|
id("com.google.devtools.ksp")
|
||||||
|
id("org.jetbrains.kotlin.android") /* kotlin("android") */
|
||||||
|
}
|
||||||
|
|
||||||
val sign = Sign("$rootDir/sign.properties")
|
val globalApplicationId = "org.autojs.autojs6"
|
||||||
val versions = Versions("$rootDir/version.properties")
|
|
||||||
|
|
||||||
val flavorDimension = "channel"
|
val flavorDimension = "channel"
|
||||||
val flavorNameApp = "app"
|
val flavorNameApp = "app"
|
||||||
@@ -22,18 +23,13 @@ val flavorNameInrt = "inrt"
|
|||||||
val buildTypeDebug = "debug"
|
val buildTypeDebug = "debug"
|
||||||
val buildTypeRelease = "release"
|
val buildTypeRelease = "release"
|
||||||
val buildActionAssemble = "assemble"
|
val buildActionAssemble = "assemble"
|
||||||
val templateName = "template"
|
|
||||||
|
|
||||||
val taskNames = gradle.startParameter.taskNames
|
val taskNames = gradle.startParameter.taskNames
|
||||||
val isAppAssembleTaskRequested = taskNames.any { it.contains(Regex("^(:?$flavorNameApp:)?$buildActionAssemble", IGNORE_CASE)) }
|
val isAppAssembleTaskRequested = taskNames.any { it.contains(Regex("^(:?$flavorNameApp:)?$buildActionAssemble", IGNORE_CASE)) }
|
||||||
val isInrtAssembleTaskRequested = taskNames.any { it.contains(Regex("^(:?$flavorNameApp:)?$buildActionAssemble$flavorNameInrt", IGNORE_CASE)) }
|
val isInrtAssembleTaskRequested = taskNames.any { it.contains(Regex("^(:?$flavorNameApp:)?$buildActionAssemble$flavorNameInrt", IGNORE_CASE)) }
|
||||||
val isInrtTaskRequested = taskNames.any { it.contains(flavorNameInrt, true) }
|
val isInrtTaskRequested = taskNames.any { it.contains(flavorNameInrt, true) }
|
||||||
|
|
||||||
plugins {
|
utils.registerTemplateApkCopy(project)
|
||||||
id("com.android.application")
|
|
||||||
id("com.google.devtools.ksp")
|
|
||||||
id("org.jetbrains.kotlin.android") /* kotlin("android") */
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies /* Unclassified */ {
|
dependencies /* Unclassified */ {
|
||||||
// Compose
|
// Compose
|
||||||
@@ -52,67 +48,66 @@ dependencies /* Unclassified */ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LeakCanary
|
// LeakCanary
|
||||||
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
|
debugImplementation(libs.leakcanary)
|
||||||
|
|
||||||
// Android supports
|
// Android supports
|
||||||
implementation("androidx.cardview:cardview:1.0.0")
|
implementation(libs.cardview)
|
||||||
implementation("androidx.multidex:multidex:2.0.1")
|
implementation(libs.multidex)
|
||||||
|
|
||||||
// Material Components
|
// Material Components
|
||||||
implementation("com.google.android.material:material:1.12.0")
|
implementation(libs.material)
|
||||||
|
|
||||||
// SwipeRefreshLayout
|
// SwipeRefreshLayout
|
||||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
implementation(libs.swiperefreshlayout)
|
||||||
|
|
||||||
// ConstraintLayout
|
// ConstraintLayout
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
implementation(libs.constraintlayout)
|
||||||
|
|
||||||
// FlexboxLayout
|
// FlexboxLayout
|
||||||
implementation("com.google.android.flexbox:flexbox:3.0.0")
|
implementation(libs.flexbox)
|
||||||
|
|
||||||
// Common Markdown
|
// Common Markdown
|
||||||
implementation("com.github.atlassian:commonmark-java:commonmark-parent-0.9.0")
|
implementation(libs.commonmark)
|
||||||
|
|
||||||
// Flexmark Java HTML to Markdown Extensible Converter
|
// Flexmark Java HTML to Markdown Extensible Converter
|
||||||
implementation("com.vladsch.flexmark:flexmark-html2md-converter:0.64.8")
|
implementation(libs.flexmark.html2md)
|
||||||
|
|
||||||
// Licenses Dialog
|
// Licenses Dialog
|
||||||
implementation("de.psdev.licensesdialog:licensesdialog:2.2.0")
|
implementation(libs.licensesdialog)
|
||||||
|
|
||||||
// Apache Commons
|
// Apache Commons
|
||||||
implementation("org.apache.commons:commons-lang3:3.18.0")
|
implementation(libs.commons.lang3)
|
||||||
|
|
||||||
// Retrofit
|
// Retrofit
|
||||||
implementation("com.squareup.retrofit2:retrofit:2.12.0")
|
implementation(libs.retrofit)
|
||||||
implementation("com.squareup.retrofit2:converter-gson:2.12.0")
|
implementation(libs.retrofit.converter.gson)
|
||||||
implementation("com.squareup.retrofit2:adapter-rxjava2:2.12.0")
|
implementation(libs.retrofit.adapter.rxjava2)
|
||||||
implementation("com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2")
|
implementation(libs.retrofit2.kotlin.coroutines.adapter)
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
implementation("com.github.bumptech.glide:glide:4.16.0")
|
implementation(libs.glide)
|
||||||
ksp("com.github.bumptech.glide:ksp:4.16.0")
|
ksp(libs.glide.ksp)
|
||||||
|
|
||||||
// Joda Time
|
// Joda Time
|
||||||
implementation("joda-time:joda-time:2.14.0")
|
implementation(libs.joda.time)
|
||||||
|
|
||||||
// Flurry
|
// Analytics
|
||||||
implementation("com.flurry.android:analytics:14.4.0")
|
implementation(libs.analytics)
|
||||||
|
|
||||||
// Bugly
|
// Bugly
|
||||||
implementation(project(":libs:com.tencent.bugly.crashreport-4.0.4"))
|
implementation(project(":libs:com.tencent.bugly.crashreport-4.0.4"))
|
||||||
|
|
||||||
// OkHttp
|
// OkHttp
|
||||||
// implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.12")
|
implementation(libs.okhttp)
|
||||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
|
||||||
|
|
||||||
// Webkit
|
// Webkit
|
||||||
implementation("androidx.webkit:webkit:1.14.0")
|
implementation(libs.webkit)
|
||||||
|
|
||||||
// Gson
|
// Gson
|
||||||
implementation("com.google.code.gson:gson:2.13.1")
|
implementation(libs.gson)
|
||||||
|
|
||||||
// Zip4j
|
// Zip4j
|
||||||
implementation("net.lingala.zip4j:zip4j:2.11.5")
|
implementation(libs.zip4j)
|
||||||
|
|
||||||
// Log4j
|
// Log4j
|
||||||
// FIXME by SuperMonster003 on Aug 14, 2024.
|
// FIXME by SuperMonster003 on Aug 14, 2024.
|
||||||
@@ -135,30 +130,30 @@ dependencies /* Unclassified */ {
|
|||||||
// ! - CVE-2019-17571, 评分: 9.8
|
// ! - CVE-2019-17571, 评分: 9.8
|
||||||
// ! 但 log4j 第二版本要求安卓 API 级别不低于 26,
|
// ! 但 log4j 第二版本要求安卓 API 级别不低于 26,
|
||||||
// ! 与最低 API 级别为 24 的当前项目无法兼容.
|
// ! 与最低 API 级别为 24 的当前项目无法兼容.
|
||||||
implementation("log4j:log4j:1.2.17")
|
implementation(libs.log4j)
|
||||||
|
|
||||||
// Android Logging Log4j
|
// Android Logging Log4j
|
||||||
implementation("de.mindpipe.android:android-logging-log4j:1.0.3")
|
implementation(libs.android.logging.log4j)
|
||||||
|
|
||||||
// Preference
|
// Preference
|
||||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
implementation(libs.preference.ktx)
|
||||||
|
|
||||||
// RootShell
|
// RootShell
|
||||||
// implementation("com.github.Stericson:RootShell:1.6")
|
// implementation("com.github.Stericson:RootShell:1.6")
|
||||||
implementation(project(":libs:root-shell-1.6"))
|
implementation(project(":libs:root-shell-1.6"))
|
||||||
|
|
||||||
// JDeferred
|
// JDeferred
|
||||||
implementation("org.jdeferred:jdeferred-android-aar:1.2.6")
|
implementation(libs.jdeferred)
|
||||||
|
|
||||||
// Rx
|
// Rx
|
||||||
implementation("io.reactivex.rxjava2:rxjava:2.2.21")
|
implementation(libs.rxjava)
|
||||||
implementation("io.reactivex.rxjava2:rxandroid:2.1.1@aar")
|
implementation(libs.rxandroid)
|
||||||
|
|
||||||
// Device Names
|
// Device Names
|
||||||
implementation("com.jaredrummler:android-device-names:2.1.1")
|
implementation(libs.android.device.names)
|
||||||
|
|
||||||
// Version Compare
|
// Version Compare
|
||||||
implementation("io.github.g00fy2:versioncompare:1.5.0")
|
implementation(libs.versioncompare)
|
||||||
|
|
||||||
// Terminal Emulator
|
// Terminal Emulator
|
||||||
implementation(project(":libs:jackpal.androidterm.libtermexec-1.0"))
|
implementation(project(":libs:jackpal.androidterm.libtermexec-1.0"))
|
||||||
@@ -209,18 +204,18 @@ dependencies /* Unclassified */ {
|
|||||||
implementation(files("$rootDir/libs/javamail-android/mail.jar"))
|
implementation(files("$rootDir/libs/javamail-android/mail.jar"))
|
||||||
|
|
||||||
// Shizuku
|
// Shizuku
|
||||||
implementation("dev.rikka.shizuku:api:13.1.5")
|
implementation(libs.shizuku.api)
|
||||||
implementation("dev.rikka.shizuku:provider:13.1.5")
|
implementation(libs.shizuku.provider)
|
||||||
|
|
||||||
// ARSCLib
|
// ARSCLib
|
||||||
implementation("io.github.reandroid:ARSCLib:1.3.5")
|
implementation(libs.arsclib)
|
||||||
|
|
||||||
// Toaster
|
// Toaster
|
||||||
implementation("com.github.getActivity:Toaster:12.6")
|
implementation(libs.toaster)
|
||||||
implementation("com.github.getActivity:EasyWindow:10.3")
|
implementation(libs.easywindow)
|
||||||
|
|
||||||
// Pinyin4j
|
// Pinyin4j
|
||||||
implementation("com.belerweb:pinyin4j:2.5.1")
|
implementation(libs.pinyin4j)
|
||||||
|
|
||||||
// Jieba Analysis (zh-CN: 结巴分词)
|
// Jieba Analysis (zh-CN: 结巴分词)
|
||||||
// implementation("com.huaban:jieba-analysis:1.0.2")
|
// implementation("com.huaban:jieba-analysis:1.0.2")
|
||||||
@@ -230,9 +225,9 @@ dependencies /* Unclassified */ {
|
|||||||
implementation(files("$rootDir/libs/tiny-sign-0.9.jar"))
|
implementation(files("$rootDir/libs/tiny-sign-0.9.jar"))
|
||||||
|
|
||||||
// Room
|
// Room
|
||||||
implementation("androidx.room:room-runtime:2.7.2")
|
implementation(libs.room.runtime)
|
||||||
implementation("androidx.room:room-ktx:2.7.2")
|
implementation(libs.room.ktx)
|
||||||
ksp("androidx.room:room-compiler:2.7.2")
|
ksp(libs.room.compiler)
|
||||||
|
|
||||||
// ApkSig
|
// ApkSig
|
||||||
// implementation("com.android.tools.build:apksig:8.7.3")
|
// implementation("com.android.tools.build:apksig:8.7.3")
|
||||||
@@ -240,21 +235,21 @@ dependencies /* Unclassified */ {
|
|||||||
// ApkSigner
|
// ApkSigner
|
||||||
implementation(project(":modules:apk-signer"))
|
implementation(project(":modules:apk-signer"))
|
||||||
|
|
||||||
// Spongy Castle
|
// Prov
|
||||||
implementation("com.madgag.spongycastle:prov:1.58.0.0")
|
implementation(libs.prov)
|
||||||
|
|
||||||
// MQTT
|
// MQTT
|
||||||
implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0")
|
implementation(libs.paho.client.mqttv3)
|
||||||
implementation("org.eclipse.paho:org.eclipse.paho.android.service:1.1.1")
|
implementation(libs.paho.android.service)
|
||||||
|
|
||||||
// Jsoup
|
// Jsoup
|
||||||
implementation("org.jsoup:jsoup:1.20.1")
|
implementation(libs.jsoup)
|
||||||
|
|
||||||
// Material Date Time Picker
|
// Material Date Time Picker
|
||||||
implementation(project(":modules:material-date-time-picker"))
|
implementation(project(":modules:material-date-time-picker"))
|
||||||
|
|
||||||
// ICU4J
|
// ICU4J
|
||||||
implementation("com.ibm.icu:icu4j:77.1")
|
implementation(libs.icu4j)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies /* MIME */ {
|
dependencies /* MIME */ {
|
||||||
@@ -270,22 +265,22 @@ dependencies /* MIME */ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies /* Test */ {
|
dependencies /* Test */ {
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation("androidx.test:runner:1.6.2")
|
androidTestImplementation(libs.test.runner)
|
||||||
androidTestImplementation("org.junit.jupiter:junit-jupiter:5.13.0")
|
androidTestImplementation(libs.junit.jupiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies /* Annotations */ {
|
dependencies /* Annotations */ {
|
||||||
// Android Annotations
|
// Android Annotations
|
||||||
implementation("org.androidannotations:androidannotations-api:4.8.0")
|
implementation(libs.androidannotations.api)
|
||||||
implementation("androidx.annotation:annotation:1.9.1")
|
implementation(libs.annotation)
|
||||||
ksp("org.androidannotations:androidannotations:4.8.0")
|
ksp(libs.androidannotations)
|
||||||
|
|
||||||
// JCIP Annotations
|
// JCIP Annotations
|
||||||
implementation("net.jcip:jcip-annotations:1.0")
|
implementation(libs.jcip.annotations)
|
||||||
|
|
||||||
// EventBus
|
// EventBus
|
||||||
implementation("org.greenrobot:eventbus:3.3.1")
|
implementation(libs.eventbus)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies /* AppCompat */ {
|
dependencies /* AppCompat */ {
|
||||||
@@ -295,7 +290,7 @@ dependencies /* AppCompat */ {
|
|||||||
// ! zh-CN:
|
// ! zh-CN:
|
||||||
// ! 查看 Appcompat 库的发行版本,
|
// ! 查看 Appcompat 库的发行版本,
|
||||||
// ! 可访问 https://developer.android.com/jetpack/androidx/releases/appcompat.
|
// ! 可访问 https://developer.android.com/jetpack/androidx/releases/appcompat.
|
||||||
implementation("androidx.appcompat:appcompat:1.7.1")
|
implementation(libs.appcompat)
|
||||||
|
|
||||||
// AppCompat for legacy views (such as JsTextViewLegacy)
|
// AppCompat for legacy views (such as JsTextViewLegacy)
|
||||||
implementation(project(":libs:androidx.appcompat-1.0.2")) {
|
implementation(project(":libs:androidx.appcompat-1.0.2")) {
|
||||||
@@ -319,7 +314,7 @@ dependencies /* Material Dialogs */ {
|
|||||||
// # implementation("com.afollestad.material-dialogs:commons", cfg)
|
// # implementation("com.afollestad.material-dialogs:commons", cfg)
|
||||||
// # }
|
// # }
|
||||||
implementation(project(":modules:material-dialogs"))
|
implementation(project(":modules:material-dialogs"))
|
||||||
implementation("me.zhanghai.android.materialprogressbar:library:1.6.1")
|
implementation(libs.materialprogressbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies /* Layout */ {
|
dependencies /* Layout */ {
|
||||||
@@ -328,7 +323,7 @@ dependencies /* Layout */ {
|
|||||||
implementation(project(":libs:expandable-layout-1.6.0"))
|
implementation(project(":libs:expandable-layout-1.6.0"))
|
||||||
|
|
||||||
// Expandable RecyclerView
|
// Expandable RecyclerView
|
||||||
implementation("com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1")
|
implementation(libs.expandablerecyclerview)
|
||||||
|
|
||||||
// Flexible Divider
|
// Flexible Divider
|
||||||
// implementation("com.yqritc:recyclerview-flexibledivider:1.4.0")
|
// implementation("com.yqritc:recyclerview-flexibledivider:1.4.0")
|
||||||
@@ -337,13 +332,13 @@ dependencies /* Layout */ {
|
|||||||
|
|
||||||
dependencies /* View */ {
|
dependencies /* View */ {
|
||||||
// RoundedImageView
|
// RoundedImageView
|
||||||
implementation("com.makeramen:roundedimageview:2.3.0")
|
implementation(libs.roundedimageview)
|
||||||
|
|
||||||
// CircleImageView
|
// CircleImageView
|
||||||
implementation("de.hdodenhof:circleimageview:3.1.0")
|
implementation(libs.circleimageview)
|
||||||
|
|
||||||
// Animated SVG
|
// Animated SVG
|
||||||
implementation("com.jaredrummler:animated-svg-view:1.0.6")
|
implementation(libs.animated.svg.view)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies /* GitHub API */ {
|
dependencies /* GitHub API */ {
|
||||||
@@ -353,7 +348,7 @@ dependencies /* GitHub API */ {
|
|||||||
because("Compatibility for Android API Level < 26 (Android 8.0) [O]")
|
because("Compatibility for Android API Level < 26 (Android 8.0) [O]")
|
||||||
version {
|
version {
|
||||||
strictly("2.8.0")
|
strictly("2.8.0")
|
||||||
because("Exception on newer versions: 'NoClassDefFoundError: org.apache.commons.io.IOUtils'")
|
because("Exception on newer versions: 'NoClassDefFoundError: org.apache.commons.io.IObuildUtils'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,16 +367,16 @@ dependencies /* GitHub API */ {
|
|||||||
|
|
||||||
dependencies /* MLKit */ {
|
dependencies /* MLKit */ {
|
||||||
// OCR
|
// OCR
|
||||||
implementation("com.google.mlkit:text-recognition-chinese:16.0.1")
|
implementation(libs.text.recognition.chinese)
|
||||||
|
|
||||||
// Barcode
|
// Barcode
|
||||||
implementation("com.google.mlkit:barcode-scanning:17.3.0")
|
implementation(libs.barcode.scanning)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies /* OpenCC */ {
|
dependencies /* OpenCC */ {
|
||||||
// OpenCC
|
// OpenCC
|
||||||
// implementation("com.github.qichuan:android-opencc:1.2.0")
|
// implementation("com.github.qichuan:android-opencc:1.2.0")
|
||||||
implementation("com.github.brooklet:android-opencc:1.2.2")
|
implementation(libs.opencc)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies /* Auto.js Extensions */ {
|
dependencies /* Auto.js Extensions */ {
|
||||||
@@ -402,8 +397,8 @@ dependencies /* Auto.js Extensions */ {
|
|||||||
// # implementation(project(":libs:Auto.js-ApkBuilder-1.0.3"))
|
// # implementation(project(":libs:Auto.js-ApkBuilder-1.0.3"))
|
||||||
|
|
||||||
// Extracted from com.github.hyb1996:MutableTheme:1.0.0
|
// Extracted from com.github.hyb1996:MutableTheme:1.0.0
|
||||||
implementation("androidx.recyclerview:recyclerview:1.4.0")
|
implementation(libs.recyclerview)
|
||||||
implementation("com.github.ozodrukh:CircularReveal:2.0.1")
|
implementation(libs.circularreveal)
|
||||||
// @Legacy com.jrummyapps:colorpicker:2.1.7
|
// @Legacy com.jrummyapps:colorpicker:2.1.7
|
||||||
// @Integrated by SuperMonster003 on Mar 25, 2025.
|
// @Integrated by SuperMonster003 on Mar 25, 2025.
|
||||||
// # implementation("com.jaredrummler:colorpicker:1.1.0")
|
// # implementation("com.jaredrummler:colorpicker:1.1.0")
|
||||||
@@ -457,7 +452,7 @@ android {
|
|||||||
|
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
buildConfigField("String", "VERSION_DATE", "\"${Utils.getDateString("MMM d, yyyy", "GMT+08:00")}\"")
|
buildConfigField("String", "VERSION_DATE", "\"${utils.getDateString("MMM d, yyyy", "GMT+08:00")}\"")
|
||||||
buildConfigField("String", "VSCODE_EXT_REQUIRED_VERSION", "\"${versions.vscodeExtRequiredVersion}\"")
|
buildConfigField("String", "VSCODE_EXT_REQUIRED_VERSION", "\"${versions.vscodeExtRequiredVersion}\"")
|
||||||
buildConfigField("boolean", "is${flavorNameInrt.uppercaseFirstChar()}", "false")
|
buildConfigField("boolean", "is${flavorNameInrt.uppercaseFirstChar()}", "false")
|
||||||
|
|
||||||
@@ -520,47 +515,6 @@ android {
|
|||||||
// ! https://github.com/kkevsekk1/AutoX/blob/a6d482189291b460c3be60970b74c5321d26e457/inrt/build.gradle.kts#L93
|
// ! https://github.com/kkevsekk1/AutoX/blob/a6d482189291b460c3be60970b74c5321d26e457/inrt/build.gradle.kts#L93
|
||||||
// noinspection ChromeOsAbiSupport
|
// noinspection ChromeOsAbiSupport
|
||||||
ndk.abiFilters += ""
|
ndk.abiFilters += ""
|
||||||
|
|
||||||
gradle.taskGraph.whenReady(object : Action<TaskExecutionGraph> {
|
|
||||||
override fun execute(taskGraph: TaskExecutionGraph) {
|
|
||||||
val taskName = "$buildActionAssemble${flavorNameInrt.uppercaseFirstChar()}${buildTypeRelease.uppercaseFirstChar()}"
|
|
||||||
project.getTasksByName(taskName, true)
|
|
||||||
.firstOrNull()
|
|
||||||
?.doLast {
|
|
||||||
copy {
|
|
||||||
val src = "build/outputs/apk/$flavorNameInrt/$buildTypeRelease"
|
|
||||||
|
|
||||||
// @Reference to LZX284 (https://github.com/LZX284) by SuperMonster003 on Nov 16, 2023.
|
|
||||||
val dst = "src/main/assets-$flavorNameApp"
|
|
||||||
|
|
||||||
val ext = Utils.FILE_EXTENSION_APK
|
|
||||||
|
|
||||||
if (!file(src).isDirectory) {
|
|
||||||
return@copy
|
|
||||||
}
|
|
||||||
|
|
||||||
from(src); into(dst)
|
|
||||||
|
|
||||||
val verName = versionName?.replace(Regex("\\s"), "-")?.lowercase()
|
|
||||||
|
|
||||||
/* e.g. inrt-v6.4.0-beta-universal.apk */
|
|
||||||
val srcFileName = "$flavorNameInrt-v$verName-universal.$ext".also {
|
|
||||||
if (!file(File(src, it)).exists()) {
|
|
||||||
throw GradleException("Source file \"${file(File(src, it))}\" doesn't exist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val dstFileName = "$templateName.$ext"
|
|
||||||
val isOverridden = file(File(dst, dstFileName)).exists()
|
|
||||||
include(srcFileName)
|
|
||||||
rename(srcFileName, dstFileName)
|
|
||||||
println("Source: ${file(File(src, srcFileName))}")
|
|
||||||
println("Destination: ${file(File(dst, dstFileName))}${if (isOverridden) " [overridden]" else ""}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?: println("$taskName doesn't exist in project ${project.name}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
androidResources {
|
androidResources {
|
||||||
@@ -676,12 +630,12 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
if (sign.isValid) {
|
if (signs.isValid) {
|
||||||
create(buildTypeRelease) {
|
create(buildTypeRelease) {
|
||||||
storeFile = sign.properties["storeFile"]?.let { file(it as String) }
|
storeFile = signs.properties["storeFile"]?.let { file(it as String) }
|
||||||
keyPassword = sign.properties["keyPassword"] as String
|
keyPassword = signs.properties["keyPassword"] as String
|
||||||
keyAlias = sign.properties["keyAlias"] as String
|
keyAlias = signs.properties["keyAlias"] as String
|
||||||
storePassword = sign.properties["storePassword"] as String
|
storePassword = signs.properties["storePassword"] as String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -691,7 +645,7 @@ android {
|
|||||||
getDefaultProguardFile("proguard-android.txt"),
|
getDefaultProguardFile("proguard-android.txt"),
|
||||||
"proguard-rules.pro",
|
"proguard-rules.pro",
|
||||||
)
|
)
|
||||||
val niceSigningConfig = takeIf { sign.isValid }?.let {
|
val niceSigningConfig = takeIf { signs.isValid }?.let {
|
||||||
signingConfigs.getByName(buildTypeRelease)
|
signingConfigs.getByName(buildTypeRelease)
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
@@ -709,10 +663,12 @@ android {
|
|||||||
buildFeatures {
|
buildFeatures {
|
||||||
aidl = true
|
aidl = true
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
|
|
||||||
// @Hint by SuperMonster003 on Aug 14, 2023.
|
// @Hint by SuperMonster003 on Aug 14, 2023.
|
||||||
// ! Substitution of "android.defaults.buildfeatures.buildconfig=true"
|
// ! Substitution of "android.defaults.buildfeatures.buildconfig=true"
|
||||||
// ! zh-CN: "android.defaults.buildfeatures.buildconfig=true" 的替代方案
|
// ! zh-CN: "android.defaults.buildfeatures.buildconfig=true" 的替代方案
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
|
|
||||||
// @Archived by SuperMonster003 on Sep 23, 2024.
|
// @Archived by SuperMonster003 on Sep 23, 2024.
|
||||||
// ! Jetpack Compose
|
// ! Jetpack Compose
|
||||||
// # compose = true
|
// # compose = true
|
||||||
@@ -750,7 +706,14 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
outputs.map { it as BaseVariantOutputImpl }.forEach {
|
outputs.map { it as BaseVariantOutputImpl }.forEach {
|
||||||
it.outputFileName = Utils.getOutputFileName(this@all as ApplicationVariantImpl, it)
|
it.outputFileName = run {
|
||||||
|
val variant = this@all as ApplicationVariantImpl
|
||||||
|
val autojs = variant.applicationId.replace("^.+\\.(.+)$".toRegex(), "$1") // e.g. autojs6
|
||||||
|
val version = variant.versionName.replace("\\s".toRegex(), "-") // e.g. 6.1.0
|
||||||
|
val architecture = it.getFilter("ABI") ?: "universal"
|
||||||
|
val extension = utils.FILE_EXTENSION_APK
|
||||||
|
"$autojs-v$version-$architecture.$extension".lowercase(getDefault())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,7 +755,7 @@ tasks {
|
|||||||
listOf(flavorNameApp, flavorNameInrt).forEach { flavorName ->
|
listOf(flavorNameApp, flavorNameInrt).forEach { flavorName ->
|
||||||
val src = "$flavorName/$buildTypeRelease"
|
val src = "$flavorName/$buildTypeRelease"
|
||||||
val dst = "${src}s"
|
val dst = "${src}s"
|
||||||
val ext = Utils.FILE_EXTENSION_APK
|
val ext = utils.FILE_EXTENSION_APK
|
||||||
|
|
||||||
if (!file(src).isDirectory) {
|
if (!file(src).isDirectory) {
|
||||||
return@forEach
|
return@forEach
|
||||||
@@ -801,7 +764,7 @@ tasks {
|
|||||||
from(src); into(dst); include("*.$ext")
|
from(src); into(dst); include("*.$ext")
|
||||||
|
|
||||||
rename { name ->
|
rename { name ->
|
||||||
Utils.digestCRC32(file("${src}/$name")).let { digest ->
|
utils.digestCRC32(file("${src}/$name")).let { digest ->
|
||||||
name.replace(Regex("^(.+?)(\\.$ext)$"), "$1-$digest$2")
|
name.replace(Regex("^(.+?)(\\.$ext)$"), "$1-$digest$2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -814,231 +777,3 @@ tasks {
|
|||||||
extra {
|
extra {
|
||||||
versions.handleIfNeeded(project, flavorNameApp, listOf(buildTypeDebug, buildTypeRelease))
|
versions.handleIfNeeded(project, flavorNameApp, listOf(buildTypeDebug, buildTypeRelease))
|
||||||
}
|
}
|
||||||
|
|
||||||
gradle.beforeProject {
|
|
||||||
extensions.extraProperties["compileSdk"] = versions.sdkVersionCompile
|
|
||||||
extensions.extraProperties["minSdk"] = versions.sdkVersionMin
|
|
||||||
extensions.extraProperties["targetSdk"] = versions.sdkVersionTarget
|
|
||||||
}
|
|
||||||
|
|
||||||
class Sign(filePath: String) {
|
|
||||||
|
|
||||||
var isValid = false
|
|
||||||
private set
|
|
||||||
|
|
||||||
val properties = Properties().also { props ->
|
|
||||||
File(filePath).takeIf { it.exists() }?.let {
|
|
||||||
props.load(FileInputStream(it))
|
|
||||||
isValid = props.isNotEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class Versions(filePath: String) {
|
|
||||||
|
|
||||||
private val properties = Properties()
|
|
||||||
private val file = File(filePath).apply {
|
|
||||||
if (!canRead()) {
|
|
||||||
throw FileNotFoundException("Cannot read file '$filePath'")
|
|
||||||
}
|
|
||||||
properties.load(FileInputStream(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
val sdkVersionMin = properties["MIN_SDK_VERSION"].let { it as String }.toInt()
|
|
||||||
val sdkVersionTarget = properties["TARGET_SDK_VERSION"].let { it as String }.toInt()
|
|
||||||
val sdkVersionTargetInrt = properties["TARGET_SDK_VERSION_INRT"].let { it as String }.toInt()
|
|
||||||
val sdkVersionCompile = properties["COMPILE_SDK_VERSION"].let { it as String }.toInt()
|
|
||||||
val appVersionName = properties["VERSION_NAME"] as String
|
|
||||||
val appVersionCode = properties["VERSION_BUILD"].let { it as String }.toInt()
|
|
||||||
val vscodeExtRequiredVersion = properties["VSCODE_EXT_REQUIRED_VERSION"] as String
|
|
||||||
|
|
||||||
private val currentVersionInt = JavaVersion.current().majorVersion.toInt()
|
|
||||||
|
|
||||||
private val javaVersionMinSupported: Int = properties["JAVA_VERSION_MIN_SUPPORTED"]
|
|
||||||
.let { it as String }.toInt()
|
|
||||||
.also {
|
|
||||||
if (currentVersionInt < it) {
|
|
||||||
throw GradleException(
|
|
||||||
"Current Gradle JDK version ${JavaVersion.current()} does not meet " +
|
|
||||||
"the minimum requirement which $it is needed."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private val javaVersionMinSuggested: Int = properties["JAVA_VERSION_MIN_SUGGESTED"].let { it as String }.toInt()
|
|
||||||
private val javaVersionMaxSupported: Int = properties["JAVA_VERSION_MAX_SUPPORTED"].let { it as String }.toInt()
|
|
||||||
private val javaVersionRaw = properties["JAVA_VERSION"] as String
|
|
||||||
private var javaVersionInfoSuffix = ""
|
|
||||||
|
|
||||||
val javaVersion: JavaVersion by lazy {
|
|
||||||
val javaVersionInt = determineJavaVersion()
|
|
||||||
gradle.beforeProject {
|
|
||||||
extensions.extraProperties["javaVersion"] = javaVersionInt
|
|
||||||
}
|
|
||||||
JavaVersion.toVersion(javaVersionInt)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun determineJavaVersion(): Int {
|
|
||||||
if (gradle.extra.has("javaVersionOverriddenByUser")) {
|
|
||||||
(gradle.extra.get("javaVersionOverriddenByUser") as? Int)?.let {
|
|
||||||
javaVersionInfoSuffix += " [user-specified]"
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var versionInt = javaVersionRaw.toInt()
|
|
||||||
var isJvmCoercive = false
|
|
||||||
|
|
||||||
while (versionInt > javaVersionMinSupported) {
|
|
||||||
if (JvmTarget.values().any { it.name.contains(Regex("_$versionInt$")) }) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
versionInt -= 1
|
|
||||||
isJvmCoercive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isJvmCoercive) {
|
|
||||||
javaVersionInfoSuffix += " [coercive-jvm-downgraded]"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (versionInt > currentVersionInt) {
|
|
||||||
versionInt = currentVersionInt
|
|
||||||
javaVersionInfoSuffix += " [consistent-downgraded]"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gradle.extra.has("javaVersionCoercedByGradle")) {
|
|
||||||
(gradle.extra["javaVersionCoercedByGradle"] as? Int)?.let {
|
|
||||||
if (versionInt > it) {
|
|
||||||
versionInt = it
|
|
||||||
javaVersionInfoSuffix += " [coercive-gradle-downgraded]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return versionInt
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isBuildNumberAutoIncremented = false
|
|
||||||
private val minBuildTimeGap = Utils.hours2Millis(0.75)
|
|
||||||
|
|
||||||
private val isBuildGapEnough
|
|
||||||
get() = properties["BUILD_TIME"]?.let {
|
|
||||||
Date().time - (it as String).toLong() > minBuildTimeGap
|
|
||||||
} == true
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (currentVersionInt < javaVersionMinSuggested) {
|
|
||||||
logger.error(
|
|
||||||
"It is recommended to upgrade current Gradle JDK version ${JavaVersion.current()} to $javaVersionMinSuggested or higher${
|
|
||||||
if (javaVersionMaxSupported > 0) " (but not higher than $javaVersionMaxSupported)" else ""
|
|
||||||
}."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (currentVersionInt > javaVersionMaxSupported) {
|
|
||||||
logger.error(
|
|
||||||
"It is recommended to downgrade current Gradle JDK version $currentVersionInt " +
|
|
||||||
"to $javaVersionMaxSupported${if (javaVersionMaxSupported > javaVersionMinSuggested) " or lower (but not lower than $javaVersionMinSuggested)" else ""}, " +
|
|
||||||
"as Gradle may be not compatible with JDK $currentVersionInt for now."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showInfo() {
|
|
||||||
val title = "Version information for AutoJs6 app library"
|
|
||||||
|
|
||||||
val infoVerName = "Version name: $appVersionName"
|
|
||||||
val infoVerCode = "Version code: ${if (isBuildNumberAutoIncremented) "${appVersionCode + 1} [auto-incremented]" else appVersionCode}"
|
|
||||||
val infoVerSdk = "SDK versions: min [$sdkVersionMin] / target [$sdkVersionTarget] / compile [$sdkVersionCompile]"
|
|
||||||
val infoVerJava = "Java version: $javaVersion${if (gradle.extra.has("isHideConsoleInfoHintSuffix") && gradle.extra.get("isHideConsoleInfoHintSuffix") == true) "" else javaVersionInfoSuffix}"
|
|
||||||
|
|
||||||
val maxLength = arrayOf(title, infoVerName, infoVerCode, infoVerSdk, infoVerJava).maxOf { it.length }
|
|
||||||
|
|
||||||
arrayOf(
|
|
||||||
"=".repeat(maxLength),
|
|
||||||
title,
|
|
||||||
"-".repeat(maxLength),
|
|
||||||
infoVerName,
|
|
||||||
infoVerCode,
|
|
||||||
infoVerSdk,
|
|
||||||
infoVerJava,
|
|
||||||
"=".repeat(maxLength),
|
|
||||||
"",
|
|
||||||
).forEach { println(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleIfNeeded(project: Project, flavorName: String, targetBuildType: List<String>) {
|
|
||||||
project.gradle.taskGraph.whenReady(object : Action<TaskExecutionGraph> {
|
|
||||||
override fun execute(taskGraph: TaskExecutionGraph) {
|
|
||||||
for (buildType in targetBuildType) {
|
|
||||||
if (taskGraph.hasTask(Utils.getAssembleFullTaskName(project.name, flavorName, buildType))) {
|
|
||||||
return appendToTask(project, flavorName, buildType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return showInfo()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun appendToTask(project: Project, flavorName: String, buildType: String) {
|
|
||||||
project.tasks.getByName(Utils.getAssembleTaskName(flavorName, buildType)).doLast {
|
|
||||||
updateProperties()
|
|
||||||
println()
|
|
||||||
showInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateProperties() {
|
|
||||||
if (isBuildGapEnough) {
|
|
||||||
val isBuildAppRelease = gradle.startParameter.taskNames.any {
|
|
||||||
it.contains(Regex("^(:?$flavorNameApp:)?$buildActionAssemble($flavorNameApp|$flavorNameInrt)$buildTypeRelease", IGNORE_CASE))
|
|
||||||
}
|
|
||||||
if (!isBuildAppRelease) {
|
|
||||||
properties["VERSION_BUILD"] = "${appVersionCode + 1}"
|
|
||||||
isBuildNumberAutoIncremented = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
properties["BUILD_TIME"] = "${Date().time}"
|
|
||||||
properties.store(file.writer(), null)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object Utils {
|
|
||||||
|
|
||||||
const val FILE_EXTENSION_APK = "apk"
|
|
||||||
|
|
||||||
fun hours2Millis(hour: Double) = hour * 3.6e6
|
|
||||||
|
|
||||||
fun getDateString(format: String, zone: String): String {
|
|
||||||
// e.g. May 23, 2011
|
|
||||||
return SimpleDateFormat(format).apply { timeZone = TimeZone.getTimeZone(zone) }.format(Date())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getOutputFileName(variant: ApplicationVariantImpl, output: BaseVariantOutputImpl): String {
|
|
||||||
val autojs = variant.applicationId.replace("^.+\\.(.+)$".toRegex(), "$1") // e.g. autojs6
|
|
||||||
val version = variant.versionName.replace("\\s".toRegex(), "-") // e.g. 6.1.0
|
|
||||||
val architecture = output.getFilter("ABI") ?: "universal"
|
|
||||||
val extension = FILE_EXTENSION_APK
|
|
||||||
|
|
||||||
return "$autojs-v$version-$architecture.$extension".lowercase(Locale.getDefault())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAssembleTaskName(flavorName: String, buildType: String) = "assemble${capitalize(flavorName)}${capitalize(buildType)}"
|
|
||||||
|
|
||||||
fun getAssembleFullTaskName(projectName: String, flavorName: String, buildType: String) = ":$projectName:${getAssembleTaskName(flavorName, buildType)}"
|
|
||||||
|
|
||||||
fun digestCRC32(file: File): String {
|
|
||||||
val fis = FileInputStream(file)
|
|
||||||
val buffer = ByteArray(4096)
|
|
||||||
var read: Int
|
|
||||||
|
|
||||||
return CRC32().let { o ->
|
|
||||||
while (fis.read(buffer).also { read = it } > 0) {
|
|
||||||
o.update(buffer, 0, read)
|
|
||||||
}
|
|
||||||
String.format("%08x", o.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun capitalize(s: String) = "${s[0].uppercase(Locale.getDefault())}${s.substring(1)}"
|
|
||||||
|
|
||||||
}
|
|
||||||
+2
-2
@@ -10,13 +10,13 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
|
gradlePluginPortal()
|
||||||
maven("https://jitpack.io")
|
maven("https://jitpack.io")
|
||||||
maven("https://maven.aliyun.com/repository/central")
|
maven("https://maven.aliyun.com/repository/central")
|
||||||
maven("https://maven.aliyun.com/repository/google")
|
maven("https://maven.aliyun.com/repository/google")
|
||||||
maven("https://maven.aliyun.com/repository/gradle-plugin")
|
maven("https://maven.aliyun.com/repository/gradle-plugin")
|
||||||
maven("https://maven.aliyun.com/repository/jcenter")
|
maven("https://maven.aliyun.com/repository/jcenter")
|
||||||
maven("https://maven.aliyun.com/repository/public")
|
maven("https://maven.aliyun.com/repository/public")
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,4 +25,4 @@ tasks {
|
|||||||
// @Legacy delete(rootProject.buildDir)
|
// @Legacy delete(rootProject.buildDir)
|
||||||
delete(rootProject.layout.buildDirectory)
|
delete(rootProject.layout.buildDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
/build
|
||||||
|
/.gradle
|
||||||
|
/.kotlin
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
@file:Suppress("ObjectLiteralToLambda")
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
`kotlin-dsl` /* kotlin("jvm") */
|
||||||
|
`java-gradle-plugin`
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.apache.commons.compress)
|
||||||
|
implementation(libs.tukaani.xz)
|
||||||
|
|
||||||
|
implementation(gradleApi())
|
||||||
|
}
|
||||||
|
|
||||||
|
gradlePlugin {
|
||||||
|
plugins {
|
||||||
|
create("buildUtils", object : Action<PluginDeclaration> {
|
||||||
|
override fun execute(t: PluginDeclaration) {
|
||||||
|
t.id = "org.autojs.build.utils"
|
||||||
|
t.implementationClass = "org.autojs.build.UtilsPlugin"
|
||||||
|
t.displayName = "AutoJs6 Build Utils Plugin"
|
||||||
|
t.description = "Provides utilities for downloading, extracting archives, and version helpers."
|
||||||
|
}
|
||||||
|
})
|
||||||
|
create("buildVersions", object : Action<PluginDeclaration> {
|
||||||
|
override fun execute(t: PluginDeclaration) {
|
||||||
|
t.id = "org.autojs.build.versions"
|
||||||
|
t.implementationClass = "org.autojs.build.VersionsPlugin"
|
||||||
|
t.displayName = "AutoJs6 Versions Plugin"
|
||||||
|
t.description = "Provides version helpers."
|
||||||
|
}
|
||||||
|
})
|
||||||
|
create("buildSigns", object : Action<PluginDeclaration> {
|
||||||
|
override fun execute(t: PluginDeclaration) {
|
||||||
|
t.id = "org.autojs.build.signs"
|
||||||
|
t.implementationClass = "org.autojs.build.SignsPlugin"
|
||||||
|
t.displayName = "AutoJs6 Signs Plugin"
|
||||||
|
t.description = "Provides signing helpers."
|
||||||
|
}
|
||||||
|
})
|
||||||
|
create("buildProperties", object : Action<PluginDeclaration> {
|
||||||
|
override fun execute(t: PluginDeclaration) {
|
||||||
|
t.id = "org.autojs.build.properties"
|
||||||
|
t.implementationClass = "org.autojs.build.PropertiesPlugin"
|
||||||
|
t.displayName = "AutoJs6 Properties Plugin"
|
||||||
|
t.description = "Provides properties helpers."
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run determineBuildSrcJdk@{
|
||||||
|
val propsFile: File = rootDir.parentFile.resolve("version.properties")
|
||||||
|
val minSupported: Int = Properties().let { props ->
|
||||||
|
require(propsFile.isFile) {
|
||||||
|
"version.properties not found in root directory"
|
||||||
|
}
|
||||||
|
propsFile.inputStream().use { props.load(it) }
|
||||||
|
val minSupportedVersion = props.getProperty("JAVA_VERSION_MIN_SUPPORTED")
|
||||||
|
require(minSupportedVersion != null) {
|
||||||
|
"version.properties does not contain \"JAVA_VERSION_MIN_SUPPORTED\""
|
||||||
|
}
|
||||||
|
minSupportedVersion.toInt()
|
||||||
|
}
|
||||||
|
val current = JavaVersion.current().majorVersion.toIntOrNull() ?: minSupported
|
||||||
|
|
||||||
|
fun tryAdjustByKotlinJvmTarget(sourceVersion: Int): Int {
|
||||||
|
return runCatching {
|
||||||
|
val cls = Class.forName("org.jetbrains.kotlin.gradle.dsl.JvmTarget")
|
||||||
|
val enumConstants = cls.enumConstants ?: return sourceVersion
|
||||||
|
var tmpVersion = sourceVersion
|
||||||
|
while (tmpVersion > minSupported) {
|
||||||
|
val wanted = "JVM_$tmpVersion"
|
||||||
|
if (enumConstants.any { it?.toString().equals(wanted, ignoreCase = true) }) {
|
||||||
|
return tmpVersion
|
||||||
|
}
|
||||||
|
tmpVersion -= 1
|
||||||
|
}
|
||||||
|
return@runCatching sourceVersion
|
||||||
|
}.getOrDefault(sourceVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
tryAdjustByKotlinJvmTarget(maxOf(current, minSupported)).also {
|
||||||
|
println("Toolchain: selected [$it] / current [$current] / min [$minSupported]")
|
||||||
|
}
|
||||||
|
}.let { jdk ->
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(jdk)
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain.languageVersion.set(JavaLanguageVersion.of(jdk))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
versionCatalogs {
|
||||||
|
create("libs") {
|
||||||
|
from(files("../gradle/libs.versions.toml"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package org.autojs.build
|
||||||
|
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class BuildProperties private constructor(
|
||||||
|
private val file: File,
|
||||||
|
private val props: Properties,
|
||||||
|
) {
|
||||||
|
|
||||||
|
val path: String get() = file.absolutePath
|
||||||
|
|
||||||
|
operator fun get(propertyName: String): String = when {
|
||||||
|
propertyName.contains("/") -> {
|
||||||
|
get(propertyName.split("/"))
|
||||||
|
}
|
||||||
|
else -> requireValue(propertyName)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(propertyInfo: List<String>): String {
|
||||||
|
val (lib, body) = propertyInfo
|
||||||
|
return requireValue(lib, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requireString(key: String, alternate: String): String {
|
||||||
|
return props.getProperty(key)
|
||||||
|
?: props.getProperty(alternate)
|
||||||
|
?: throw IllegalStateException("Property '$key' is missing in '${file.absolutePath}'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requireString(key: String): String {
|
||||||
|
return props.getProperty(key)
|
||||||
|
?: throw IllegalStateException("Property '$key' is missing in '${file.absolutePath}'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requireInt(key: String): Int = requireString(key).toInt()
|
||||||
|
|
||||||
|
fun getIntOrNull(key: String): Int? = props.getProperty(key)?.toIntOrNull()
|
||||||
|
|
||||||
|
private fun requireValue(name: String): String {
|
||||||
|
return when (val value = requireString("${name}_VERSION", name)) {
|
||||||
|
"PUBLIC" -> requireString("PUBLIC_${name}_VERSION", "PUBLIC_${name}")
|
||||||
|
else -> value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requireValue(lib: String, body: String): String {
|
||||||
|
return when (val value = requireString("${lib}_${body}_VERSION", "${lib}_${body}")) {
|
||||||
|
"PUBLIC" -> requireString("PUBLIC_${body}_VERSION", "PUBLIC_${body}")
|
||||||
|
else -> value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun loadFrom(project: Project): BuildProperties {
|
||||||
|
return loadFrom("${project.rootDir}/version.properties")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun loadFrom(filePath: String): BuildProperties {
|
||||||
|
val f = File(filePath)
|
||||||
|
if (!f.canRead()) {
|
||||||
|
throw FileNotFoundException("Cannot read file '$filePath'")
|
||||||
|
}
|
||||||
|
val p = Properties()
|
||||||
|
f.inputStream().use { p.load(it) }
|
||||||
|
return BuildProperties(f, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package org.autojs.build
|
||||||
|
|
||||||
|
class Formatted(
|
||||||
|
private val title: CharSequence,
|
||||||
|
private val contents: Collection<CharSequence> = emptyList(),
|
||||||
|
private val subtitle: CharSequence? = null
|
||||||
|
) {
|
||||||
|
private val formattedOutput: List<CharSequence> = run {
|
||||||
|
val maxLength = (listOfNotNull(title, subtitle) + contents).maxOf { it.length }
|
||||||
|
buildList {
|
||||||
|
add("=".repeat(maxLength))
|
||||||
|
add(title)
|
||||||
|
subtitle?.let { add(it) }
|
||||||
|
if (contents.isNotEmpty()) add("-".repeat(maxLength))
|
||||||
|
addAll(contents)
|
||||||
|
add("=".repeat(maxLength))
|
||||||
|
add("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun print(contentsMatters: Boolean = false) {
|
||||||
|
formattedOutput.forEach {
|
||||||
|
if (!contentsMatters || contents.isNotEmpty()) {
|
||||||
|
println(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun throwException() {
|
||||||
|
throw Exception(formattedOutput.joinToString("\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
package org.autojs.build
|
||||||
|
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.kotlin.dsl.extra
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.net.SocketTimeoutException
|
||||||
|
import java.net.URI
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.channels.FileChannel
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.*
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
class LibDeployer(
|
||||||
|
private val project: Project,
|
||||||
|
private val name: String,
|
||||||
|
private val downloadUrl: String,
|
||||||
|
) {
|
||||||
|
private var sourceDir: String = File.separator
|
||||||
|
private var sourceFile: File = project.file(File.separator)
|
||||||
|
|
||||||
|
private var destDir: String = File.separator
|
||||||
|
private var destFile: File = project.file(File.separator)
|
||||||
|
|
||||||
|
private val cacheRootFile: File = project.file("cache").apply { mkdirs() }
|
||||||
|
private val cacheFileName: String
|
||||||
|
private val cacheFileExtensionName: String
|
||||||
|
private val cacheFile: File
|
||||||
|
|
||||||
|
private val shouldPrintProgress: Boolean
|
||||||
|
get() = project.gradle.extra["platform"]?.let {
|
||||||
|
it::class.java.getMethod("getShouldPrintProgress")
|
||||||
|
.apply { isAccessible = true }
|
||||||
|
.invoke(it)
|
||||||
|
} == true
|
||||||
|
|
||||||
|
init {
|
||||||
|
val extracted = extractFileFromUrl(downloadUrl)
|
||||||
|
cacheFileName = "${extracted.fileName}-[${generateShortMd5String(downloadUrl).lowercase()}]"
|
||||||
|
cacheFileExtensionName = extracted.extensionName
|
||||||
|
cacheFile = project.file(File(cacheRootFile, "$cacheFileName.$cacheFileExtensionName"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSkipFile(): File = project.file(File(destFile, "$cacheFileName.skip"))
|
||||||
|
private fun getTempOutFile(): File = project.file(File(destFile, "temp-extracted"))
|
||||||
|
|
||||||
|
fun setSourceDir(sourceDir: String): LibDeployer {
|
||||||
|
this.sourceDir = sourceDir
|
||||||
|
this.sourceFile = project.file(sourceDir)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDestDir(destDir: String): LibDeployer {
|
||||||
|
val dest = if (destDir.startsWith(File.separator)) {
|
||||||
|
project.file(destDir.substring(1))
|
||||||
|
} else {
|
||||||
|
project.file(destDir)
|
||||||
|
}
|
||||||
|
dest.mkdirs()
|
||||||
|
this.destDir = destDir
|
||||||
|
this.destFile = dest
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deploy() {
|
||||||
|
val tempOutFile = getTempOutFile()
|
||||||
|
if (tempOutFile.exists()) {
|
||||||
|
project.delete(tempOutFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
val (shouldDownload, shouldExtract) = checkCacheAndSkipFiles()
|
||||||
|
|
||||||
|
var needExtract = shouldExtract
|
||||||
|
if (shouldDownload) {
|
||||||
|
printDownloadInfo()
|
||||||
|
downloadWithRetry()
|
||||||
|
needExtract = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needExtract) {
|
||||||
|
printExtractInfo()
|
||||||
|
try {
|
||||||
|
extractCacheFile()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println()
|
||||||
|
println("Cache file was deleted as there is an error during extraction")
|
||||||
|
println("Cache file: ${cacheFile.absolutePath}")
|
||||||
|
cacheFile.delete()
|
||||||
|
e.message?.let { println("Error message: $it") }
|
||||||
|
println()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
generateMd5File(cacheFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clean() {
|
||||||
|
project.delete(getSkipFile())
|
||||||
|
project.delete(getTempOutFile())
|
||||||
|
deleteDestAccordingToSrc()
|
||||||
|
deleteCacheAccordingToMd5()
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class ExtractedFile(val fileName: String, val extensionName: String)
|
||||||
|
|
||||||
|
private fun extractFileFromUrl(url: String): ExtractedFile {
|
||||||
|
val fileNameWithExtension = url.substringAfterLast('/')
|
||||||
|
val dot = fileNameWithExtension.lastIndexOf('.')
|
||||||
|
val fileNameRaw = if (dot >= 0) fileNameWithExtension.take(dot) else fileNameWithExtension
|
||||||
|
val fileName = fileNameRaw.lowercase()
|
||||||
|
.replace(Regex("\\s+"), "")
|
||||||
|
.replace(Regex("[^a-z0-9.]"), "-")
|
||||||
|
val extension = if (dot >= 0) fileNameWithExtension.substring(dot + 1) else ""
|
||||||
|
return ExtractedFile(fileName, extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkCacheAndSkipFiles(): Pair<Boolean, Boolean> {
|
||||||
|
var shouldDownload = true
|
||||||
|
var shouldExtract = true
|
||||||
|
|
||||||
|
val skip = getSkipFile()
|
||||||
|
if (skip.exists()) {
|
||||||
|
println("No need to download or extract \"$name\" archive file as the \"skip file\" exists")
|
||||||
|
shouldDownload = false
|
||||||
|
shouldExtract = false
|
||||||
|
println()
|
||||||
|
} else if (cacheFile.exists()) {
|
||||||
|
if (validateMd5File(cacheFile)) {
|
||||||
|
println("No need to download \"$name\" archive file as the cache file exists and is valid")
|
||||||
|
shouldDownload = false
|
||||||
|
println("Cache file of \"$name\" needs to be extracted as the \"skip file\" doesn't exist")
|
||||||
|
} else {
|
||||||
|
println("Cache file of \"$name\" was deleted as it is invalid")
|
||||||
|
println("Cache file: $cacheFile")
|
||||||
|
project.delete(cacheFile)
|
||||||
|
val md5File = File(cacheFile.parentFile, cacheFile.name + ".md5")
|
||||||
|
if (md5File.exists()) {
|
||||||
|
println("MD5 file of \"$name\" was deleted as it is unreliable")
|
||||||
|
println("MD5 file: $md5File")
|
||||||
|
project.delete(md5File)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
return shouldDownload to shouldExtract
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateMd5File(file: File): Boolean {
|
||||||
|
val md5File = File(file.parentFile, file.name + ".md5")
|
||||||
|
if (!md5File.exists()) return false
|
||||||
|
val expected = md5File.readText().trim().uppercase()
|
||||||
|
val actual = generateMd5String(file).uppercase()
|
||||||
|
return expected == actual
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateMd5File(file: File) {
|
||||||
|
val md5File = File(file.parentFile, file.name + ".md5")
|
||||||
|
println("Generating MD5...")
|
||||||
|
val md5 = generateMd5String(file)
|
||||||
|
md5File.writeText(md5)
|
||||||
|
println("MD5 generated: $md5")
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateMd5String(file: File): String {
|
||||||
|
FileInputStream(file).use { fis ->
|
||||||
|
val channel: FileChannel = fis.channel
|
||||||
|
val md = MessageDigest.getInstance("MD5")
|
||||||
|
val buffer = ByteBuffer.allocate(4096)
|
||||||
|
while (channel.read(buffer) > 0) {
|
||||||
|
buffer.flip()
|
||||||
|
md.update(buffer)
|
||||||
|
buffer.clear()
|
||||||
|
}
|
||||||
|
return BigInteger(1, md.digest()).toString(16).padStart(32, '0')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateShortMd5String(s: String): String {
|
||||||
|
val md = MessageDigest.getInstance("MD5")
|
||||||
|
md.update(s.toByteArray())
|
||||||
|
return BigInteger(1, md.digest()).toString(32)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printDownloadInfo() {
|
||||||
|
val title = "Download \"$name\" archive file for \"${project.extensions.extraProperties["projectName"]}\" Gradle project"
|
||||||
|
val srcInfo = "Source: $downloadUrl"
|
||||||
|
val destInfo = "Destination: $cacheFile"
|
||||||
|
val hintInfo = listOf(
|
||||||
|
"If the download gets stuck and won't finish,",
|
||||||
|
"try downloading the source file with tools like IDM (Internet Download Manager),",
|
||||||
|
"then renaming it into the destination path above."
|
||||||
|
)
|
||||||
|
|
||||||
|
val maxLength = listOf(title, srcInfo, destInfo, *hintInfo.toTypedArray()).maxOf { it.length }
|
||||||
|
listOf(
|
||||||
|
"=".repeat(maxLength),
|
||||||
|
title,
|
||||||
|
"-".repeat(maxLength),
|
||||||
|
srcInfo,
|
||||||
|
destInfo,
|
||||||
|
"-".repeat(maxLength),
|
||||||
|
hintInfo.joinToString("\n"),
|
||||||
|
"=".repeat(maxLength),
|
||||||
|
""
|
||||||
|
).forEach { println(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printExtractInfo() {
|
||||||
|
val title = "Extract the archive file for \"${project.extensions.extraProperties["projectName"]}\" Gradle project"
|
||||||
|
val srcInfo = "Source: $cacheFile"
|
||||||
|
val destInfo = "Destination: $destFile"
|
||||||
|
val items = listOf(title, srcInfo, destInfo)
|
||||||
|
val maxLength = items.maxOf { it.length }
|
||||||
|
listOf(
|
||||||
|
"=".repeat(maxLength),
|
||||||
|
title,
|
||||||
|
"-".repeat(maxLength),
|
||||||
|
srcInfo,
|
||||||
|
destInfo,
|
||||||
|
"=".repeat(maxLength),
|
||||||
|
""
|
||||||
|
).forEach { println(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadWithRetry(maxRetries: Int = 3, retryDelayMs: Long = 2000) {
|
||||||
|
var attempt = 0
|
||||||
|
var success = false
|
||||||
|
while (attempt < maxRetries && !success) {
|
||||||
|
try {
|
||||||
|
attempt++
|
||||||
|
download()
|
||||||
|
success = true
|
||||||
|
} catch (_: SocketTimeoutException) {
|
||||||
|
println("Attempt $attempt/$maxRetries failed: Connection timed out. Retrying...")
|
||||||
|
} catch (e: IOException) {
|
||||||
|
println("Attempt $attempt/$maxRetries failed: ${e.message}. Retrying...")
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
Thread.sleep(retryDelayMs)
|
||||||
|
} else {
|
||||||
|
println("Download failed after $maxRetries attempts.")
|
||||||
|
throw GradleException("Download failed after $maxRetries attempts", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun download() {
|
||||||
|
cacheFile.parentFile.mkdirs()
|
||||||
|
val urlConn = URI(downloadUrl).toURL().openConnection().apply {
|
||||||
|
connectTimeout = 120_000
|
||||||
|
readTimeout = 90_000
|
||||||
|
}
|
||||||
|
val fileSize = urlConn.contentLengthLong
|
||||||
|
urlConn.getInputStream().use { input ->
|
||||||
|
FileOutputStream(cacheFile).use { output ->
|
||||||
|
val buffer = ByteArray(8192)
|
||||||
|
var downloaded = 0L
|
||||||
|
var read: Int
|
||||||
|
if (shouldPrintProgress && fileSize <= 0) {
|
||||||
|
println("\rDownloading...")
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
read = input.read(buffer)
|
||||||
|
if (read == -1) break
|
||||||
|
output.write(buffer, 0, read)
|
||||||
|
downloaded += read
|
||||||
|
if (shouldPrintProgress && fileSize > 0) {
|
||||||
|
val progress = downloaded * 100.0 / fileSize
|
||||||
|
val bar = generateProgressBar(progress)
|
||||||
|
print(String.format(Locale.getDefault(), "\rDownloading... [ %s ] %.2f%%", bar, progress))
|
||||||
|
System.out.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val path = cacheFile.absolutePath
|
||||||
|
if (fileSize > 0) {
|
||||||
|
val formattedSize = formatFileSize(fileSize)
|
||||||
|
print(String.format("\rDownload complete [ %s | %s ]\n", path, formattedSize))
|
||||||
|
} else {
|
||||||
|
print(String.format("\rDownload complete [ %s ]\n", path))
|
||||||
|
}
|
||||||
|
System.out.flush()
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractCacheFile() {
|
||||||
|
when (cacheFileExtensionName.lowercase()) {
|
||||||
|
"zip" -> handleZip()
|
||||||
|
"7z" -> handleSevenZip()
|
||||||
|
else -> throw GradleException("Unknown archive file type: $cacheFileExtensionName")
|
||||||
|
}
|
||||||
|
println("All files extracted into [ $destFile ]")
|
||||||
|
val skip = getSkipFile()
|
||||||
|
if (!skip.exists()) {
|
||||||
|
skip.parentFile.mkdirs()
|
||||||
|
skip.createNewFile()
|
||||||
|
println("File \"${skip.name}\" created")
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleZip() {
|
||||||
|
val sourceDirPath = File(sourceDir).path.let { p ->
|
||||||
|
if (p.startsWith(File.separator)) p.substring(1) else p
|
||||||
|
}
|
||||||
|
val tempOut = getTempOutFile()
|
||||||
|
val zipForTotal = ZipFile(cacheFile)
|
||||||
|
val entriesAll = zipForTotal.entries()
|
||||||
|
val entries = mutableListOf<ZipEntry>()
|
||||||
|
var totalExtractedSize = 0L
|
||||||
|
while (entriesAll.hasMoreElements()) {
|
||||||
|
val entry = entriesAll.nextElement()
|
||||||
|
val entryName = File(entry.name).path
|
||||||
|
if (entryName.startsWith(sourceDirPath)) {
|
||||||
|
entries += entry
|
||||||
|
if (!entry.isDirectory) totalExtractedSize += entry.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zipForTotal.close()
|
||||||
|
|
||||||
|
val zip = ZipFile(cacheFile)
|
||||||
|
val zipEntries = zip.entries()
|
||||||
|
var processed = 0
|
||||||
|
val totalEntries = entries.size
|
||||||
|
while (zipEntries.hasMoreElements()) {
|
||||||
|
val entry = zipEntries.nextElement()
|
||||||
|
val entryName = File(entry.name).path
|
||||||
|
if (!entryName.startsWith(sourceDirPath)) continue
|
||||||
|
val outFile = project.file(File(tempOut, entryName.substring(sourceDirPath.length)))
|
||||||
|
if (entry.isDirectory) {
|
||||||
|
outFile.mkdirs()
|
||||||
|
} else {
|
||||||
|
outFile.parentFile.mkdirs()
|
||||||
|
zip.getInputStream(entry).use { input ->
|
||||||
|
FileOutputStream(outFile).use { output ->
|
||||||
|
val buffer = ByteArray(8192)
|
||||||
|
while (true) {
|
||||||
|
val read = input.read(buffer)
|
||||||
|
if (read == -1) break
|
||||||
|
output.write(buffer, 0, read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldPrintProgress) {
|
||||||
|
val progress = processed * 100.0 / max(1, totalEntries)
|
||||||
|
val bar = generateProgressBar(progress)
|
||||||
|
print(String.format(Locale.getDefault(), "\rExtracting... [ %s ] %.2f%%", bar, progress))
|
||||||
|
System.out.flush()
|
||||||
|
}
|
||||||
|
processed++
|
||||||
|
}
|
||||||
|
val formatted = formatFileSize(totalExtractedSize)
|
||||||
|
print(String.format("\rExtraction complete [ %s | %s ]\n", destFile.absolutePath, formatted))
|
||||||
|
System.out.flush()
|
||||||
|
println()
|
||||||
|
zip.close()
|
||||||
|
project.copy {
|
||||||
|
from(tempOut)
|
||||||
|
into(destFile)
|
||||||
|
}
|
||||||
|
project.delete(tempOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSevenZip() {
|
||||||
|
val tempOut = getTempOutFile()
|
||||||
|
val totalBytes = SevenZExtractor.extractDirectoryFrom7z(
|
||||||
|
cacheFile, sourceDir, tempOut, shouldPrintProgress
|
||||||
|
)
|
||||||
|
val formatted = formatFileSize(max(totalBytes, 0L))
|
||||||
|
print(String.format("\rExtraction complete [ %s | %s ]\n", destFile.absolutePath, formatted))
|
||||||
|
System.out.flush()
|
||||||
|
project.copy {
|
||||||
|
from(tempOut)
|
||||||
|
into(destFile)
|
||||||
|
}
|
||||||
|
project.delete(tempOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteDestAccordingToSrc() {
|
||||||
|
project.delete(destFile.absolutePath)
|
||||||
|
var tmp: File? = destFile.parentFile
|
||||||
|
while (tmp != null && tmp != project.projectDir) {
|
||||||
|
val list = tmp.listFiles()?.toList().orEmpty()
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
println("Delete empty directory: ${tmp.absolutePath}")
|
||||||
|
project.delete(tmp)
|
||||||
|
}
|
||||||
|
tmp = tmp.parentFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteCacheAccordingToMd5() {
|
||||||
|
cacheRootFile.listFiles()?.forEach { f ->
|
||||||
|
if (!f.name.endsWith(".md5")) {
|
||||||
|
val md5 = File(f.parentFile, f.name + ".md5")
|
||||||
|
if (!md5.exists() || !validateMd5File(f)) {
|
||||||
|
project.delete(f)
|
||||||
|
println("Delete cache file: ${f.absolutePath}")
|
||||||
|
if (md5.exists()) {
|
||||||
|
project.delete(md5)
|
||||||
|
println("Delete MD5 file: ${md5.absolutePath}")
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateProgressBar(progress: Double, length: Int = 30): String {
|
||||||
|
val filledLen = (length * progress / 100.0).toInt().coerceIn(0, length)
|
||||||
|
val filled = "#".repeat(filledLen)
|
||||||
|
val empty = "-".repeat(length - filledLen)
|
||||||
|
return filled + empty
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatFileSize(size: Long): String = when {
|
||||||
|
// @formatter:off
|
||||||
|
size < 1024L -> String.format(Locale.getDefault(), "%d B", size)
|
||||||
|
size < 1024L * 1024 -> String.format(Locale.getDefault(), "%.2f KB", size / (1024.0))
|
||||||
|
size < 1024L * 1024 * 1024 -> String.format(Locale.getDefault(), "%.2f MB", size / (1024.0 * 1024))
|
||||||
|
else -> String.format(Locale.getDefault(), "%.2f GB", size / (1024.0 * 1024 * 1024))
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package org.autojs.build
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Gradle plugin that provides properties helpers.
|
||||||
|
*
|
||||||
|
* zh-CN: Gradle properties 辅助工具.
|
||||||
|
*
|
||||||
|
* - `id`: "org.autojs.build.properties"
|
||||||
|
* - `implementationClass`: "org.autojs.build.PropertiesPlugin"
|
||||||
|
* - `displayName`: "AutoJs6 Properties Plugin"
|
||||||
|
* - `description`: "Provides properties helpers."
|
||||||
|
*
|
||||||
|
* Apply this plugin to your Android module's `build.gradle.kts`:
|
||||||
|
*
|
||||||
|
* zh-CN: 在 Android 模块的 `build.gradle.kts` 中应用此插件:<br>
|
||||||
|
*
|
||||||
|
* ```kts
|
||||||
|
* plugins {
|
||||||
|
* id("org.autojs.build.properties")
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* props["MIN_SDK"]
|
||||||
|
* props["COMPILE_SDK"]
|
||||||
|
* props["TARGET_SDK"]
|
||||||
|
*
|
||||||
|
* props["RAPID_OCR/NDK"]
|
||||||
|
* props["RAPID_OCR/CMAKE"]
|
||||||
|
*
|
||||||
|
* props["PADDLE_OCR/NDK"]
|
||||||
|
* props["PADDLE_OCR/CMAKE"]
|
||||||
|
* props["PADDLE_OCR/OPENCV"]
|
||||||
|
*
|
||||||
|
* props["IMAGE_QUANT/NDK"]
|
||||||
|
* props["IMAGE_QUANT/CMAKE"]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class PropertiesPlugin : Plugin<Project> {
|
||||||
|
override fun apply(project: Project) {
|
||||||
|
project.extensions.add("props", Utils.newProperties(project))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package org.autojs.build
|
||||||
|
|
||||||
|
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry
|
||||||
|
import org.apache.commons.compress.archivers.sevenz.SevenZFile
|
||||||
|
import java.io.BufferedOutputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
object SevenZExtractor {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun extractDirectoryFrom7z(
|
||||||
|
archive: File,
|
||||||
|
sourceDir: String,
|
||||||
|
outDir: File,
|
||||||
|
shouldPrintProgress: Boolean = true
|
||||||
|
): Long {
|
||||||
|
require(archive.isFile) { "7z archive not found: ${archive.absolutePath}" }
|
||||||
|
if (!outDir.exists()) outDir.mkdirs()
|
||||||
|
|
||||||
|
val sourceDirPath = normalizePrefix(sourceDir)
|
||||||
|
var sevenZFile: SevenZFile? = null
|
||||||
|
|
||||||
|
var totalBytes: Long
|
||||||
|
var writtenBytes = 0L
|
||||||
|
val buffer = ByteArray(64 * 1024)
|
||||||
|
|
||||||
|
try {
|
||||||
|
sevenZFile = SevenZFile.Builder().setFile(archive).get()
|
||||||
|
val allEntries: Iterable<SevenZArchiveEntry> = sevenZFile.entries
|
||||||
|
|
||||||
|
val targetEntries = allEntries.filter { e ->
|
||||||
|
val entryPath = e.name.replace('\\', '/')
|
||||||
|
entryPath.startsWith(sourceDirPath) ||
|
||||||
|
entryPath.startsWith(trimLeadingSlash(sourceDirPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
totalBytes = targetEntries
|
||||||
|
.filter { !it.isDirectory && it.size >= 0 && it.hasStream() }
|
||||||
|
.sumOf { it.size }
|
||||||
|
|
||||||
|
val entriesCount = targetEntries.size
|
||||||
|
var processed = 0
|
||||||
|
|
||||||
|
for (entry in targetEntries) {
|
||||||
|
val entryPath = entry.name.replace('\\', '/')
|
||||||
|
var relative = when {
|
||||||
|
entryPath.startsWith(sourceDirPath) ->
|
||||||
|
entryPath.substring(sourceDirPath.length)
|
||||||
|
entryPath.startsWith(trimLeadingSlash(sourceDirPath)) ->
|
||||||
|
entryPath.substring(trimLeadingSlash(sourceDirPath).length)
|
||||||
|
else -> {
|
||||||
|
processed++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading separator to avoid being treated as absolute path.
|
||||||
|
// zh-CN: // 去掉前导分隔符, 避免被当作绝对路径.
|
||||||
|
while (relative.startsWith("/") || relative.startsWith("\\")) {
|
||||||
|
relative = relative.substring(1)
|
||||||
|
}
|
||||||
|
if (relative.isEmpty()) {
|
||||||
|
processed++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val outFile = File(outDir, relative)
|
||||||
|
if (entry.isDirectory) {
|
||||||
|
outFile.mkdirs()
|
||||||
|
} else {
|
||||||
|
if (!entry.hasStream()) {
|
||||||
|
processed++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
outFile.parentFile?.mkdirs()
|
||||||
|
var ins: InputStream? = null
|
||||||
|
var bos: BufferedOutputStream? = null
|
||||||
|
try {
|
||||||
|
ins = sevenZFile.getInputStream(entry)
|
||||||
|
bos = BufferedOutputStream(FileOutputStream(outFile))
|
||||||
|
while (true) {
|
||||||
|
val read = ins.read(buffer)
|
||||||
|
if (read == -1) break
|
||||||
|
bos.write(buffer, 0, read)
|
||||||
|
if (shouldPrintProgress && totalBytes > 0) {
|
||||||
|
writtenBytes += read
|
||||||
|
printProgress(writtenBytes, totalBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bos.flush()
|
||||||
|
if (entry.size >= 0 && outFile.length() != entry.size) {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"Extracted file size mismatch for: ${entry.name}, expected=${entry.size}, actual=${outFile.length()}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try { bos?.close() } catch (_: Throwable) {}
|
||||||
|
try { ins?.close() } catch (_: Throwable) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldPrintProgress && totalBytes == 0L) {
|
||||||
|
val pct = processed * 100.0 / entriesCount
|
||||||
|
printProgress(pct)
|
||||||
|
}
|
||||||
|
processed++
|
||||||
|
}
|
||||||
|
return totalBytes
|
||||||
|
} finally {
|
||||||
|
try { sevenZFile?.close() } catch (_: Throwable) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalizePrefix(path: String): String {
|
||||||
|
var p = File(path).path.replace('\\', '/')
|
||||||
|
if (p.startsWith("/")) p = p.substring(1)
|
||||||
|
if (!p.endsWith("/")) p += "/"
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun trimLeadingSlash(s: String): String =
|
||||||
|
if (s.startsWith("/")) s.substring(1) else s
|
||||||
|
|
||||||
|
private fun printProgress(written: Long, total: Long) {
|
||||||
|
val pct = if (total > 0) written * 100.0 / total else 0.0
|
||||||
|
printProgress(pct)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printProgress(percent: Double) {
|
||||||
|
val width = 30
|
||||||
|
val filled = ((percent / 100.0) * width).toInt().coerceIn(0, width)
|
||||||
|
val bar = buildString {
|
||||||
|
append("[").append("#".repeat(filled)).append("-".repeat(width - filled)).append("]")
|
||||||
|
}
|
||||||
|
print("\rExtracting... $bar ${"%.2f".format(percent)}%")
|
||||||
|
System.out.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.autojs.build
|
||||||
|
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Signs @JvmOverloads constructor(project: Project, filePath: String = "${project.rootDir}/sign.properties") {
|
||||||
|
|
||||||
|
var isValid = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
val properties = Properties().also { props ->
|
||||||
|
File(filePath).takeIf { it.exists() }?.let { file ->
|
||||||
|
file.inputStream().use { props.load(it) }
|
||||||
|
isValid = props.isNotEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package org.autojs.build
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Gradle plugin that provides signing functionality.
|
||||||
|
*
|
||||||
|
* zh-CN: Gradle 签名功能插件.
|
||||||
|
*
|
||||||
|
* - `id`: "org.autojs.build.signs"
|
||||||
|
* - `implementationClass`: "org.autojs.build.SignsPlugin"
|
||||||
|
* - `displayName`: "AutoJs6 Signs Plugin"
|
||||||
|
* - `description`: "Provides signing helpers."
|
||||||
|
*
|
||||||
|
* Apply this plugin to your Android module's `build.gradle.kts`:
|
||||||
|
*
|
||||||
|
* zh-CN: 在 Android 模块的 `build.gradle.kts` 中应用此插件:<br>
|
||||||
|
*
|
||||||
|
* ```kts
|
||||||
|
* plugins {
|
||||||
|
* id("org.autojs.build.signs")
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* android {
|
||||||
|
* signingConfigs {
|
||||||
|
* if (signs.isValid) {
|
||||||
|
* create(buildTypeRelease) {
|
||||||
|
* storeFile = signs.properties["storeFile"]?.let { file(it as String) }
|
||||||
|
* keyPassword = signs.properties["keyPassword"] as String
|
||||||
|
* keyAlias = signs.properties["keyAlias"] as String
|
||||||
|
* storePassword = signs.properties["storePassword"] as String
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class SignsPlugin : Plugin<Project> {
|
||||||
|
override fun apply(project: Project) {
|
||||||
|
project.extensions.add("signs", Utils.newSigns(project))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
package org.autojs.build
|
||||||
|
|
||||||
|
import org.gradle.api.Action
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.Task
|
||||||
|
import org.gradle.api.file.CopySpec
|
||||||
|
import org.gradle.api.plugins.JavaPluginExtension
|
||||||
|
import org.gradle.api.tasks.compile.JavaCompile
|
||||||
|
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||||
|
import org.gradle.jvm.toolchain.JavaToolchainService
|
||||||
|
import org.gradle.kotlin.dsl.extra
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import java.util.zip.CRC32
|
||||||
|
|
||||||
|
object Utils {
|
||||||
|
|
||||||
|
const val FILE_EXTENSION_APK = "apk"
|
||||||
|
|
||||||
|
private object LOGGER {
|
||||||
|
const val LEVEL_INFO = 1
|
||||||
|
const val LEVEL_WARN = 2
|
||||||
|
const val LEVEL_ERROR = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
private var CURRENT_LOGGER_LEVEL = LOGGER.LEVEL_ERROR
|
||||||
|
|
||||||
|
fun newLibDeployer(project: Project, name: String, downloadUrl: String) = LibDeployer(project, name, downloadUrl)
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun newFormatted(title: String, contents: Collection<String> = emptyList(), subtitle: String? = null) = Formatted(title, contents, subtitle)
|
||||||
|
|
||||||
|
fun newVersions(project: Project) = Versions(project)
|
||||||
|
|
||||||
|
fun newSigns(project: Project) = Signs(project)
|
||||||
|
|
||||||
|
fun newProperties(project: Project) = BuildProperties.loadFrom(project)
|
||||||
|
|
||||||
|
fun hours2Millis(hour: Double) = hour * 3.6e6
|
||||||
|
|
||||||
|
fun getDateString(format: String, zone: String): String {
|
||||||
|
// e.g. May 23, 2011
|
||||||
|
return SimpleDateFormat(format, Locale.getDefault()).apply {
|
||||||
|
timeZone = TimeZone.getTimeZone(zone)
|
||||||
|
}.format(Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAssembleTaskName(flavorName: String, buildType: String) = "assemble${capitalize(flavorName)}${capitalize(buildType)}"
|
||||||
|
|
||||||
|
fun getAssembleFullTaskName(projectName: String, flavorName: String, buildType: String) = ":$projectName:${getAssembleTaskName(flavorName, buildType)}"
|
||||||
|
|
||||||
|
fun digestCRC32(file: File): String {
|
||||||
|
val fis = FileInputStream(file)
|
||||||
|
val buffer = ByteArray(4096)
|
||||||
|
var read: Int
|
||||||
|
|
||||||
|
return CRC32().let { o ->
|
||||||
|
while (fis.read(buffer).also { read = it } > 0) {
|
||||||
|
o.update(buffer, 0, read)
|
||||||
|
}
|
||||||
|
String.format("%08x", o.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一 "版本信息打印 + 部署 + 清理" 生命周期钩子.
|
||||||
|
*
|
||||||
|
* @param project 当前模块 Project
|
||||||
|
* @param projectDisplayName 用于打印的项目展示名 (默认用模块名)
|
||||||
|
* @param versionLines 版本信息行 (如 ["OpenCV: 4.2.0", "NDK: 21.1.6352462"])
|
||||||
|
* @param libsToDeploy 需要部署/清理的 LibDeployer 列表
|
||||||
|
* @param cleanupFlagKey gradle.ext 的布尔开关键 (如 "isCleanupPaddleOcr"), null 表示不参与清理逻辑
|
||||||
|
* @param extraFilesToDeleteOnClean clean 时额外需删除的相对路径
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun configureLibraryLifecycleHooks(
|
||||||
|
project: Project,
|
||||||
|
projectDisplayName: String = project.name,
|
||||||
|
versionLines: List<String> = emptyList(),
|
||||||
|
libsToDeploy: List<LibDeployer> = emptyList(),
|
||||||
|
cleanupFlagKey: String? = null,
|
||||||
|
extraFilesToDeleteOnClean: List<String> = listOf(".cxx"),
|
||||||
|
) {
|
||||||
|
val gradle = project.gradle
|
||||||
|
val onlyClean = AtomicBoolean(false)
|
||||||
|
|
||||||
|
// 单一监听器: 既判断 "是否纯 clean", 也负责非 clean 流程的打印与部署
|
||||||
|
gradle.taskGraph.addTaskExecutionGraphListener { graph ->
|
||||||
|
val all = graph.allTasks
|
||||||
|
val isOnlyClean = all.isNotEmpty() && all.all { it.name.contains("clean", ignoreCase = true) }
|
||||||
|
onlyClean.set(isOnlyClean)
|
||||||
|
|
||||||
|
if (!isOnlyClean) {
|
||||||
|
if (versionLines.isNotEmpty()) {
|
||||||
|
newFormatted("Version information for $projectDisplayName library", versionLines).print()
|
||||||
|
}
|
||||||
|
if (libsToDeploy.isNotEmpty()) {
|
||||||
|
libsToDeploy.forEach { it.deploy() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean 钩子 (显式 Java SAM, 避免 Kotlin/Groovy 重载歧义)
|
||||||
|
@Suppress("ObjectLiteralToLambda")
|
||||||
|
project.tasks.named("clean").configure(object : Action<Task> {
|
||||||
|
override fun execute(cleanTask: Task) {
|
||||||
|
cleanTask.doFirst {
|
||||||
|
project.delete(project.layout.buildDirectory)
|
||||||
|
extraFilesToDeleteOnClean.forEach { rel ->
|
||||||
|
project.delete(project.file(rel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未提供开关键则直接跳过清理逻辑
|
||||||
|
val key = cleanupFlagKey ?: return@doFirst
|
||||||
|
val cleanupEnabled = gradle.extra.require<Boolean>(key)
|
||||||
|
if (cleanupEnabled && onlyClean.get()) {
|
||||||
|
libsToDeploy.forEach { it.clean() }
|
||||||
|
} else {
|
||||||
|
val projectName = project.extensions.extraProperties.getOrNull<String>("projectName") ?: projectDisplayName
|
||||||
|
println("The library files of $projectName won't be cleaned up due to the configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册模板 APK 拷贝: 在指定 assemble 任务完成后, 将 universal APK 拷贝为 assets 模板.
|
||||||
|
*
|
||||||
|
* @param project 当前模块 Project
|
||||||
|
* @param taskName 构建任务名 (如 "assembleInrtRelease")
|
||||||
|
* @param srcDir APK 输出目录 (如 "build/outputs/apk/inrt/release")
|
||||||
|
* @param destDir 目标目录 (如 "src/main/assets-app")
|
||||||
|
* @param templateApkName 模板文件名 (如 "template.apk")
|
||||||
|
* @param universalNameFn 从版本名得出源 APK 名的函数 (默认 inrt-v<ver>-universal.apk)
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun registerTemplateApkCopy(
|
||||||
|
project: Project,
|
||||||
|
taskName: String = "assembleInrtRelease",
|
||||||
|
srcDir: String = "build/outputs/apk/inrt/release",
|
||||||
|
destDir: String = "src/main/assets-app",
|
||||||
|
templateApkName: String = "template.$FILE_EXTENSION_APK",
|
||||||
|
universalNameFn: (String) -> String = { ver -> "inrt-v${ver.replace(Regex("\\s"), "-").lowercase()}-universal.$FILE_EXTENSION_APK" },
|
||||||
|
) {
|
||||||
|
val versions = newVersions(project)
|
||||||
|
val versionName = versions.appVersionName
|
||||||
|
|
||||||
|
// 待所有项目评估完成后再定位并配置任务, 避免早期查找不到任务
|
||||||
|
project.gradle.projectsEvaluated {
|
||||||
|
val assembleTask = project.tasks.findByName(taskName)
|
||||||
|
if (assembleTask == null) {
|
||||||
|
println("$taskName doesn't exist in project ${project.name}")
|
||||||
|
return@projectsEvaluated
|
||||||
|
}
|
||||||
|
assembleTask.doLast {
|
||||||
|
@Suppress("ObjectLiteralToLambda")
|
||||||
|
project.copy(object : Action<CopySpec> {
|
||||||
|
override fun execute(spec: CopySpec) {
|
||||||
|
val srcFileName = universalNameFn(versionName)
|
||||||
|
val srcFile = project.file(File(srcDir, srcFileName))
|
||||||
|
require(srcFile.exists()) {
|
||||||
|
"Source file \"$srcFile\" doesn't exist"
|
||||||
|
}
|
||||||
|
|
||||||
|
spec.from(srcDir)
|
||||||
|
spec.into(destDir)
|
||||||
|
spec.include(srcFileName)
|
||||||
|
spec.rename(srcFileName, templateApkName)
|
||||||
|
|
||||||
|
val dstFile = project.file(File(destDir, templateApkName))
|
||||||
|
val overridden = dstFile.exists()
|
||||||
|
newFormatted(
|
||||||
|
"Copy template APK into assets", listOf(
|
||||||
|
"Source: $srcFile",
|
||||||
|
"Destination: $dstFile${if (overridden) " [overridden]" else ""}"
|
||||||
|
)
|
||||||
|
).print()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> org.gradle.api.plugins.ExtraPropertiesExtension.getOrNull(key: String): T? {
|
||||||
|
if (!has(key)) return null
|
||||||
|
val result = get(key)
|
||||||
|
require(result is T?) {
|
||||||
|
"The type of $key is ${result?.javaClass?.name}, but ${T::class.java.name} is required"
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> org.gradle.api.plugins.ExtraPropertiesExtension.require(key: String): T {
|
||||||
|
require(has(key)) {
|
||||||
|
"The key $key is not found in extra properties"
|
||||||
|
}
|
||||||
|
val result = get(key)
|
||||||
|
require(result is T) {
|
||||||
|
"The type of $key is ${result?.javaClass?.name}, but ${T::class.java.name} is required"
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun capitalize(s: String) = "${s[0].uppercase(Locale.getDefault())}${s.substring(1)}"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一为 Android 模块配置 Java/Kotlin 的目标版本:
|
||||||
|
* - Android.compileOptions.sourceCompatibility/targetCompatibility = versions.javaVersion
|
||||||
|
* - Kotlin 编译任务的 jvmTarget = versions.javaVersion 对应级别
|
||||||
|
*
|
||||||
|
* 注意:
|
||||||
|
* - Android 扩展在 projectsEvaluated 后稳定可得
|
||||||
|
* - Kotlin 任务用 tasks.configureEach 动态配置, 任务何时创建都能命中
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun configureJvmForAndroidModule(project: Project) {
|
||||||
|
val versions = newVersions(project)
|
||||||
|
project.logInfo("[JvmConv] Enter configureJvmForAndroidModule for module='${project.path}', javaVersion='${versions.javaVersion}', javaVersionString='${versions.javaVersionString}'")
|
||||||
|
|
||||||
|
val installer = {
|
||||||
|
project.logInfo("[JvmConv] Detected Android plugin in module='${project.path}', installing configuration")
|
||||||
|
|
||||||
|
// A) Java: 使用 Toolchain (模块级 + 任务级), 不要设置 --release
|
||||||
|
configureJavaToolchainLanguageLevel(project, versions.javaVersionInt)
|
||||||
|
configureJavaToolchainForAllJavaCompile(project, versions.javaVersionInt)
|
||||||
|
|
||||||
|
// B) Kotlin: 继续懒配置设置 jvmTarget (你之前已验证成功)
|
||||||
|
configureKotlinJvmTargetLazily(project, versions)
|
||||||
|
|
||||||
|
project.logInfo("[JvmConv] Installed Java toolchain (module+tasks) and Kotlin jvmTarget for '${project.path}'")
|
||||||
|
}
|
||||||
|
|
||||||
|
project.plugins.withId("com.android.application") { installer() }
|
||||||
|
project.plugins.withId("com.android.library") { installer() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模块级 Toolchain: 让 AGP/Gradle 知道本模块应使用的 JDK 语言级别
|
||||||
|
private fun configureJavaToolchainLanguageLevel(project: Project, target: Int) {
|
||||||
|
val javaExt = project.extensions.findByType(JavaPluginExtension::class.java)
|
||||||
|
if (javaExt == null) {
|
||||||
|
project.logWarn("[JvmConv] JavaPluginExtension not found in '${project.path}', skip module-level toolchain")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runCatching {
|
||||||
|
javaExt.toolchain.languageVersion.set(JavaLanguageVersion.of(target))
|
||||||
|
}.onSuccess {
|
||||||
|
project.logInfo("[JvmConv] Module-level toolchain languageVersion set to $target for '${project.path}'")
|
||||||
|
}.onFailure {
|
||||||
|
project.logError("[JvmConv] Set module-level toolchain languageVersion failed on '${project.path}': ${it.message}", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务级 Toolchain: 对所有 JavaCompile 指定 javaCompiler, 且不要设置 --release (AGP 禁止)
|
||||||
|
private fun configureJavaToolchainForAllJavaCompile(project: Project, target: Int) {
|
||||||
|
val toolchains = runCatching {
|
||||||
|
project.extensions.getByType(JavaToolchainService::class.java)
|
||||||
|
}.onFailure {
|
||||||
|
project.logError("[JvmConv] JavaToolchainService not available on '${project.path}': ${it.message}", it)
|
||||||
|
}.getOrNull() ?: return
|
||||||
|
|
||||||
|
val langVersion = JavaLanguageVersion.of(target)
|
||||||
|
project.tasks.withType(JavaCompile::class.java).configureEach(object : Action<JavaCompile> {
|
||||||
|
override fun execute(t: JavaCompile) {
|
||||||
|
runCatching {
|
||||||
|
val compilerProvider = toolchains.compilerFor { languageVersion.set(langVersion) }
|
||||||
|
t.javaCompiler.set(compilerProvider)
|
||||||
|
project.logInfo("[JvmConv] JavaCompile '${t.path}' uses toolchain JDK $target (no --release)")
|
||||||
|
}.onFailure {
|
||||||
|
project.logError("[JvmConv] '${t.path}' set javaCompiler(toolchain) failed: ${it.message}", it)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不要设置 t.options.release, AGP 会报错阻止
|
||||||
|
// 也不强制改写 sourceCompatibility/targetCompatibility, 交给 AGP + toolchain 统一管理
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为 KotlinCompile 任务设置 jvmTarget: 使用 configureEach, 任务实现时自动应用; 兼容新旧 API
|
||||||
|
private fun configureKotlinJvmTargetLazily(project: Project, versions: Versions) {
|
||||||
|
val desiredStr = versions.javaVersionString // 例如 "22"
|
||||||
|
val desiredEnumName = "JVM_${desiredStr}" // 例如 "JVM_22"
|
||||||
|
project.logInfo("[JvmConv] Will configure Kotlin jvmTarget lazily to '$desiredEnumName' in '${project.path}'")
|
||||||
|
|
||||||
|
project.tasks.configureEach(object : Action<Task> {
|
||||||
|
override fun execute(task: Task) {
|
||||||
|
if (!isKotlinCompileTask(task, project)) return
|
||||||
|
|
||||||
|
project.logInfo("[JvmConv] <KotlinTask> '${task.path}' class='${task.javaClass.name}'")
|
||||||
|
|
||||||
|
// 优先尝试 Kotlin 2.x: compilerOptions.jvmTarget(Property<JvmTarget>)
|
||||||
|
val compilerOptions = runCatching {
|
||||||
|
task.javaClass.methods.firstOrNull { it.name == "getCompilerOptions" && it.parameterTypes.isEmpty() }
|
||||||
|
?.also { project.logInfo("[JvmConv] '${task.path}' found method: ${it.toGenericString()}") }
|
||||||
|
?.invoke(task)
|
||||||
|
}.onFailure {
|
||||||
|
project.logError("[JvmConv] '${task.path}' getCompilerOptions() failed", it)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
if (compilerOptions != null) {
|
||||||
|
project.logInfo("[JvmConv] '${task.path}' compilerOptions class='${compilerOptions.javaClass.name}'")
|
||||||
|
val jvmTargetProp = runCatching {
|
||||||
|
compilerOptions.javaClass.methods.firstOrNull { it.name == "getJvmTarget" && it.parameterTypes.isEmpty() }
|
||||||
|
?.also { project.logInfo("[JvmConv] '${task.path}' found method: ${it.toGenericString()}") }
|
||||||
|
?.invoke(compilerOptions)
|
||||||
|
}.onFailure {
|
||||||
|
project.logError("[JvmConv] '${task.path}' compilerOptions.getJvmTarget() failed", it)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
if (jvmTargetProp != null) {
|
||||||
|
project.logInfo("[JvmConv] '${task.path}' jvmTarget property class='${jvmTargetProp.javaClass.name}'")
|
||||||
|
val propSet = jvmTargetProp.javaClass.methods.firstOrNull {
|
||||||
|
it.name == "set" && it.parameterTypes.size == 1
|
||||||
|
}
|
||||||
|
project.logInfo("[JvmConv] '${task.path}' Property.set method: ${propSet?.toGenericString() ?: "NOT_FOUND"}")
|
||||||
|
|
||||||
|
val jvmTargetEnum = runCatching {
|
||||||
|
val enumClass = Class.forName("org.jetbrains.kotlin.gradle.dsl.JvmTarget")
|
||||||
|
enumClass.enumConstants?.firstOrNull { it.toString().equals(desiredEnumName, ignoreCase = true) }
|
||||||
|
?.also { project.logInfo("[JvmConv] '${task.path}' resolved enum '$desiredEnumName' = $it") }
|
||||||
|
}.onFailure {
|
||||||
|
project.logError("[JvmConv] '${task.path}' resolve enum '$desiredEnumName' failed", it)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
if (propSet != null && jvmTargetEnum != null) {
|
||||||
|
runCatching { propSet.invoke(jvmTargetProp, jvmTargetEnum) }
|
||||||
|
.onSuccess {
|
||||||
|
project.logInfo("[JvmConv] '${task.path}' jvmTarget set via compilerOptions to '$desiredEnumName'")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
.onFailure {
|
||||||
|
project.logWarn("[JvmConv] '${task.path}' jvmTarget set via compilerOptions failed, will try legacy API.\ne: $it")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
project.logWarn("[JvmConv] '${task.path}' compilerOptions path unavailable (propSet=$propSet, enum=$jvmTargetEnum), trying legacy kotlinOptions")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
project.logWarn("[JvmConv] '${task.path}' compilerOptions.getJvmTarget() returned null, trying legacy kotlinOptions")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
project.logWarn("[JvmConv] '${task.path}' compilerOptions not found, trying legacy kotlinOptions")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧 API: kotlinOptions.setJvmTarget(String)
|
||||||
|
val kotlinOptions = runCatching {
|
||||||
|
task.javaClass.methods.firstOrNull { it.name == "getKotlinOptions" && it.parameterTypes.isEmpty() }
|
||||||
|
?.also { project.logInfo("[JvmConv] '${task.path}' found method: ${it.toGenericString()}") }
|
||||||
|
?.invoke(task)
|
||||||
|
}.onFailure {
|
||||||
|
project.logError("[JvmConv] '${task.path}' getKotlinOptions() failed", it)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
if (kotlinOptions != null) {
|
||||||
|
project.logInfo("[JvmConv] '${task.path}' kotlinOptions class='${kotlinOptions.javaClass.name}'")
|
||||||
|
val setJvmTarget = kotlinOptions.javaClass.methods.firstOrNull {
|
||||||
|
it.name == "setJvmTarget" && it.parameterTypes.size == 1 && it.parameterTypes[0] == String::class.java
|
||||||
|
}
|
||||||
|
project.logInfo("[JvmConv] '${task.path}' kotlinOptions.setJvmTarget method: ${setJvmTarget?.toGenericString() ?: "NOT_FOUND"}")
|
||||||
|
|
||||||
|
runCatching { setJvmTarget?.invoke(kotlinOptions, desiredStr) }
|
||||||
|
.onSuccess { project.logInfo("[JvmConv] '${task.path}' jvmTarget set via kotlinOptions to '$desiredStr'") }
|
||||||
|
.onFailure { project.logError("[JvmConv] '${task.path}' jvmTarget set via kotlinOptions failed", it) }
|
||||||
|
} else {
|
||||||
|
project.logWarn("[JvmConv] '${task.path}' kotlinOptions not found; jvmTarget not configured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isKotlinCompileTask(task: Task, project: Project): Boolean {
|
||||||
|
val name = task.name.lowercase(Locale.getDefault())
|
||||||
|
val clsName = task.javaClass.name
|
||||||
|
val hasCompilerOptions = task.javaClass.methods.any { it.name == "getCompilerOptions" && it.parameterTypes.isEmpty() }
|
||||||
|
val hasKotlinOptions = task.javaClass.methods.any { it.name == "getKotlinOptions" && it.parameterTypes.isEmpty() }
|
||||||
|
val matched = when {
|
||||||
|
name.contains("kotlin") && name.contains("compile") -> true
|
||||||
|
clsName.contains("Kotlin", ignoreCase = true) && clsName.contains("Compile", ignoreCase = true) -> true
|
||||||
|
hasCompilerOptions || hasKotlinOptions -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
if (matched) {
|
||||||
|
project.logInfo("[JvmConv] Task matched as KotlinCompile: path='${task.path}', class='$clsName'")
|
||||||
|
}
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Project.logInfo(msg: String) {
|
||||||
|
if (CURRENT_LOGGER_LEVEL <= LOGGER.LEVEL_INFO) {
|
||||||
|
logger.lifecycle(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Project.logWarn(msg: String) {
|
||||||
|
if (CURRENT_LOGGER_LEVEL <= LOGGER.LEVEL_WARN) {
|
||||||
|
logger.warn(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Project.logError(msg: String, t: Throwable? = null) {
|
||||||
|
if (CURRENT_LOGGER_LEVEL <= LOGGER.LEVEL_ERROR) {
|
||||||
|
if (t != null) logger.error(msg, t) else logger.error(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package org.autojs.build
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Gradle plugin that provides basic utilities.
|
||||||
|
*
|
||||||
|
* zh-CN: Gradle 基础工具插件.
|
||||||
|
*
|
||||||
|
* - `id`: "org.autojs.build.utils"
|
||||||
|
* - `implementationClass`: "org.autojs.build.UtilsPlugin"
|
||||||
|
* - `displayName`: "AutoJs6 Build Utils Plugin"
|
||||||
|
* - `description`: "Provides utilities for downloading, extracting archives, and version helpers."
|
||||||
|
*
|
||||||
|
* Apply this plugin to your Android module's `build.gradle.kts`:
|
||||||
|
*
|
||||||
|
* zh-CN: 在 Android 模块的 `build.gradle.kts` 中应用此插件:<br>
|
||||||
|
*
|
||||||
|
* ```kts
|
||||||
|
* plugins {
|
||||||
|
* id("org.autojs.build.utils")
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* utils.digestCRC32(file("some/file.zip"))
|
||||||
|
* utils.getDateString("MMM d, yyyy", "GMT+08:00")
|
||||||
|
* utils.hours2Millis(0.75)
|
||||||
|
* utils.compareVersionStrings("6.3.2 beta", "6.3.2 alpha4) > 0
|
||||||
|
*
|
||||||
|
* utils.registerTemplateApkCopy(project)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class UtilsPlugin : Plugin<Project> {
|
||||||
|
override fun apply(project: Project) {
|
||||||
|
// project.extensions.extraProperties.set("utils", Utils)
|
||||||
|
project.extensions.add("utils", Utils)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
package org.autojs.build
|
||||||
|
|
||||||
|
import org.autojs.build.Utils.getOrNull
|
||||||
|
import org.gradle.api.Action
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.JavaVersion
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.execution.TaskExecutionGraph
|
||||||
|
import org.gradle.kotlin.dsl.extra
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
import kotlin.text.RegexOption.IGNORE_CASE
|
||||||
|
|
||||||
|
class Versions @JvmOverloads constructor(
|
||||||
|
private val project: Project,
|
||||||
|
filePath: String = "${project.rootDir}/version.properties",
|
||||||
|
) {
|
||||||
|
private val gradle = project.gradle
|
||||||
|
private val logger = project.logger
|
||||||
|
|
||||||
|
private val currentVersionInt = JavaVersion.current().majorVersion.toInt()
|
||||||
|
|
||||||
|
private val bp = BuildProperties.loadFrom(filePath)
|
||||||
|
|
||||||
|
private val javaVersionMinSuggested: Int = bp.requireInt("JAVA_VERSION_MIN_SUGGESTED")
|
||||||
|
private val javaVersionMaxSupported: Int = bp.requireInt("JAVA_VERSION_MAX_SUPPORTED")
|
||||||
|
|
||||||
|
private var isBuildNumberAutoIncremented = false
|
||||||
|
private val minBuildTimeGap = Utils.hours2Millis(0.75)
|
||||||
|
|
||||||
|
private val isBuildGapEnough
|
||||||
|
get() = Date().time - bp["BUILD_TIME"].toLong() > minBuildTimeGap
|
||||||
|
|
||||||
|
val sdkVersionMin = bp.requireInt("MIN_SDK_VERSION")
|
||||||
|
val sdkVersionTarget = bp.requireInt("TARGET_SDK_VERSION")
|
||||||
|
val sdkVersionTargetInrt = bp.requireInt("TARGET_SDK_VERSION_INRT")
|
||||||
|
val sdkVersionCompile = bp.requireInt("COMPILE_SDK_VERSION")
|
||||||
|
val appVersionName = bp.requireString("VERSION_NAME")
|
||||||
|
val appVersionCode = bp.requireInt("VERSION_BUILD")
|
||||||
|
val vscodeExtRequiredVersion = bp.requireString("VSCODE_EXT_REQUIRED_VERSION")
|
||||||
|
|
||||||
|
private val javaVersionMinSupported: Int = bp.requireInt("JAVA_VERSION_MIN_SUPPORTED")
|
||||||
|
|
||||||
|
var javaVersionInt by Delegates.notNull<Int>()
|
||||||
|
var javaVersionInfoSuffix by Delegates.notNull<String>()
|
||||||
|
|
||||||
|
val javaVersion: JavaVersion
|
||||||
|
get() = JavaVersion.toVersion(javaVersionInt)
|
||||||
|
|
||||||
|
val javaVersionString: String
|
||||||
|
get() = javaVersion.toString()
|
||||||
|
|
||||||
|
init {
|
||||||
|
validateCurrentVersion()
|
||||||
|
determineJavaVersion().also { (javaVersionInt, javaVersionInfoSuffix) ->
|
||||||
|
this.javaVersionInt = javaVersionInt
|
||||||
|
this.javaVersionInfoSuffix = javaVersionInfoSuffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateCurrentVersion() {
|
||||||
|
if (gradle.extra.getOrNull<Boolean>(VALIDATED_EXTRA_KEY) == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (currentVersionInt < javaVersionMinSupported) {
|
||||||
|
throw GradleException("Current Gradle JDK version [$currentVersionInt] does not meet the minimum requirement which [$javaVersionMinSupported] is needed.")
|
||||||
|
}
|
||||||
|
if (currentVersionInt < javaVersionMinSuggested) {
|
||||||
|
val suffix = if (javaVersionMaxSupported > 0) " (but not higher than [$javaVersionMaxSupported])" else ""
|
||||||
|
logger.error("It is recommended to upgrade current Gradle JDK version [$currentVersionInt] to [$javaVersionMinSuggested] or higher$suffix.")
|
||||||
|
}
|
||||||
|
if (currentVersionInt > javaVersionMaxSupported) {
|
||||||
|
val suffix = if (javaVersionMaxSupported > javaVersionMinSuggested) " or lower (but not lower than [$javaVersionMinSuggested])" else ""
|
||||||
|
logger.error("It is recommended to downgrade current Gradle JDK version [$currentVersionInt] to [$javaVersionMaxSupported]$suffix, as Gradle may be not compatible with JDK [$currentVersionInt] for now.")
|
||||||
|
}
|
||||||
|
gradle.extra.set(VALIDATED_EXTRA_KEY, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun determineJavaVersion(): Pair<Int, String> {
|
||||||
|
var javaVersionInfoSuffix = ""
|
||||||
|
|
||||||
|
gradle.extra.getOrNull<Int>("javaVersionOverriddenByUser")?.let {
|
||||||
|
javaVersionInfoSuffix += " [user-specified]"
|
||||||
|
return it to javaVersionInfoSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
var versionInt = JavaVersion.current().majorVersion.toInt()
|
||||||
|
|
||||||
|
run tryAdjustJavaVersionByKotlinJvmTarget@{
|
||||||
|
var isJvmCoercive = false
|
||||||
|
|
||||||
|
while (versionInt > javaVersionMinSupported) {
|
||||||
|
if (isJvmTargetAvailable(versionInt)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
versionInt -= 1
|
||||||
|
isJvmCoercive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isJvmCoercive) {
|
||||||
|
javaVersionInfoSuffix += " [coercive-jvm-downgraded]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gradle.extra.getOrNull<Int>("javaVersionCoercedByGradle")?.let {
|
||||||
|
if (versionInt > it) {
|
||||||
|
versionInt = it
|
||||||
|
javaVersionInfoSuffix += " [coercive-gradle-downgraded]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return versionInt to javaVersionInfoSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isJvmTargetAvailable(target: Int): Boolean {
|
||||||
|
try {
|
||||||
|
// Weak dependency with Kotlin plugin: Use reflection to check if JvmTarget is available;
|
||||||
|
// if reflection fails, treat it as available (to avoid incorrect downgrading).
|
||||||
|
// zh-CN: 与 Kotlin 插件弱依赖: 反射判断 JvmTarget 是否可用; 反射失败则视为可用 (避免误降级).
|
||||||
|
val cls = Class.forName("org.jetbrains.kotlin.gradle.dsl.JvmTarget")
|
||||||
|
cls.enumConstants?.let { values ->
|
||||||
|
return values.any { it?.toString().equals("JVM_$target", true) }
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
logger.error("Failed to check JVM target availability: $e")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(propertyName: String) = bp[propertyName]
|
||||||
|
|
||||||
|
operator fun get(propertyInfo: List<String>) = bp[propertyInfo]
|
||||||
|
|
||||||
|
fun showInfo() {
|
||||||
|
val title = "Version information for AutoJs6 app library"
|
||||||
|
|
||||||
|
val infoVerName = "Version name: $appVersionName"
|
||||||
|
val infoVerCode = "Version code: ${if (isBuildNumberAutoIncremented) "${appVersionCode + 1} [auto-incremented]" else appVersionCode}"
|
||||||
|
val infoVerSdk = "SDK versions: min [$sdkVersionMin] / target [$sdkVersionTarget] / compile [$sdkVersionCompile]"
|
||||||
|
val infoVerJava = "Java version: $javaVersion${
|
||||||
|
when {
|
||||||
|
gradle.extra.getOrNull<Boolean>("isHideConsoleInfoHintSuffix") == true -> ""
|
||||||
|
else -> javaVersionInfoSuffix
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
|
||||||
|
val maxLength = arrayOf(title, infoVerName, infoVerCode, infoVerSdk, infoVerJava).maxOf { it.length }
|
||||||
|
|
||||||
|
arrayOf(
|
||||||
|
"=".repeat(maxLength),
|
||||||
|
title,
|
||||||
|
"-".repeat(maxLength),
|
||||||
|
infoVerName,
|
||||||
|
infoVerCode,
|
||||||
|
infoVerSdk,
|
||||||
|
infoVerJava,
|
||||||
|
"=".repeat(maxLength),
|
||||||
|
"",
|
||||||
|
).forEach { println(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleIfNeeded(project: Project, flavorName: String, targetBuildType: List<String>) {
|
||||||
|
project.gradle.taskGraph.whenReady(object : Action<TaskExecutionGraph> {
|
||||||
|
override fun execute(taskGraph: TaskExecutionGraph) {
|
||||||
|
for (buildType in targetBuildType) {
|
||||||
|
if (taskGraph.hasTask(Utils.getAssembleFullTaskName(project.name, flavorName, buildType))) {
|
||||||
|
return appendToTask(project, flavorName, buildType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return showInfo()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun appendToTask(project: Project, flavorName: String, buildType: String) {
|
||||||
|
project.tasks.getByName(Utils.getAssembleTaskName(flavorName, buildType)).doLast {
|
||||||
|
updateProperties()
|
||||||
|
println()
|
||||||
|
showInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateProperties() {
|
||||||
|
val propsPath = bp.path
|
||||||
|
val props = Properties().apply {
|
||||||
|
FileInputStream(propsPath).use { load(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBuildGapEnough) {
|
||||||
|
val isBuildAppRelease = gradle.startParameter.taskNames.any {
|
||||||
|
it.contains(Regex("^(:?app:)?assemble(app|inrt)release", IGNORE_CASE))
|
||||||
|
}
|
||||||
|
if (!isBuildAppRelease) {
|
||||||
|
props["VERSION_BUILD"] = "${appVersionCode + 1}"
|
||||||
|
isBuildNumberAutoIncremented = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props["BUILD_TIME"] = "${Date().time}"
|
||||||
|
|
||||||
|
FileOutputStream(propsPath).use { out ->
|
||||||
|
props.store(out, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val VALIDATED_EXTRA_KEY = "org.autojs.build.Versions.currentValidated"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package org.autojs.build
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Gradle plugin that provides version management functionality.
|
||||||
|
*
|
||||||
|
* zh-CN: Gradle 版本管理插件.
|
||||||
|
*
|
||||||
|
* - `id`: "org.autojs.build.versions"
|
||||||
|
* - `implementationClass`: "org.autojs.build.VersionsPlugin"
|
||||||
|
* - `displayName`: "AutoJs6 Versions Plugin"
|
||||||
|
* - `description`: "Provides version helpers."
|
||||||
|
*
|
||||||
|
* Apply this plugin to your Android module's `build.gradle.kts`:
|
||||||
|
*
|
||||||
|
* zh-CN: 在 Android 模块的 `build.gradle.kts` 中应用此插件:<br>
|
||||||
|
*
|
||||||
|
* ```kts
|
||||||
|
* plugins {
|
||||||
|
* id("org.autojs.build.versions")
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* versions.appVersionName
|
||||||
|
* versions.appVersionCode
|
||||||
|
* versions.sdkVersionCompile
|
||||||
|
* versions.sdkVersionMin
|
||||||
|
* versions.sdkVersionTarget
|
||||||
|
* versions.sdkVersionTargetInrt
|
||||||
|
* versions.vscodeExtRequiredVersion
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class VersionsPlugin : Plugin<Project> {
|
||||||
|
override fun apply(project: Project) {
|
||||||
|
project.extensions.add("versions", Utils.newVersions(project))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
apply plugin: 'com.android.library'
|
plugins {
|
||||||
apply plugin: 'org.jetbrains.kotlin.android'
|
id 'org.autojs.build.utils'
|
||||||
apply from: '../utils.build.gradle'
|
id 'org.autojs.build.properties'
|
||||||
|
id 'com.android.library'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
projectName = "Image Quantization"
|
projectName = "Image Quantization"
|
||||||
}
|
}
|
||||||
|
|
||||||
def versions = Utils.newVersions(project)
|
|
||||||
|
|
||||||
def versionMap = [
|
def versionMap = [
|
||||||
"libimagequant": "2.17.0",
|
"libimagequant": "2.17.0",
|
||||||
"libpng" : "1.6.49",
|
"libpng" : "1.6.49",
|
||||||
|
|
||||||
"MIN_SDK" : versions["MIN_SDK"] as Integer,
|
"MIN_SDK" : props["MIN_SDK"] as Integer,
|
||||||
"COMPILE_SDK" : versions["COMPILE_SDK"] as Integer,
|
"COMPILE_SDK" : props["COMPILE_SDK"] as Integer,
|
||||||
"TARGET_SDK" : versions["TARGET_SDK"] as Integer,
|
"TARGET_SDK" : props["TARGET_SDK"] as Integer,
|
||||||
|
|
||||||
"NDK" : versions["IMAGE_QUANT/NDK"],
|
"NDK" : props["IMAGE_QUANT/NDK"],
|
||||||
"CMAKE" : versions["IMAGE_QUANT/CMAKE"],
|
"CMAKE" : props["IMAGE_QUANT/CMAKE"],
|
||||||
]
|
]
|
||||||
|
|
||||||
def nameMap = [
|
def nameMap = [
|
||||||
@@ -26,6 +27,13 @@ def nameMap = [
|
|||||||
"CMAKE" : "Cmake",
|
"CMAKE" : "Cmake",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
utils.configureLibraryLifecycleHooks(project, nameMap.PROJECT, [
|
||||||
|
"libimagequant",
|
||||||
|
"libpng",
|
||||||
|
"NDK",
|
||||||
|
"CMAKE",
|
||||||
|
].collect { "${nameMap[it] ?: it}: ${versionMap[it]}" }, Collections.emptyList(), null, [".cxx"])
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
||||||
namespace = "org.pngquant"
|
namespace = "org.pngquant"
|
||||||
@@ -75,20 +83,5 @@ android {
|
|||||||
version versionMap.CMAKE
|
version versionMap.CMAKE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clean.doFirst {
|
|
||||||
delete project.layout.buildDirectory
|
|
||||||
['.cxx'].forEach { delete file(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
gradle.taskGraph.whenReady { taskGraph ->
|
|
||||||
if (taskGraph.allTasks.any { it.name == "clean" }) return
|
|
||||||
|
|
||||||
Utils.newFormatted("Version information for ${nameMap.PROJECT} Library", [
|
|
||||||
"libimagequant",
|
|
||||||
"libpng",
|
|
||||||
"NDK",
|
|
||||||
"CMAKE",
|
|
||||||
].collect { "${nameMap[it] ?: it}: ${versionMap[it]}" }).print()
|
|
||||||
}
|
|
||||||
+18
-35
@@ -6,25 +6,24 @@
|
|||||||
* Modified by SuperMonster003 as of Sep 4, 2023.
|
* Modified by SuperMonster003 as of Sep 4, 2023.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
apply {
|
plugins {
|
||||||
plugin 'com.android.library'
|
id 'org.autojs.build.utils'
|
||||||
plugin 'org.jetbrains.kotlin.android'
|
id 'org.autojs.build.properties'
|
||||||
from '../utils.build.gradle'
|
id 'com.android.library'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
projectName = "Paddle OCR"
|
projectName = "Paddle OCR"
|
||||||
}
|
}
|
||||||
|
|
||||||
def versions = Utils.newVersions(project)
|
|
||||||
|
|
||||||
def versionMap = [
|
def versionMap = [
|
||||||
"MIN_SDK" : versions["MIN_SDK"] as Integer,
|
"MIN_SDK" : props["MIN_SDK"] as Integer,
|
||||||
"COMPILE_SDK": versions["COMPILE_SDK"] as Integer,
|
"COMPILE_SDK": props["COMPILE_SDK"] as Integer,
|
||||||
"TARGET_SDK" : versions["TARGET_SDK"] as Integer,
|
"TARGET_SDK" : props["TARGET_SDK"] as Integer,
|
||||||
"NDK" : versions["PADDLE_OCR/NDK"],
|
"NDK" : props["PADDLE_OCR/NDK"],
|
||||||
"CMAKE" : versions["PADDLE_OCR/CMAKE"],
|
"CMAKE" : props["PADDLE_OCR/CMAKE"],
|
||||||
"OPENCV" : versions["PADDLE_OCR/OPENCV"],
|
"OPENCV" : props["PADDLE_OCR/OPENCV"],
|
||||||
]
|
]
|
||||||
|
|
||||||
def nameMap = [
|
def nameMap = [
|
||||||
@@ -43,12 +42,18 @@ def libsToDeploy = [
|
|||||||
// ! Download the archive for source code of OpenCV (defaults to 4.2.0).
|
// ! Download the archive for source code of OpenCV (defaults to 4.2.0).
|
||||||
// ! Not matching the version in Auto.js (e.g., 4.8.0) will cause conflicts.
|
// ! Not matching the version in Auto.js (e.g., 4.8.0) will cause conflicts.
|
||||||
// ! Adjust the corresponding content in version.properties as needed.
|
// ! Adjust the corresponding content in version.properties as needed.
|
||||||
Utils.newLibDeployer(project, nameMap.OPENCV, "https://github.com/opencv/opencv/releases/download" +
|
utils.newLibDeployer(project, nameMap.OPENCV, "https://github.com/opencv/opencv/releases/download" +
|
||||||
"/${versionMap.OPENCV}/opencv-${versionMap.OPENCV}-android-sdk.zip")
|
"/${versionMap.OPENCV}/opencv-${versionMap.OPENCV}-android-sdk.zip")
|
||||||
.setSourceDir("/OpenCV-android-sdk/sdk/native/")
|
.setSourceDir("/OpenCV-android-sdk/sdk/native/")
|
||||||
.setDestDir("/src/sdk/native/")
|
.setDestDir("/src/sdk/native/")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
utils.configureLibraryLifecycleHooks(project, nameMap.PROJECT, [
|
||||||
|
"OPENCV",
|
||||||
|
"NDK",
|
||||||
|
"CMAKE",
|
||||||
|
].collect { "${nameMap[it]}: ${versionMap[it]}" }, libsToDeploy, "isCleanupPaddleOcr", [".cxx"])
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
||||||
namespace = "com.baidu.paddle.lite.ocr"
|
namespace = "com.baidu.paddle.lite.ocr"
|
||||||
@@ -148,25 +153,3 @@ dependencies {
|
|||||||
implementation libs.preference.ktx
|
implementation libs.preference.ktx
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clean.doFirst {
|
|
||||||
delete project.layout.buildDirectory
|
|
||||||
['.cxx'].forEach { delete file(it) }
|
|
||||||
if (gradle.ext["isCleanupPaddleOcr"] as Boolean) {
|
|
||||||
libsToDeploy.forEach { it.clean() }
|
|
||||||
} else {
|
|
||||||
println("The library files of ${project.ext["projectName"]} won't be cleaned up due to the configuration")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gradle.taskGraph.whenReady { taskGraph ->
|
|
||||||
if (taskGraph.allTasks.every { it.name == "clean" }) return
|
|
||||||
|
|
||||||
Utils.newFormatted("Version information for ${nameMap.PROJECT} library", [
|
|
||||||
"OPENCV",
|
|
||||||
"NDK",
|
|
||||||
"CMAKE",
|
|
||||||
].collect { "${nameMap[it]}: ${versionMap[it]}" }).print()
|
|
||||||
|
|
||||||
libsToDeploy.forEach { it.deploy() }
|
|
||||||
}
|
|
||||||
@@ -10,10 +10,11 @@ cmake_minimum_required(VERSION 3.4.1)
|
|||||||
# CMake builds them for you. Gradle automatically packages shared libraries with
|
# CMake builds them for you. Gradle automatically packages shared libraries with
|
||||||
# your APK.
|
# your APK.
|
||||||
|
|
||||||
set(PaddleLite_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../PaddleLite")
|
get_filename_component(MODULE_DIR "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)
|
||||||
|
set(PaddleLite_DIR "${MODULE_DIR}/PaddleLite")
|
||||||
include_directories(${PaddleLite_DIR}/cxx/include)
|
include_directories(${PaddleLite_DIR}/cxx/include)
|
||||||
|
|
||||||
set(OpenCV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../src/sdk/native/jni")
|
set(OpenCV_DIR "${MODULE_DIR}/src/sdk/native/jni")
|
||||||
message(STATUS "opencv dir: ${OpenCV_DIR}")
|
message(STATUS "opencv dir: ${OpenCV_DIR}")
|
||||||
find_package(OpenCV REQUIRED)
|
find_package(OpenCV REQUIRED)
|
||||||
message(STATUS "OpenCV libraries: ${OpenCV_LIBS}")
|
message(STATUS "OpenCV libraries: ${OpenCV_LIBS}")
|
||||||
|
|||||||
+31
-43
@@ -4,26 +4,27 @@
|
|||||||
* Created by SuperMonster003 on Sep 19, 2024.
|
* Created by SuperMonster003 on Sep 19, 2024.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
apply plugin: 'com.android.library'
|
plugins {
|
||||||
apply plugin: 'org.jetbrains.kotlin.android'
|
id 'org.autojs.build.utils'
|
||||||
apply plugin: 'kotlin-parcelize'
|
id 'org.autojs.build.properties'
|
||||||
apply from: '../utils.build.gradle'
|
id 'com.android.library'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
id 'kotlin-parcelize'
|
||||||
|
}
|
||||||
|
|
||||||
ext["projectName"] = "Rapid OCR"
|
ext["projectName"] = "Rapid OCR"
|
||||||
|
|
||||||
def versions = Utils.newVersions(project)
|
|
||||||
|
|
||||||
def versionMap = [
|
def versionMap = [
|
||||||
"OFFICIAL_NAME" : "1.3.0", /* From original build.gradle file. */
|
"OFFICIAL_NAME" : "1.3.0", /* From original build.gradle file. */
|
||||||
"MIN_SDK" : versions["MIN_SDK"] as Integer,
|
"MIN_SDK" : props["MIN_SDK"] as Integer,
|
||||||
"COMPILE_SDK" : versions["COMPILE_SDK"] as Integer,
|
"COMPILE_SDK" : props["COMPILE_SDK"] as Integer,
|
||||||
"TARGET_SDK" : versions["TARGET_SDK"] as Integer,
|
"TARGET_SDK" : props["TARGET_SDK"] as Integer,
|
||||||
"NDK" : versions["RAPID_OCR/NDK"],
|
"NDK" : props["RAPID_OCR/NDK"],
|
||||||
"CMAKE" : versions["RAPID_OCR/CMAKE"],
|
"CMAKE" : props["RAPID_OCR/CMAKE"],
|
||||||
"OPENCV_MOBILE" : versions["RAPID_OCR/OPENCV_MOBILE"],
|
"OPENCV_MOBILE" : props["RAPID_OCR/OPENCV_MOBILE"],
|
||||||
"OPENCV_MOBILE_LABEL": versions["RAPID_OCR/OPENCV_MOBILE_LABEL"],
|
"OPENCV_MOBILE_LABEL": props["RAPID_OCR/OPENCV_MOBILE_LABEL"],
|
||||||
"ONNX" : versions["RAPID_OCR/ONNX"],
|
"ONNX" : props["RAPID_OCR/ONNX"],
|
||||||
"ONNX_RUNTIME" : versions["RAPID_OCR/ONNX_RUNTIME"],
|
"ONNX_RUNTIME" : props["RAPID_OCR/ONNX_RUNTIME"],
|
||||||
]
|
]
|
||||||
|
|
||||||
def nameMap = [
|
def nameMap = [
|
||||||
@@ -37,20 +38,29 @@ def nameMap = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
def libsToDeploy = [
|
def libsToDeploy = [
|
||||||
Utils.newLibDeployer(project, nameMap.OPENCV_MOBILE, "https://github.com/nihui/opencv-mobile/releases/download" +
|
utils.newLibDeployer(project, nameMap.OPENCV_MOBILE, "https://github.com/nihui/opencv-mobile/releases/download" +
|
||||||
"/v${versionMap.OPENCV_MOBILE_LABEL}/opencv-mobile-${versionMap.OPENCV_MOBILE}-android.zip")
|
"/v${versionMap.OPENCV_MOBILE_LABEL}/opencv-mobile-${versionMap.OPENCV_MOBILE}-android.zip")
|
||||||
.setSourceDir("/opencv-mobile-${versionMap.OPENCV_MOBILE}-android/sdk/native/")
|
.setSourceDir("/opencv-mobile-${versionMap.OPENCV_MOBILE}-android/sdk/native/")
|
||||||
.setDestDir("/src/sdk/native/"),
|
.setDestDir("/src/sdk/native/"),
|
||||||
Utils.newLibDeployer(project, nameMap.ONNX, "https://github.com/RapidAI/RapidOcrOnnx/releases/download" +
|
utils.newLibDeployer(project, nameMap.ONNX, "https://github.com/RapidAI/RapidOcrOnnx/releases/download" +
|
||||||
"/${versionMap.ONNX}/Project_RapidOcrOnnx-${versionMap.ONNX}.7z")
|
"/${versionMap.ONNX}/Project_RapidOcrOnnx-${versionMap.ONNX}.7z")
|
||||||
.setSourceDir("/Project_RapidOcrOnnx-${versionMap.ONNX}/models/")
|
.setSourceDir("/Project_RapidOcrOnnx-${versionMap.ONNX}/models/")
|
||||||
.setDestDir("/src/main/assets/models/"),
|
.setDestDir("/src/main/assets/models/"),
|
||||||
Utils.newLibDeployer(project, nameMap.ONNX_RUNTIME, "https://github.com/RapidAI/OnnxruntimeBuilder/releases/download" +
|
utils.newLibDeployer(project, nameMap.ONNX_RUNTIME, "https://github.com/RapidAI/OnnxruntimeBuilder/releases/download" +
|
||||||
"/${versionMap.ONNX_RUNTIME}/onnxruntime-${versionMap.ONNX_RUNTIME}-android-shared.7z")
|
"/${versionMap.ONNX_RUNTIME}/onnxruntime-${versionMap.ONNX_RUNTIME}-android-shared.7z")
|
||||||
.setSourceDir("/onnxruntime-shared/")
|
.setSourceDir("/onnxruntime-shared/")
|
||||||
.setDestDir("/src/main/onnxruntime-shared/"),
|
.setDestDir("/src/main/onnxruntime-shared/"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
utils.configureLibraryLifecycleHooks(project, nameMap.PROJECT, [
|
||||||
|
"OPENCV_MOBILE",
|
||||||
|
"OPENCV_MOBILE_LABEL",
|
||||||
|
"ONNX",
|
||||||
|
"ONNX_RUNTIME",
|
||||||
|
"NDK",
|
||||||
|
"CMAKE",
|
||||||
|
].collect { "${nameMap[it]}: ${versionMap[it]}" }, libsToDeploy, "isCleanupRapidOcr", [".cxx"])
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
||||||
namespace = "com.benjaminwan.ocrlibrary"
|
namespace = "com.benjaminwan.ocrlibrary"
|
||||||
@@ -113,29 +123,7 @@ dependencies {
|
|||||||
androidTestImplementation libs.test.ext.junit
|
androidTestImplementation libs.test.ext.junit
|
||||||
androidTestImplementation libs.test.espresso.core
|
androidTestImplementation libs.test.espresso.core
|
||||||
|
|
||||||
|
implementation libs.core.ktx
|
||||||
|
implementation libs.appcompat
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clean.doFirst {
|
|
||||||
delete project.layout.buildDirectory
|
|
||||||
['.cxx'].forEach { delete file(it) }
|
|
||||||
if (gradle.ext["isCleanupRapidOcr"] as Boolean) {
|
|
||||||
libsToDeploy.forEach { it.clean() }
|
|
||||||
} else {
|
|
||||||
println("The library files of ${project.ext["projectName"]} won't be cleaned up due to the configuration")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gradle.taskGraph.whenReady { taskGraph ->
|
|
||||||
if (taskGraph.allTasks.every { it.name == "clean" }) return
|
|
||||||
|
|
||||||
Utils.newFormatted("Version information for ${nameMap.PROJECT} library", [
|
|
||||||
"OPENCV_MOBILE",
|
|
||||||
"OPENCV_MOBILE_LABEL",
|
|
||||||
"ONNX",
|
|
||||||
"ONNX_RUNTIME",
|
|
||||||
"NDK",
|
|
||||||
"CMAKE",
|
|
||||||
].collect { "${nameMap[it]}: ${versionMap[it]}" }).print()
|
|
||||||
|
|
||||||
libsToDeploy.forEach { it.deploy() }
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,8 @@ project(RapidOcr)
|
|||||||
|
|
||||||
# OnnxRuntime
|
# OnnxRuntime
|
||||||
|
|
||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../onnxruntime-shared/OnnxRuntimeWrapper.cmake)
|
get_filename_component(MODULE_DIR "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)
|
||||||
|
include(${MODULE_DIR}/src/main/onnxruntime-shared/OnnxRuntimeWrapper.cmake)
|
||||||
find_package(OnnxRuntime REQUIRED)
|
find_package(OnnxRuntime REQUIRED)
|
||||||
if (OnnxRuntime_FOUND)
|
if (OnnxRuntime_FOUND)
|
||||||
message(STATUS "OnnxRuntime_LIBS: ${OnnxRuntime_LIBS}")
|
message(STATUS "OnnxRuntime_LIBS: ${OnnxRuntime_LIBS}")
|
||||||
@@ -15,7 +16,7 @@ endif (OnnxRuntime_FOUND)
|
|||||||
|
|
||||||
## opencv 库
|
## opencv 库
|
||||||
|
|
||||||
set(OpenCV_DIR "${CMAKE_SOURCE_DIR}/../../sdk/native/jni")
|
set(OpenCV_DIR "${MODULE_DIR}/src/sdk/native/jni")
|
||||||
find_package(OpenCV REQUIRED)
|
find_package(OpenCV REQUIRED)
|
||||||
if (OpenCV_FOUND)
|
if (OpenCV_FOUND)
|
||||||
message(STATUS "OpenCV_LIBS: ${OpenCV_LIBS}")
|
message(STATUS "OpenCV_LIBS: ${OpenCV_LIBS}")
|
||||||
|
|||||||
@@ -1,679 +0,0 @@
|
|||||||
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry
|
|
||||||
import org.apache.commons.compress.archivers.sevenz.SevenZFile
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.channels.FileChannel
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.security.NoSuchAlgorithmException
|
|
||||||
import java.util.zip.ZipFile
|
|
||||||
|
|
||||||
ext.Utils = Utils
|
|
||||||
|
|
||||||
class Utils {
|
|
||||||
|
|
||||||
static LibDeployer newLibDeployer(Project project, String name, String downloadUrl) {
|
|
||||||
return new LibDeployer(project, name, downloadUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
static Formatted newFormatted(String title, Collection<String> contents = [], String subtitle = null) {
|
|
||||||
return new Formatted(title, contents, subtitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
static Versions newVersions(Project project) {
|
|
||||||
return new Versions("$project.rootDir/version.properties")
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String generateProgressBar(double progress, int length = 30) {
|
|
||||||
int filledLength = (int) (length * progress / 100.0)
|
|
||||||
String filled = '#' * filledLength
|
|
||||||
String empty = '-' * (length - filledLength)
|
|
||||||
filled + empty
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String formatFileSize(long size) {
|
|
||||||
if (size < 1024) {
|
|
||||||
String.format("%d B", size)
|
|
||||||
} else if (size < (1024 * 1024)) {
|
|
||||||
String.format("%.2f KB", size / 1024.0)
|
|
||||||
} else if (size < (1024 * 1024 * 1024)) {
|
|
||||||
String.format("%.2f MB", size / (1024.0 * 1024))
|
|
||||||
} else {
|
|
||||||
String.format("%.2f GB", size / (1024.0 * 1024 * 1024))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ExtractedFile extractFileFromUrl(url, standardize = false) {
|
|
||||||
def fileNameWithExtension = url.tokenize("/").last()
|
|
||||||
|
|
||||||
String fileName
|
|
||||||
String extensionName
|
|
||||||
|
|
||||||
if (fileNameWithExtension.contains('.')) {
|
|
||||||
def lastIndexOfDot = fileNameWithExtension.lastIndexOf('.')
|
|
||||||
fileName = fileNameWithExtension.take(lastIndexOfDot)
|
|
||||||
extensionName = fileNameWithExtension.substring(lastIndexOfDot + 1)
|
|
||||||
} else {
|
|
||||||
fileName = fileNameWithExtension
|
|
||||||
extensionName = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (standardize) {
|
|
||||||
fileName = fileName.toLowerCase().replaceAll(/\s+/, '').replaceAll(/[^a-z0-9.]/, '-')
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ExtractedFile(fileName, extensionName)
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LibDeployer {
|
|
||||||
|
|
||||||
String name
|
|
||||||
String downloadUrl = null
|
|
||||||
Project project
|
|
||||||
|
|
||||||
String sourceDir = File.separator
|
|
||||||
File sourceFile = project.file(File.separator)
|
|
||||||
|
|
||||||
String destDir = File.separator
|
|
||||||
File destFile = project.file(File.separator)
|
|
||||||
|
|
||||||
File cacheFile
|
|
||||||
File cacheRootFile
|
|
||||||
String cacheFileName
|
|
||||||
String cacheFileExtensionName
|
|
||||||
|
|
||||||
File getSkipFile() {
|
|
||||||
project.file(new File(destFile, "${cacheFileName}.skip"))
|
|
||||||
}
|
|
||||||
|
|
||||||
File getTempOutFile() {
|
|
||||||
project.file(new File(destFile, "temp-extracted"))
|
|
||||||
}
|
|
||||||
|
|
||||||
LibDeployer(Project project, String name, String downloadUrl) {
|
|
||||||
this.project = project
|
|
||||||
this.name = name
|
|
||||||
this.downloadUrl = downloadUrl
|
|
||||||
|
|
||||||
def extractedFile = extractFileFromUrl(downloadUrl, true)
|
|
||||||
|
|
||||||
this.cacheRootFile = project.file("cache")
|
|
||||||
cacheRootFile.mkdirs()
|
|
||||||
this.cacheFileName = "${extractedFile.fileName}-[${generateShortMd5String(downloadUrl).toLowerCase()}]"
|
|
||||||
this.cacheFileExtensionName = extractedFile.extensionName
|
|
||||||
this.cacheFile = project.file(new File(cacheRootFile, "$cacheFileName.$cacheFileExtensionName"))
|
|
||||||
}
|
|
||||||
|
|
||||||
LibDeployer setSourceDir(String sourceDir) {
|
|
||||||
this.sourceDir = sourceDir
|
|
||||||
this.sourceFile = project.file(sourceDir)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
LibDeployer setDestDir(String destDir) {
|
|
||||||
|
|
||||||
// @Hint by SuperMonster003 on Dec 2, 2024.
|
|
||||||
// ! On some platforms (such as Temurin),
|
|
||||||
// ! using project.file might not correctly retrieve the absolute path of the current project.
|
|
||||||
// ! For example, using "/foo/bar",
|
|
||||||
// ! Expected path: "/.../AutoJs6/libs/.../src/sdk/native",
|
|
||||||
// ! Actual path: "/src/sdk/native",
|
|
||||||
// ! In this case, the leading path separator should be removed, using "foo/bar" instead of "/foo/bar".
|
|
||||||
// !
|
|
||||||
// ! zh-CN:
|
|
||||||
// !
|
|
||||||
// ! 某些平台 (如 Temurin) 使用 project.file 可能无法正确获取当前项目的绝对路径.
|
|
||||||
// ! 以 "/foo/bar" 为例,
|
|
||||||
// ! 预期路径: "/.../AutoJs6/libs/.../src/sdk/native",
|
|
||||||
// ! 实际路径: "/src/sdk/native",
|
|
||||||
// ! 这种情况下需要去除路径分隔符前缀, 即使用 "foo/bar" 而非 "/foo/bar".
|
|
||||||
def destFile = destDir.startsWith(File.separator)
|
|
||||||
? project.file(destDir.substring(1))
|
|
||||||
: project.file(destDir)
|
|
||||||
|
|
||||||
destFile.mkdirs()
|
|
||||||
this.destDir = destDir
|
|
||||||
this.destFile = destFile
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
void deploy() {
|
|
||||||
if (tempOutFile.exists()) {
|
|
||||||
project.delete tempOutFile
|
|
||||||
}
|
|
||||||
|
|
||||||
def checkResult = checkCacheAndSkipFiles()
|
|
||||||
def (shouldDownload, shouldExtract) = [checkResult.shouldDownload, checkResult.shouldExtract]
|
|
||||||
|
|
||||||
if (shouldDownload) {
|
|
||||||
printDownloadInfo()
|
|
||||||
downloadWithRetry()
|
|
||||||
shouldExtract = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldExtract) {
|
|
||||||
printExtractInfo()
|
|
||||||
try {
|
|
||||||
extractCacheFile()
|
|
||||||
} catch (Exception e) {
|
|
||||||
println("\n") // two line breaks
|
|
||||||
println("Cache file was deleted as there is an error during extraction")
|
|
||||||
println("Cache file: ${cacheFile.absolutePath}")
|
|
||||||
cacheFile.delete()
|
|
||||||
if (e.message != null) {
|
|
||||||
println("Error message: $e.message")
|
|
||||||
}
|
|
||||||
println()
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
generateMd5File(cacheFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void clean() {
|
|
||||||
project.delete skipFile
|
|
||||||
project.delete tempOutFile
|
|
||||||
deleteDestAccordingToSrc()
|
|
||||||
deleteCacheAccordingToMd5()
|
|
||||||
}
|
|
||||||
|
|
||||||
private LinkedHashMap<String, Boolean> checkCacheAndSkipFiles() {
|
|
||||||
def shouldDownload = true
|
|
||||||
def shouldExtract = true
|
|
||||||
|
|
||||||
if (skipFile.exists()) {
|
|
||||||
println("No need to download or extract \"$name\" archive file as the \"skip file\" exists")
|
|
||||||
shouldDownload = false
|
|
||||||
shouldExtract = false
|
|
||||||
println()
|
|
||||||
} else if (cacheFile.exists()) {
|
|
||||||
if (validateMd5File(cacheFile)) {
|
|
||||||
println("No need to download \"$name\" archive file as the cache file exists and is valid")
|
|
||||||
shouldDownload = false
|
|
||||||
println("Cache file of \"$name\" needs to be extracted as the \"skip file\" doesn't exist")
|
|
||||||
} else {
|
|
||||||
println("Cache file of \"$name\" was deleted as it is invalid")
|
|
||||||
println("Cache file: $cacheFile")
|
|
||||||
project.delete cacheFile
|
|
||||||
def md5File = new File(cacheFile.parentFile, cacheFile.name + ".md5")
|
|
||||||
if (md5File.exists()) {
|
|
||||||
println("MD5 file of \"$name\" was deleted as it is unreliable")
|
|
||||||
println("MD5 file: $md5File")
|
|
||||||
project.delete md5File
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
return [shouldDownload: shouldDownload, shouldExtract: shouldExtract]
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean validateMd5File(File cacheFile) {
|
|
||||||
def md5File = new File(cacheFile.parentFile, cacheFile.name + ".md5")
|
|
||||||
if (!md5File.exists()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
def expectedMd5 = md5File.text.trim().toUpperCase()
|
|
||||||
def actualMd5 = generateMd5String(cacheFile).toUpperCase()
|
|
||||||
return expectedMd5 == actualMd5
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void generateMd5File(File file) {
|
|
||||||
def md5File = new File(file.parentFile, file.name + ".md5")
|
|
||||||
|
|
||||||
println("Generating MD5...")
|
|
||||||
def generatedMd5 = generateMd5String(file)
|
|
||||||
md5File.text = generatedMd5
|
|
||||||
println("MD5 generated: $generatedMd5")
|
|
||||||
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String generateMd5String(File file) {
|
|
||||||
try (FileInputStream fis = new FileInputStream(file)) {
|
|
||||||
FileChannel fileChannel = fis.getChannel()
|
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5")
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(4096)
|
|
||||||
|
|
||||||
while (fileChannel.read(buffer) > 0) {
|
|
||||||
buffer.flip()
|
|
||||||
md.update(buffer)
|
|
||||||
buffer.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BigInteger(1, md.digest()).toString(16).padLeft(32, '0')
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException("MD5 algorithm not found", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String generateShortMd5String(String s) {
|
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5")
|
|
||||||
md.update(s.bytes)
|
|
||||||
return new BigInteger(1, md.digest()).toString(32)
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printDownloadInfo() {
|
|
||||||
def title = "Download \"$name\" archive file for \"${project.ext["projectName"]}\" Gradle project"
|
|
||||||
def srcInfo = "Source: $downloadUrl"
|
|
||||||
def destInfo = "Destination: $cacheFile"
|
|
||||||
def hintInfo = [
|
|
||||||
"If the download gets stuck and won't finish,",
|
|
||||||
"try downloading the source file with tools like IDM (Internet Download Manager),",
|
|
||||||
"then renaming it into the destination path above.",
|
|
||||||
]
|
|
||||||
|
|
||||||
def maxLength = [
|
|
||||||
[title, srcInfo, destInfo].max { it.length() },
|
|
||||||
hintInfo.max { it.length() }
|
|
||||||
].max { it.length() }.length()
|
|
||||||
|
|
||||||
def infoList = [
|
|
||||||
"=".repeat(maxLength),
|
|
||||||
title,
|
|
||||||
"-".repeat(maxLength),
|
|
||||||
srcInfo,
|
|
||||||
destInfo,
|
|
||||||
"-".repeat(maxLength),
|
|
||||||
hintInfo.join("\n"),
|
|
||||||
"=".repeat(maxLength),
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
infoList.forEach { println(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printExtractInfo() {
|
|
||||||
def title = "Extract the archive file for \"${project.ext["projectName"]}\" Gradle project"
|
|
||||||
def srcInfo = "Source: $cacheFile"
|
|
||||||
def destInfo = "Destination: $destFile"
|
|
||||||
def hintInfo = []
|
|
||||||
|
|
||||||
def maxLength = [
|
|
||||||
[title, srcInfo, destInfo].max { it.length() },
|
|
||||||
hintInfo.max { it.length() }
|
|
||||||
].max { it != null ? it.length() : 0 }.length()
|
|
||||||
|
|
||||||
def infoList = [
|
|
||||||
"=".repeat(maxLength),
|
|
||||||
title,
|
|
||||||
"-".repeat(maxLength),
|
|
||||||
srcInfo,
|
|
||||||
destInfo,
|
|
||||||
hintInfo.isEmpty() ? null : "-".repeat(maxLength),
|
|
||||||
hintInfo.isEmpty() ? null : hintInfo.join("\n"),
|
|
||||||
"=".repeat(maxLength),
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
infoList.forEach { if (it != null) println(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadWithRetry(int maxRetries = 3, int retryDelay = 2000) {
|
|
||||||
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
|
||||||
try {
|
|
||||||
download()
|
|
||||||
return
|
|
||||||
} catch (IOException e) {
|
|
||||||
println("Attempt $attempt of $maxRetries failed: ${e.message}")
|
|
||||||
if (attempt < maxRetries) {
|
|
||||||
println("Retrying after ${retryDelay / 1000} seconds...")
|
|
||||||
sleep(retryDelay)
|
|
||||||
} else {
|
|
||||||
throw new GradleException("Download failed after $maxRetries attempts", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void extractCacheFile() {
|
|
||||||
switch (cacheFileExtensionName) {
|
|
||||||
case 'zip':
|
|
||||||
handleZip()
|
|
||||||
break
|
|
||||||
case '7z':
|
|
||||||
handleSevenZip()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
throw new GradleException("Unknown archive file type: $cacheFileExtensionName")
|
|
||||||
}
|
|
||||||
println("All files extracted into [ $destFile ]")
|
|
||||||
if (!skipFile.exists()) {
|
|
||||||
skipFile.parentFile.mkdirs()
|
|
||||||
skipFile.createNewFile()
|
|
||||||
println("File \"${skipFile.name}\" created")
|
|
||||||
}
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteDestAccordingToSrc() {
|
|
||||||
// TODO by SuperMonster003 on Oct 22, 2024.
|
|
||||||
// ! Use the cache file as a reference file.
|
|
||||||
// ! If the cache file does not exist, attempt to re-download it.
|
|
||||||
// ! According to the sourceDir, obtain a single-level file directory tree
|
|
||||||
// ! from the compressed file, and delete the corresponding files
|
|
||||||
// ! under the destDir directory based on this.
|
|
||||||
// ! If the processed destDir is empty, delete it as well.
|
|
||||||
// ! zh-CN:
|
|
||||||
// ! 将缓存文件作为参考文件, 缓存文件不存在时, 尝试重新下载.
|
|
||||||
// ! 根据 sourceDir, 从压缩文件中获取单一层级的文件目录树, 由此删除 destDir 目录下的对应文件.
|
|
||||||
// ! 处理后的 destDir 若为空, 则一并删除.
|
|
||||||
|
|
||||||
/* Pending code... */
|
|
||||||
|
|
||||||
// @Caution by SuperMonster003 on Oct 22, 2024.
|
|
||||||
// ! This operation is NOT safe.
|
|
||||||
// ! zh-CN: 此操作存在安全隐患.
|
|
||||||
project.delete destFile.absolutePath
|
|
||||||
|
|
||||||
def tmp = destFile.parentFile
|
|
||||||
while (tmp != project.projectDir) {
|
|
||||||
if (tmp.listFiles().toList().isEmpty()) {
|
|
||||||
println("Delete empty directory: $tmp.absolutePath")
|
|
||||||
project.delete tmp
|
|
||||||
}
|
|
||||||
tmp = tmp.parentFile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private deleteCacheAccordingToMd5() {
|
|
||||||
cacheRootFile.eachFile { file ->
|
|
||||||
if (!file.name.endsWith('.md5')) {
|
|
||||||
def md5File = new File(file.parentFile, file.name + ".md5")
|
|
||||||
if (!md5File.exists() || !validateMd5File(file)) {
|
|
||||||
project.delete(file)
|
|
||||||
println("Delete cache file: ${file.absolutePath}")
|
|
||||||
if (md5File.exists()) {
|
|
||||||
project.delete(md5File)
|
|
||||||
println("Delete MD5 file: ${md5File.absolutePath}")
|
|
||||||
}
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void download() {
|
|
||||||
final int MAX_RETRIES = 3
|
|
||||||
int attempt = 0
|
|
||||||
boolean success = false
|
|
||||||
boolean shouldPrintProgress = project.ext["platform"]["shouldPrintProgress"] == true
|
|
||||||
|
|
||||||
while (attempt < MAX_RETRIES && !success) {
|
|
||||||
try {
|
|
||||||
attempt++
|
|
||||||
|
|
||||||
cacheFile.parentFile.mkdirs()
|
|
||||||
|
|
||||||
def urlConn = new URI(downloadUrl).toURL().openConnection()
|
|
||||||
urlConn.connectTimeout = 120_000
|
|
||||||
urlConn.readTimeout = 90_000
|
|
||||||
long fileSize = urlConn.contentLengthLong
|
|
||||||
|
|
||||||
urlConn.getInputStream().withCloseable { inputStream ->
|
|
||||||
cacheFile.withOutputStream { outputStream ->
|
|
||||||
byte[] buffer = new byte[8192]
|
|
||||||
long downloadedSize = 0
|
|
||||||
int bytesRead
|
|
||||||
|
|
||||||
if (shouldPrintProgress && fileSize <= 0) {
|
|
||||||
println "\rDownloading..."
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
|
||||||
outputStream.write(buffer, 0, bytesRead)
|
|
||||||
downloadedSize += bytesRead
|
|
||||||
if (shouldPrintProgress && fileSize > 0) {
|
|
||||||
try {
|
|
||||||
double progress = (downloadedSize * 100.0 / fileSize)
|
|
||||||
String progressBar = generateProgressBar(progress)
|
|
||||||
print String.format("\rDownloading... [ %s ] %.2f%%", progressBar, progress)
|
|
||||||
System.out.flush()
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
/* Ignored. */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String downloadPath = cacheFile.absolutePath
|
|
||||||
if (fileSize > 0) {
|
|
||||||
String formattedFileSize = formatFileSize(fileSize)
|
|
||||||
print String.format("\rDownload complete [ %s | %s ]\n", downloadPath, formattedFileSize)
|
|
||||||
} else {
|
|
||||||
print String.format("\rDownload complete [ %s ]\n", downloadPath)
|
|
||||||
}
|
|
||||||
System.out.flush()
|
|
||||||
println()
|
|
||||||
|
|
||||||
success = true
|
|
||||||
|
|
||||||
} catch (SocketTimeoutException ignored) {
|
|
||||||
println String.format("Attempt %d/%d failed: Connection timed out. Retrying...", attempt, MAX_RETRIES)
|
|
||||||
} catch (IOException e) {
|
|
||||||
println String.format("Attempt %d/%d failed: %s. Retrying...", attempt, MAX_RETRIES, e.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!success && attempt >= MAX_RETRIES) {
|
|
||||||
println "Download failed after $MAX_RETRIES attempts."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleZip() {
|
|
||||||
def shouldPrintProgress = project.ext["platform"]["shouldPrintProgress"] == true
|
|
||||||
|
|
||||||
def zipFileForTotalSize = new ZipFile(cacheFile)
|
|
||||||
def zipEntriesForTotalSize = zipFileForTotalSize.entries()
|
|
||||||
def entries = []
|
|
||||||
def totalExtractedSize = 0L
|
|
||||||
|
|
||||||
def sourceDirPath = new File(sourceDir).path
|
|
||||||
if (sourceDirPath.startsWith(File.separator)) sourceDirPath = sourceDirPath.substring(1)
|
|
||||||
|
|
||||||
while (zipEntriesForTotalSize.hasMoreElements()) {
|
|
||||||
def entry = zipEntriesForTotalSize.nextElement()
|
|
||||||
def entryName = new File(entry.name).path
|
|
||||||
if (entryName.startsWith(sourceDirPath)) {
|
|
||||||
entries.add(entry)
|
|
||||||
totalExtractedSize += entry.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zipFileForTotalSize.close()
|
|
||||||
|
|
||||||
def zipFile = new ZipFile(cacheFile)
|
|
||||||
def zipEntries = zipFile.entries()
|
|
||||||
|
|
||||||
int totalEntries = entries.size()
|
|
||||||
int processedEntries = 0
|
|
||||||
|
|
||||||
while (zipEntries.hasMoreElements()) {
|
|
||||||
def entry = zipEntries.nextElement()
|
|
||||||
def entryName = new File(entry.name).path
|
|
||||||
if (entryName.startsWith(sourceDirPath)) {
|
|
||||||
File outFile = project.file(new File(tempOutFile, entryName.substring(sourceDirPath.length())))
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
outFile.mkdirs()
|
|
||||||
} else {
|
|
||||||
outFile.parentFile.mkdirs()
|
|
||||||
zipFile.getInputStream(entry).withCloseable { entryInputStream ->
|
|
||||||
outFile.withOutputStream { outputStream ->
|
|
||||||
byte[] buffer = new byte[8192]
|
|
||||||
int bytesRead
|
|
||||||
while ((bytesRead = entryInputStream.read(buffer)) != -1) {
|
|
||||||
outputStream.write(buffer, 0, bytesRead)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldPrintProgress) {
|
|
||||||
double progress = (processedEntries * 100.0 / totalEntries)
|
|
||||||
String progressBar = generateProgressBar(progress)
|
|
||||||
print String.format("\rExtracting... [ %s ] %.2f%%", progressBar, progress)
|
|
||||||
System.out.flush()
|
|
||||||
}
|
|
||||||
processedEntries++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String formattedTotalExtractedSize = formatFileSize(totalExtractedSize)
|
|
||||||
print String.format("\rExtraction complete [ %s | %s ]\n", destFile.absolutePath, formattedTotalExtractedSize)
|
|
||||||
System.out.flush()
|
|
||||||
println()
|
|
||||||
|
|
||||||
zipFile.close()
|
|
||||||
|
|
||||||
project.copy {
|
|
||||||
from tempOutFile
|
|
||||||
into destFile
|
|
||||||
}
|
|
||||||
project.delete tempOutFile
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSevenZip() {
|
|
||||||
boolean shouldPrintProgress = project.ext["platform"]["shouldPrintProgress"] == true
|
|
||||||
|
|
||||||
String sourceDirPath = new File(sourceDir).path
|
|
||||||
if (sourceDirPath.startsWith(File.separator)) sourceDirPath = sourceDirPath.substring(1)
|
|
||||||
|
|
||||||
SevenZFile sevenZFile = new SevenZFile.Builder().setFile(cacheFile).get()
|
|
||||||
List<SevenZArchiveEntry> allEntries = sevenZFile.getEntries()
|
|
||||||
|
|
||||||
List<SevenZArchiveEntry> targetEntries = allEntries.findAll { it.name.startsWith(sourceDirPath) }
|
|
||||||
|
|
||||||
int entriesCount = targetEntries.size()
|
|
||||||
long totalExtractedSize = 0L
|
|
||||||
for (SevenZArchiveEntry entry : targetEntries) {
|
|
||||||
totalExtractedSize += entry.size
|
|
||||||
}
|
|
||||||
|
|
||||||
int processedEntries = 0
|
|
||||||
byte[] buffer = new byte[64 * 1024]
|
|
||||||
|
|
||||||
for (SevenZArchiveEntry entry : targetEntries) {
|
|
||||||
File outFile = project.file(new File(tempOutFile, new File(entry.name).path.substring(sourceDirPath.length())))
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
outFile.mkdirs()
|
|
||||||
} else {
|
|
||||||
outFile.parentFile.mkdirs()
|
|
||||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFile))) {
|
|
||||||
sevenZFile.getInputStream(entry).withCloseable { entryInputStream ->
|
|
||||||
int bytesRead
|
|
||||||
while ((bytesRead = entryInputStream.read(buffer)) != -1) {
|
|
||||||
bos.write(buffer, 0, bytesRead)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldPrintProgress) {
|
|
||||||
double progress = (processedEntries * 100.0 / entriesCount)
|
|
||||||
String progressBar = generateProgressBar(progress)
|
|
||||||
print String.format("\rExtracting... [ %s ] %.2f%%", progressBar, progress)
|
|
||||||
System.out.flush()
|
|
||||||
}
|
|
||||||
processedEntries++
|
|
||||||
}
|
|
||||||
|
|
||||||
String formattedUncompressedSize = formatFileSize(totalExtractedSize)
|
|
||||||
print String.format("\rExtraction complete [ %s | %s ]\n", destFile.absolutePath, formattedUncompressedSize)
|
|
||||||
System.out.flush()
|
|
||||||
|
|
||||||
sevenZFile.close()
|
|
||||||
|
|
||||||
project.copy {
|
|
||||||
from tempOutFile
|
|
||||||
into destFile
|
|
||||||
}
|
|
||||||
project.delete tempOutFile
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Versions {
|
|
||||||
|
|
||||||
private Properties properties
|
|
||||||
|
|
||||||
private static String PREFIX_PUBLIC = "PUBLIC"
|
|
||||||
private static String SUFFIX_VERSION = "VERSION"
|
|
||||||
|
|
||||||
Versions(String filePath) {
|
|
||||||
File file = new File(filePath)
|
|
||||||
if (!file.canRead()) {
|
|
||||||
throw FileNotFoundException("Cannot read file '$filePath'")
|
|
||||||
}
|
|
||||||
properties = new Properties()
|
|
||||||
properties.load(new FileInputStream(file))
|
|
||||||
}
|
|
||||||
|
|
||||||
String get(String propertyName) {
|
|
||||||
if (propertyName.contains("/")) {
|
|
||||||
return get(propertyName.split("/").toList())
|
|
||||||
}
|
|
||||||
var value = properties["${propertyName}_$SUFFIX_VERSION"] as String
|
|
||||||
return value == PREFIX_PUBLIC
|
|
||||||
? properties["${PREFIX_PUBLIC}_${propertyName}_$SUFFIX_VERSION"] as String
|
|
||||||
: value
|
|
||||||
}
|
|
||||||
|
|
||||||
String get(List<String> propertyInfo) {
|
|
||||||
def (lib, body) = propertyInfo
|
|
||||||
var value = properties["${lib}_${body}_$SUFFIX_VERSION"] as String
|
|
||||||
return value == PREFIX_PUBLIC
|
|
||||||
? properties["${PREFIX_PUBLIC}_${body}_$SUFFIX_VERSION"] as String
|
|
||||||
: value
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Formatted {
|
|
||||||
|
|
||||||
private String title
|
|
||||||
private Collection<String> contents
|
|
||||||
private String subtitle
|
|
||||||
private List<String> formattedOutput
|
|
||||||
|
|
||||||
Formatted(String title, Collection<String> contents = [], String subtitle = null) {
|
|
||||||
this.title = title
|
|
||||||
this.contents = contents
|
|
||||||
this.subtitle = subtitle
|
|
||||||
|
|
||||||
formattedOutput = {
|
|
||||||
def elements = []
|
|
||||||
if (subtitle != null) elements.add(subtitle)
|
|
||||||
elements.addAll(contents)
|
|
||||||
def maxLength = ([title, subtitle] + contents).findAll().collect { it.length() }.max()
|
|
||||||
def result = [
|
|
||||||
'=' * maxLength,
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
contents.isEmpty() ? null : '-' * maxLength
|
|
||||||
].findAll() + contents + ['=' * maxLength, '']
|
|
||||||
return result
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
void print(boolean contentsMatters = false) {
|
|
||||||
formattedOutput.each {
|
|
||||||
if (!contentsMatters || !contents.isEmpty()) {
|
|
||||||
println(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void throwException() {
|
|
||||||
throw new Exception(formattedOutput.join('\n'))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ExtractedFile {
|
|
||||||
String fileName
|
|
||||||
String extensionName
|
|
||||||
|
|
||||||
ExtractedFile(String fileName, String extensionName) {
|
|
||||||
this.fileName = fileName
|
|
||||||
this.extensionName = extensionName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
id 'org.autojs.build.versions'
|
||||||
id 'com.android.library'
|
id 'com.android.library'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'net.dongliu.apk.parser'
|
namespace 'net.dongliu.apk.parser'
|
||||||
compileSdk = project.ext.compileSdk
|
compileSdk = versions.sdkVersionCompile
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = project.ext.minSdk
|
minSdk = versions.sdkVersionMin
|
||||||
targetSdk = project.ext.targetSdk
|
targetSdk = versions.sdkVersionTarget
|
||||||
|
|
||||||
versionName '6'
|
versionName '6'
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
id 'org.autojs.build.versions'
|
||||||
id 'com.android.library'
|
id 'com.android.library'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = 'com.mcal.apksigner'
|
namespace = 'com.mcal.apksigner'
|
||||||
compileSdk = project.ext.compileSdk
|
compileSdk = versions.sdkVersionCompile
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = project.ext.minSdk
|
minSdk = versions.sdkVersionMin
|
||||||
targetSdk = project.ext.targetSdk
|
targetSdk = versions.sdkVersionTarget
|
||||||
|
|
||||||
versionName '1.1-template'
|
versionName '1.1-template'
|
||||||
versionCode 11
|
versionCode 11
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
id 'org.autojs.build.versions'
|
||||||
id 'com.android.library'
|
id 'com.android.library'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
}
|
}
|
||||||
@@ -6,13 +7,13 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
namespace = 'com.jaredrummler.android.colorpicker'
|
namespace = 'com.jaredrummler.android.colorpicker'
|
||||||
|
|
||||||
compileSdk = project.ext.compileSdk
|
compileSdk = versions.sdkVersionCompile
|
||||||
|
|
||||||
resourcePrefix "cpv_"
|
resourcePrefix "cpv_"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = project.ext.minSdk
|
minSdk = versions.sdkVersionMin
|
||||||
targetSdk = project.ext.targetSdk
|
targetSdk = versions.sdkVersionTarget
|
||||||
|
|
||||||
versionName '1.1.0'
|
versionName '1.1.0'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
id 'org.autojs.build.versions'
|
||||||
id 'com.android.library'
|
id 'com.android.library'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = 'com.huaban.jieba'
|
namespace = 'com.huaban.jieba'
|
||||||
compileSdk = project.ext.compileSdk
|
compileSdk = versions.sdkVersionCompile
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = project.ext.minSdk
|
minSdk = versions.sdkVersionMin
|
||||||
targetSdk = project.ext.targetSdk
|
targetSdk = versions.sdkVersionTarget
|
||||||
|
|
||||||
group = 'com.huaban'
|
group = 'com.huaban'
|
||||||
versionName = '1.0.3-SNAPSHOT (Optimized for AutoJs6)'
|
versionName = '1.0.3-SNAPSHOT (Optimized for AutoJs6)'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
id 'org.autojs.build.versions'
|
||||||
id 'com.android.library'
|
id 'com.android.library'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
}
|
}
|
||||||
@@ -6,11 +7,11 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
namespace = 'com.wdullaer.materialdatetimepicker'
|
namespace = 'com.wdullaer.materialdatetimepicker'
|
||||||
|
|
||||||
compileSdk = project.ext.compileSdk
|
compileSdk = versions.sdkVersionCompile
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = project.ext.minSdk
|
minSdk = versions.sdkVersionMin
|
||||||
targetSdk = project.ext.targetSdk
|
targetSdk = versions.sdkVersionTarget
|
||||||
|
|
||||||
versionName '4.2.3'
|
versionName '4.2.3'
|
||||||
versionCode 54
|
versionCode 54
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
id 'org.autojs.build.versions'
|
||||||
id 'com.android.library'
|
id 'com.android.library'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
}
|
}
|
||||||
@@ -6,11 +7,11 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
namespace = 'com.afollestad.materialdialogs'
|
namespace = 'com.afollestad.materialdialogs'
|
||||||
|
|
||||||
compileSdk = project.ext.compileSdk
|
compileSdk = versions.sdkVersionCompile
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = project.ext.minSdk
|
minSdk = versions.sdkVersionMin
|
||||||
targetSdk = project.ext.targetSdk
|
targetSdk = versions.sdkVersionTarget
|
||||||
|
|
||||||
versionName '0.9.6.0'
|
versionName '0.9.6.0'
|
||||||
versionCode 179
|
versionCode 179
|
||||||
|
|||||||
+75
-64
@@ -61,19 +61,16 @@ pluginManagement {
|
|||||||
val overriddenKspVersion: String? = null
|
val overriddenKspVersion: String? = null
|
||||||
|
|
||||||
val versionProperties = java.util.Properties().apply {
|
val versionProperties = java.util.Properties().apply {
|
||||||
load(java.io.FileInputStream("$rootDir/version.properties"))
|
rootDir.resolve("version.properties").inputStream().use { load(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// @AnchorBegin EMBEDDED_KOTLIN_LIST
|
// @AnchorBegin GRADLE_KOTLIN_COMPATIBILITY_LIST
|
||||||
// @Script /.utils/scrape-and-inject-embedded-kotlin-list.mjs
|
// @Script /.utils/scrape-and-inject-gradle-kotlin-compatibility-list.mjs
|
||||||
// @Signature Pair<Gradle Version, Embedded Compatible Kotlin Version>
|
// @Signature Pair<Gradle Version, Max Supported Kotlin Version>
|
||||||
// @Reference gradle-<version>-src.zip\gradle\dependency-management\kotlin-version.properties
|
|
||||||
// @Reference https://docs.gradle.org/current/userguide/compatibility.html#kotlin
|
// @Reference https://docs.gradle.org/current/userguide/compatibility.html#kotlin
|
||||||
// @Updated by SuperMonster003 on Aug 9, 2025.
|
// @Updated by SuperMonster003 on Sep 18, 2025.
|
||||||
val embeddedKotlin = listOf(
|
val gradleKotlinCompatibility = listOf(
|
||||||
"9.0" to "2.2.0",
|
"9.0.0" to "2.2.0",
|
||||||
"8.14" to "2.1.10", /* Unofficial. */
|
|
||||||
"8.13" to "2.1.10", /* Unofficial. */
|
|
||||||
"8.12" to "2.0.21",
|
"8.12" to "2.0.21",
|
||||||
"8.11" to "2.0.20",
|
"8.11" to "2.0.20",
|
||||||
"8.10" to "1.9.24",
|
"8.10" to "1.9.24",
|
||||||
@@ -84,15 +81,16 @@ pluginManagement {
|
|||||||
"8.3" to "1.9.0",
|
"8.3" to "1.9.0",
|
||||||
"8.2" to "1.8.20",
|
"8.2" to "1.8.20",
|
||||||
)
|
)
|
||||||
// @AnchorEnd EMBEDDED_KOTLIN_LIST
|
// @AnchorEnd GRADLE_KOTLIN_COMPATIBILITY_LIST
|
||||||
|
|
||||||
// @AnchorBegin JAVA_GRADLE_COMPATIBILITY_LIST
|
// @AnchorBegin JAVA_GRADLE_COMPATIBILITY_LIST
|
||||||
// @Script /.utils/scrape-java-gradle-compatibility-map.mjs
|
// @Script /.utils/scrape-java-gradle-compatibility-map.mjs
|
||||||
// @Signature Pair<Supported Java Version, Min Required Gradle Version>
|
// @Signature Pair<Supported Java Version, Min Required Gradle Version>
|
||||||
// @Reference https://docs.gradle.org/current/userguide/compatibility.html#java_runtime
|
// @Reference https://docs.gradle.org/current/userguide/compatibility.html#java_runtime
|
||||||
// @Updated by SuperMonster003 on May 12, 2025.
|
// @Updated by SuperMonster003 on Sep 18, 2025.
|
||||||
val javaGradleCompatibility = listOf(
|
val javaGradleCompatibility = listOf(
|
||||||
25 to "9.0", /* Unofficial. */
|
26 to "N/A",
|
||||||
|
25 to "9.1",
|
||||||
24 to "8.14",
|
24 to "8.14",
|
||||||
23 to "8.10",
|
23 to "8.10",
|
||||||
22 to "8.8",
|
22 to "8.8",
|
||||||
@@ -128,9 +126,9 @@ pluginManagement {
|
|||||||
// @AnchorBegin ANDROID_GRADLE_PLUGIN_RELEASES_LIST
|
// @AnchorBegin ANDROID_GRADLE_PLUGIN_RELEASES_LIST
|
||||||
// @Script /.utils/scrape-and-inject-agp-releases.mjs
|
// @Script /.utils/scrape-and-inject-agp-releases.mjs
|
||||||
// @Reference https://developer.android.com/reference/tools/gradle-api
|
// @Reference https://developer.android.com/reference/tools/gradle-api
|
||||||
// @Updated by SuperMonster003 on Sep 12, 2025.
|
// @Updated by SuperMonster003 on Sep 23, 2025.
|
||||||
val agpReleases = listOf(
|
val agpReleases = listOf(
|
||||||
"9.0.0-alpha05",
|
"9.0.0-alpha06",
|
||||||
"8.13.0",
|
"8.13.0",
|
||||||
"8.12.3",
|
"8.12.3",
|
||||||
"8.11.2",
|
"8.11.2",
|
||||||
@@ -215,13 +213,11 @@ pluginManagement {
|
|||||||
val utils = object {
|
val utils = object {
|
||||||
|
|
||||||
private val SUFFIX_PRIORITY: Map<String, Int> = mapOf(
|
private val SUFFIX_PRIORITY: Map<String, Int> = mapOf(
|
||||||
// 早期/快照
|
|
||||||
"canary" to 1, "nightly" to 1, "snapshot" to 1, "dev" to 1,
|
"canary" to 1, "nightly" to 1, "snapshot" to 1, "dev" to 1,
|
||||||
"pre-alpha" to 2, "prealpha" to 2, "preview" to 2, "eap" to 2, "milestone" to 2,
|
"pre-alpha" to 2, "prealpha" to 2, "preview" to 2, "eap" to 2, "milestone" to 2,
|
||||||
"alpha" to 3,
|
"alpha" to 3,
|
||||||
"beta" to 4,
|
"beta" to 4,
|
||||||
"rc" to 5,
|
"rc" to 5,
|
||||||
// 稳定/正式
|
|
||||||
"" to 10, "stable" to 10, "ga" to 10, "final" to 10, "release" to 10, "lts" to 10
|
"" to 10, "stable" to 10, "ga" to 10, "final" to 10, "release" to 10, "lts" to 10
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -269,8 +265,7 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun toVersionParts(version: String): Pair<List<Int>, Pair<String, Int>> {
|
fun toVersionParts(version: String): Pair<List<Int>, Pair<String, Int>> {
|
||||||
// 支持: 1.2.3-rc1 / 1.2.3 RC 1 / 1.2.3-Alpha / 1.2.3.m2 / 1.2.3_preview-2 等
|
// e.g. "1.2.3-rc1" / "1.2.3 RC 1" / "1.2.3-Alpha" / "1.2.3.m2" / "1.2.3_preview-2".
|
||||||
// 以第一个空白/加号/连字符分隔数字部分与后缀部分
|
|
||||||
val split = version.split(Regex("[\\s+\\-]"), limit = 2)
|
val split = version.split(Regex("[\\s+\\-]"), limit = 2)
|
||||||
val numberStr = split[0]
|
val numberStr = split[0]
|
||||||
val numberParts = numberStr.split('.').map {
|
val numberParts = numberStr.split('.').map {
|
||||||
@@ -280,8 +275,6 @@ pluginManagement {
|
|||||||
val suffixStr = split.getOrNull(1)?.trim().orEmpty()
|
val suffixStr = split.getOrNull(1)?.trim().orEmpty()
|
||||||
if (suffixStr.isEmpty()) return numberParts to ("" to 0)
|
if (suffixStr.isEmpty()) return numberParts to ("" to 0)
|
||||||
|
|
||||||
// 更宽松的匹配: 名称 + 可选分隔符 + 可选数字; 或 空名称 + 数字 (极少见)
|
|
||||||
// 分隔符允许: 空格 . _ -
|
|
||||||
val regex = Regex("([A-Za-z]+)[\\s._-]*(\\d*)|([A-Za-z]*)[\\s._-]*(\\d+)", RegexOption.IGNORE_CASE)
|
val regex = Regex("([A-Za-z]+)[\\s._-]*(\\d*)|([A-Za-z]*)[\\s._-]*(\\d+)", RegexOption.IGNORE_CASE)
|
||||||
val m = regex.matchEntire(suffixStr) ?: return numberParts to ("" to 0)
|
val m = regex.matchEntire(suffixStr) ?: return numberParts to ("" to 0)
|
||||||
|
|
||||||
@@ -362,8 +355,9 @@ pluginManagement {
|
|||||||
// @AnchorBegin ANDROID_STUDIO_CODENAME_VERSION_MAP
|
// @AnchorBegin ANDROID_STUDIO_CODENAME_VERSION_MAP
|
||||||
// @Script /.utils/scrape-and-inject-android-studio-codename_maps.mjs
|
// @Script /.utils/scrape-and-inject-android-studio-codename_maps.mjs
|
||||||
// @Reference https://developer.android.com/studio/archive?hl=en
|
// @Reference https://developer.android.com/studio/archive?hl=en
|
||||||
// @Updated by SuperMonster003 on Sep 5, 2025.
|
// @Updated by SuperMonster003 on Sep 23, 2025.
|
||||||
codenameVersionMap = mapOf(
|
codenameVersionMap = mapOf(
|
||||||
|
"2025.2" to "O",
|
||||||
"2025.1" to "N",
|
"2025.1" to "N",
|
||||||
"2024.3" to "M",
|
"2024.3" to "M",
|
||||||
"2024.2" to "L",
|
"2024.2" to "L",
|
||||||
@@ -387,8 +381,9 @@ pluginManagement {
|
|||||||
// @AnchorBegin ANDROID_STUDIO_CODENAME_MAP
|
// @AnchorBegin ANDROID_STUDIO_CODENAME_MAP
|
||||||
// @Script /.utils/scrape-and-inject-android-studio-codename_maps.mjs
|
// @Script /.utils/scrape-and-inject-android-studio-codename_maps.mjs
|
||||||
// @Reference https://developer.android.com/studio/archive?hl=en
|
// @Reference https://developer.android.com/studio/archive?hl=en
|
||||||
// @Updated by SuperMonster003 on Apr 11, 2025.
|
// @Updated by SuperMonster003 on Sep 23, 2025.
|
||||||
codenameMap = mapOf(
|
codenameMap = mapOf(
|
||||||
|
"O" to "Otter", /* Born on Sep 22, 2025. */
|
||||||
"N" to "Narwhal", /* Born on Mar 19, 2025. */
|
"N" to "Narwhal", /* Born on Mar 19, 2025. */
|
||||||
"M" to "Meerkat", /* Born on Nov 12, 2024. */
|
"M" to "Meerkat", /* Born on Nov 12, 2024. */
|
||||||
"L" to "Ladybug", /* Born on Jul 15, 2024. */
|
"L" to "Ladybug", /* Born on Jul 15, 2024. */
|
||||||
@@ -427,9 +422,10 @@ pluginManagement {
|
|||||||
val intelliJIdea = object : Platform(
|
val intelliJIdea = object : Platform(
|
||||||
name = "IntelliJIdea", vendor = "Jetbrains",
|
name = "IntelliJIdea", vendor = "Jetbrains",
|
||||||
// @Reference AGP Upgrade Assistant integrated within JetBrains IntelliJ IDEA.
|
// @Reference AGP Upgrade Assistant integrated within JetBrains IntelliJ IDEA.
|
||||||
// @Updated by SuperMonster003 on Aug 10, 2025. (Manual)
|
// @Updated by SuperMonster003 on Aug 20, 2025. (Manual)
|
||||||
agpVersionMap = mapOf(
|
agpVersionMap = mapOf(
|
||||||
"2025.2" to "8.11.1",
|
"2025.2.2" to "8.12.0",
|
||||||
|
"2025.2.1" to "8.11.1",
|
||||||
"2025.1" to "8.10.1",
|
"2025.1" to "8.10.1",
|
||||||
"2024.3" to "8.7.3",
|
"2024.3" to "8.7.3",
|
||||||
"2024.2" to "8.5.2",
|
"2024.2" to "8.5.2",
|
||||||
@@ -510,6 +506,8 @@ pluginManagement {
|
|||||||
val libs = listOf(
|
val libs = listOf(
|
||||||
Classpath(id = "com.android.tools.build:gradle", version = overriddenAgpVersion ?: "auto:agp"),
|
Classpath(id = "com.android.tools.build:gradle", version = overriddenAgpVersion ?: "auto:agp"),
|
||||||
Classpath(id = "org.jetbrains.kotlin:kotlin-gradle-plugin", version = overriddenKotlinVersion ?: "auto:kotlin"),
|
Classpath(id = "org.jetbrains.kotlin:kotlin-gradle-plugin", version = overriddenKotlinVersion ?: "auto:kotlin"),
|
||||||
|
Classpath(id = "org.apache.commons:commons-compress", version = "toml:commons-compress"),
|
||||||
|
Classpath(id = "org.tukaani:xz", version = "toml:xz"),
|
||||||
Plugin(id = "com.google.devtools.ksp", version = overriddenKspVersion ?: "auto:ksp"),
|
Plugin(id = "com.google.devtools.ksp", version = overriddenKspVersion ?: "auto:ksp"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -585,12 +583,6 @@ pluginManagement {
|
|||||||
"1.8.22" to "1.0.11", /* Jun 9, 2023. */
|
"1.8.22" to "1.0.11", /* Jun 9, 2023. */
|
||||||
"1.8.21" to "1.0.11", /* Apr 27, 2023. */
|
"1.8.21" to "1.0.11", /* Apr 27, 2023. */
|
||||||
"1.8.20" to "1.0.11", /* Apr 18, 2023. */
|
"1.8.20" to "1.0.11", /* Apr 18, 2023. */
|
||||||
"1.8.20-RC2" to "1.0.9", /* Mar 24, 2023. */
|
|
||||||
"1.8.20-RC" to "1.0.9", /* Mar 9, 2023. */
|
|
||||||
"1.8.20-Beta" to "1.0.9", /* Feb 9, 2023. */
|
|
||||||
"1.8.10" to "1.0.9", /* Feb 3, 2023. */
|
|
||||||
"1.8.0" to "1.0.9", /* Jan 26, 2023. */
|
|
||||||
"1.8.0-RC2" to "1.0.8", /* Dec 21, 2022. */
|
|
||||||
)
|
)
|
||||||
// @AnchorEnd KSP_VERSION_MAP
|
// @AnchorEnd KSP_VERSION_MAP
|
||||||
|
|
||||||
@@ -672,6 +664,7 @@ pluginManagement {
|
|||||||
|
|
||||||
val specified = "specified"
|
val specified = "specified"
|
||||||
val auto = "auto"
|
val auto = "auto"
|
||||||
|
val toml = "toml"
|
||||||
|
|
||||||
val fallbackSuffix = " [$fallback]"
|
val fallbackSuffix = " [$fallback]"
|
||||||
val upgradedSuffix = " [$upgraded]"
|
val upgradedSuffix = " [$upgraded]"
|
||||||
@@ -679,6 +672,7 @@ pluginManagement {
|
|||||||
val nearestLowerMatchedSuffix = " [$nearestLowerMatched]"
|
val nearestLowerMatchedSuffix = " [$nearestLowerMatched]"
|
||||||
val autoSpecifiedSuffix = " [$auto-$specified]"
|
val autoSpecifiedSuffix = " [$auto-$specified]"
|
||||||
val userSpecifiedSuffix = " [user-$specified]"
|
val userSpecifiedSuffix = " [user-$specified]"
|
||||||
|
val tomlSpecifiedSuffix = " [toml-$specified]"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,23 +742,23 @@ pluginManagement {
|
|||||||
|
|
||||||
override fun refinedBestMatchingValue(bestMatchingValue: String?): String? {
|
override fun refinedBestMatchingValue(bestMatchingValue: String?): String? {
|
||||||
val currentGradleVersion = gradle.gradleVersion.toGradleVersion()
|
val currentGradleVersion = gradle.gradleVersion.toGradleVersion()
|
||||||
val embeddedMin = embeddedKotlin
|
val kotlinMin = gradleKotlinCompatibility
|
||||||
.filter { (gradleMin, _) -> currentGradleVersion >= gradleMin.toGradleVersion() }
|
.filter { (gradleMin, _) -> currentGradleVersion >= gradleMin.toGradleVersion() }
|
||||||
.maxByOrNull { it.first.toGradleVersion() }
|
.maxByOrNull { it.first.toGradleVersion() }
|
||||||
?.second
|
?.second
|
||||||
return when {
|
return when {
|
||||||
embeddedMin == null -> bestMatchingValue
|
kotlinMin == null -> bestMatchingValue
|
||||||
bestMatchingValue == null -> {
|
bestMatchingValue == null -> {
|
||||||
bestMatchingOperationHintSuffix += identifier.autoSpecifiedSuffix
|
bestMatchingOperationHintSuffix += identifier.autoSpecifiedSuffix
|
||||||
embeddedMin
|
kotlinMin
|
||||||
}
|
}
|
||||||
bestMatchingValue.toGradleVersion() < embeddedMin.toGradleVersion() -> {
|
bestMatchingValue.toGradleVersion() < kotlinMin.toGradleVersion() -> {
|
||||||
bestMatchingOperationHintSuffix += identifier.upgradedSuffix
|
bestMatchingOperationHintSuffix += identifier.upgradedSuffix
|
||||||
embeddedMin
|
kotlinMin
|
||||||
}
|
}
|
||||||
bestMatchingValue.toGradleVersion() > embeddedMin.toGradleVersion() -> {
|
bestMatchingValue.toGradleVersion() > kotlinMin.toGradleVersion() -> {
|
||||||
bestMatchingOperationHintSuffix += identifier.downgradedSuffix
|
bestMatchingOperationHintSuffix += identifier.downgradedSuffix
|
||||||
embeddedMin
|
kotlinMin
|
||||||
}
|
}
|
||||||
else -> bestMatchingValue
|
else -> bestMatchingValue
|
||||||
}
|
}
|
||||||
@@ -803,11 +797,34 @@ pluginManagement {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val toml: Map<String, String> = run {
|
||||||
|
val lines = file("gradle/libs.versions.toml").also {
|
||||||
|
require(it.isFile) { "File $it doesn't exist" }
|
||||||
|
}.readLines()
|
||||||
|
val versions = mutableMapOf<String, String>()
|
||||||
|
var inVersions = false
|
||||||
|
val keyValuePattern = Regex("""^\s*([A-Za-z0-9._-]+)\s*=\s*"(.*?)"\s*(#.*)?$""")
|
||||||
|
for (raw in lines) {
|
||||||
|
val line = raw.trim()
|
||||||
|
if (line.isEmpty() || line.startsWith("#")) continue
|
||||||
|
if (line.startsWith("[")) {
|
||||||
|
inVersions = line == "[versions]"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!inVersions) continue
|
||||||
|
keyValuePattern.find(line)?.let { m ->
|
||||||
|
val (_, key, value) = m.groupValues
|
||||||
|
versions[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@run versions
|
||||||
|
}
|
||||||
|
|
||||||
val classpath = config.libs.filterIsInstance<Classpath>().map { lib ->
|
val classpath = config.libs.filterIsInstance<Classpath>().map { lib ->
|
||||||
var suffix = ""
|
var suffix = ""
|
||||||
val version: String = when {
|
val version: String = when {
|
||||||
lib.version.startsWith("${identifier.auto}:") -> {
|
lib.version.startsWith("${identifier.auto}:") -> {
|
||||||
val mapType = lib.version.substring("${identifier.auto}:".length)
|
val mapType = lib.version.removePrefix("${identifier.auto}:")
|
||||||
val ver = when (mapType) {
|
val ver = when (mapType) {
|
||||||
"agp" -> versions.agp
|
"agp" -> versions.agp
|
||||||
"kotlin" -> versions.kotlin
|
"kotlin" -> versions.kotlin
|
||||||
@@ -819,6 +836,11 @@ pluginManagement {
|
|||||||
suffix += identifier.fallbackSuffix
|
suffix += identifier.fallbackSuffix
|
||||||
} ?: consts.DEFAULT_VERSION
|
} ?: consts.DEFAULT_VERSION
|
||||||
}
|
}
|
||||||
|
lib.version.startsWith("${identifier.toml}:") -> {
|
||||||
|
toml.getValue(lib.version.removePrefix("${identifier.toml}:")).also {
|
||||||
|
suffix += identifier.tomlSpecifiedSuffix
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> lib.version.also {
|
else -> lib.version.also {
|
||||||
suffix += identifier.userSpecifiedSuffix
|
suffix += identifier.userSpecifiedSuffix
|
||||||
}
|
}
|
||||||
@@ -836,11 +858,16 @@ pluginManagement {
|
|||||||
var suffix = ""
|
var suffix = ""
|
||||||
val version: String = when {
|
val version: String = when {
|
||||||
lib.version.startsWith("${identifier.auto}:") -> {
|
lib.version.startsWith("${identifier.auto}:") -> {
|
||||||
val automator = pluginVersionAutomatorMap[lib.version.substring("${identifier.auto}:".length)]!!
|
val automator = pluginVersionAutomatorMap.getValue(lib.version.removePrefix("${identifier.auto}:"))
|
||||||
val result = automator.invoke(config.kspVersionMap)
|
val result = automator.invoke(config.kspVersionMap)
|
||||||
result["suffix"]?.let { s -> suffix += s }
|
result["suffix"]?.let { s -> suffix += s }
|
||||||
result["version"] ?: throw Exception("Unknown version for plugin ${lib.id}")
|
result["version"] ?: throw Exception("Unknown version for plugin ${lib.id}")
|
||||||
}
|
}
|
||||||
|
lib.version.startsWith("${identifier.toml}:") -> {
|
||||||
|
toml.getValue(lib.version.removePrefix("${identifier.toml}:")).also {
|
||||||
|
suffix += identifier.tomlSpecifiedSuffix
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
suffix += identifier.userSpecifiedSuffix
|
suffix += identifier.userSpecifiedSuffix
|
||||||
lib.version
|
lib.version
|
||||||
@@ -923,18 +950,20 @@ pluginManagement {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies /* Android/Kotlin Gradle Plugin. */ {
|
dependencies {
|
||||||
notations.classpath.forEach { classpath(it) }
|
notations.classpath.forEach { classpath(it) }
|
||||||
}
|
}
|
||||||
dependencies /* Apache Compress for utils.build.gradle module. */ {
|
|
||||||
classpath("org.apache.commons:commons-compress:1.28.0")
|
|
||||||
classpath("org.tukaani:xz:1.10")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
@@ -951,28 +980,10 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gradle.extra.apply {
|
gradle.extra.apply {
|
||||||
|
set("platform", platform)
|
||||||
set("isCleanupPaddleOcr", config.isCleanupPaddleOcr)
|
set("isCleanupPaddleOcr", config.isCleanupPaddleOcr)
|
||||||
set("isCleanupRapidOcr", config.isCleanupRapidOcr)
|
set("isCleanupRapidOcr", config.isCleanupRapidOcr)
|
||||||
set("isHideConsoleInfoHintSuffix", config.isHideConsoleInfoHintSuffix)
|
set("isHideConsoleInfoHintSuffix", config.isHideConsoleInfoHintSuffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
gradle.beforeProject {
|
|
||||||
extensions.extraProperties["kotlinVersion"] = notations.classpath.find {
|
|
||||||
it.contains("kotlin-gradle-plugin")
|
|
||||||
}?.substringAfterLast(":") ?: config.fallbackKotlinVersion
|
|
||||||
extensions.extraProperties["platform"] = platform
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
versionCatalogs {
|
|
||||||
create("libs") {
|
|
||||||
from(files("./gradle/libs.versions.toml"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ BUILD_TIME=1758543658764
|
|||||||
COMPILE_SDK_VERSION=35
|
COMPILE_SDK_VERSION=35
|
||||||
IMAGE_QUANT_CMAKE_VERSION=3.22.1
|
IMAGE_QUANT_CMAKE_VERSION=3.22.1
|
||||||
IMAGE_QUANT_NDK_VERSION=26.1.10909125
|
IMAGE_QUANT_NDK_VERSION=26.1.10909125
|
||||||
JAVA_VERSION=24
|
|
||||||
JAVA_VERSION_MAX_SUPPORTED=24
|
JAVA_VERSION_MAX_SUPPORTED=24
|
||||||
JAVA_VERSION_MIN_SUGGESTED=21
|
JAVA_VERSION_MIN_SUGGESTED=21
|
||||||
JAVA_VERSION_MIN_SUPPORTED=17
|
JAVA_VERSION_MIN_SUPPORTED=17
|
||||||
|
|||||||
Reference in new issue
Block a user