6.7.0 - Alpha6 - 模块化 Gradle 脚本, 将共享构建逻辑迁移至 buildSrc 并抽象为约定插件
This commit is contained in:
@@ -1,23 +1,24 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'org.jetbrains.kotlin.android'
|
||||
apply from: '../utils.build.gradle'
|
||||
plugins {
|
||||
id 'org.autojs.build.utils'
|
||||
id 'org.autojs.build.properties'
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
ext {
|
||||
projectName = "Image Quantization"
|
||||
}
|
||||
|
||||
def versions = Utils.newVersions(project)
|
||||
|
||||
def versionMap = [
|
||||
"libimagequant": "2.17.0",
|
||||
"libpng" : "1.6.49",
|
||||
|
||||
"MIN_SDK" : versions["MIN_SDK"] as Integer,
|
||||
"COMPILE_SDK" : versions["COMPILE_SDK"] as Integer,
|
||||
"TARGET_SDK" : versions["TARGET_SDK"] as Integer,
|
||||
"MIN_SDK" : props["MIN_SDK"] as Integer,
|
||||
"COMPILE_SDK" : props["COMPILE_SDK"] as Integer,
|
||||
"TARGET_SDK" : props["TARGET_SDK"] as Integer,
|
||||
|
||||
"NDK" : versions["IMAGE_QUANT/NDK"],
|
||||
"CMAKE" : versions["IMAGE_QUANT/CMAKE"],
|
||||
"NDK" : props["IMAGE_QUANT/NDK"],
|
||||
"CMAKE" : props["IMAGE_QUANT/CMAKE"],
|
||||
]
|
||||
|
||||
def nameMap = [
|
||||
@@ -26,6 +27,13 @@ def nameMap = [
|
||||
"CMAKE" : "Cmake",
|
||||
]
|
||||
|
||||
utils.configureLibraryLifecycleHooks(project, nameMap.PROJECT, [
|
||||
"libimagequant",
|
||||
"libpng",
|
||||
"NDK",
|
||||
"CMAKE",
|
||||
].collect { "${nameMap[it] ?: it}: ${versionMap[it]}" }, Collections.emptyList(), null, [".cxx"])
|
||||
|
||||
android {
|
||||
|
||||
namespace = "org.pngquant"
|
||||
@@ -75,20 +83,5 @@ android {
|
||||
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()
|
||||
}
|
||||
@@ -6,25 +6,24 @@
|
||||
* Modified by SuperMonster003 as of Sep 4, 2023.
|
||||
*/
|
||||
|
||||
apply {
|
||||
plugin 'com.android.library'
|
||||
plugin 'org.jetbrains.kotlin.android'
|
||||
from '../utils.build.gradle'
|
||||
plugins {
|
||||
id 'org.autojs.build.utils'
|
||||
id 'org.autojs.build.properties'
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
ext {
|
||||
projectName = "Paddle OCR"
|
||||
}
|
||||
|
||||
def versions = Utils.newVersions(project)
|
||||
|
||||
def versionMap = [
|
||||
"MIN_SDK" : versions["MIN_SDK"] as Integer,
|
||||
"COMPILE_SDK": versions["COMPILE_SDK"] as Integer,
|
||||
"TARGET_SDK" : versions["TARGET_SDK"] as Integer,
|
||||
"NDK" : versions["PADDLE_OCR/NDK"],
|
||||
"CMAKE" : versions["PADDLE_OCR/CMAKE"],
|
||||
"OPENCV" : versions["PADDLE_OCR/OPENCV"],
|
||||
"MIN_SDK" : props["MIN_SDK"] as Integer,
|
||||
"COMPILE_SDK": props["COMPILE_SDK"] as Integer,
|
||||
"TARGET_SDK" : props["TARGET_SDK"] as Integer,
|
||||
"NDK" : props["PADDLE_OCR/NDK"],
|
||||
"CMAKE" : props["PADDLE_OCR/CMAKE"],
|
||||
"OPENCV" : props["PADDLE_OCR/OPENCV"],
|
||||
]
|
||||
|
||||
def nameMap = [
|
||||
@@ -43,12 +42,18 @@ def libsToDeploy = [
|
||||
// ! 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.
|
||||
// ! 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")
|
||||
.setSourceDir("/OpenCV-android-sdk/sdk/native/")
|
||||
.setDestDir("/src/sdk/native/")
|
||||
]
|
||||
|
||||
utils.configureLibraryLifecycleHooks(project, nameMap.PROJECT, [
|
||||
"OPENCV",
|
||||
"NDK",
|
||||
"CMAKE",
|
||||
].collect { "${nameMap[it]}: ${versionMap[it]}" }, libsToDeploy, "isCleanupPaddleOcr", [".cxx"])
|
||||
|
||||
android {
|
||||
|
||||
namespace = "com.baidu.paddle.lite.ocr"
|
||||
@@ -148,25 +153,3 @@ dependencies {
|
||||
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
|
||||
# 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)
|
||||
|
||||
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}")
|
||||
find_package(OpenCV REQUIRED)
|
||||
message(STATUS "OpenCV libraries: ${OpenCV_LIBS}")
|
||||
|
||||
@@ -4,26 +4,27 @@
|
||||
* Created by SuperMonster003 on Sep 19, 2024.
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'org.jetbrains.kotlin.android'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply from: '../utils.build.gradle'
|
||||
plugins {
|
||||
id 'org.autojs.build.utils'
|
||||
id 'org.autojs.build.properties'
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'kotlin-parcelize'
|
||||
}
|
||||
|
||||
ext["projectName"] = "Rapid OCR"
|
||||
|
||||
def versions = Utils.newVersions(project)
|
||||
|
||||
def versionMap = [
|
||||
"OFFICIAL_NAME" : "1.3.0", /* From original build.gradle file. */
|
||||
"MIN_SDK" : versions["MIN_SDK"] as Integer,
|
||||
"COMPILE_SDK" : versions["COMPILE_SDK"] as Integer,
|
||||
"TARGET_SDK" : versions["TARGET_SDK"] as Integer,
|
||||
"NDK" : versions["RAPID_OCR/NDK"],
|
||||
"CMAKE" : versions["RAPID_OCR/CMAKE"],
|
||||
"OPENCV_MOBILE" : versions["RAPID_OCR/OPENCV_MOBILE"],
|
||||
"OPENCV_MOBILE_LABEL": versions["RAPID_OCR/OPENCV_MOBILE_LABEL"],
|
||||
"ONNX" : versions["RAPID_OCR/ONNX"],
|
||||
"ONNX_RUNTIME" : versions["RAPID_OCR/ONNX_RUNTIME"],
|
||||
"MIN_SDK" : props["MIN_SDK"] as Integer,
|
||||
"COMPILE_SDK" : props["COMPILE_SDK"] as Integer,
|
||||
"TARGET_SDK" : props["TARGET_SDK"] as Integer,
|
||||
"NDK" : props["RAPID_OCR/NDK"],
|
||||
"CMAKE" : props["RAPID_OCR/CMAKE"],
|
||||
"OPENCV_MOBILE" : props["RAPID_OCR/OPENCV_MOBILE"],
|
||||
"OPENCV_MOBILE_LABEL": props["RAPID_OCR/OPENCV_MOBILE_LABEL"],
|
||||
"ONNX" : props["RAPID_OCR/ONNX"],
|
||||
"ONNX_RUNTIME" : props["RAPID_OCR/ONNX_RUNTIME"],
|
||||
]
|
||||
|
||||
def nameMap = [
|
||||
@@ -37,20 +38,29 @@ def nameMap = [
|
||||
]
|
||||
|
||||
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")
|
||||
.setSourceDir("/opencv-mobile-${versionMap.OPENCV_MOBILE}-android/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")
|
||||
.setSourceDir("/Project_RapidOcrOnnx-${versionMap.ONNX}/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")
|
||||
.setSourceDir("/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 {
|
||||
|
||||
namespace = "com.benjaminwan.ocrlibrary"
|
||||
@@ -113,29 +123,7 @@ dependencies {
|
||||
androidTestImplementation libs.test.ext.junit
|
||||
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
|
||||
|
||||
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)
|
||||
if (OnnxRuntime_FOUND)
|
||||
message(STATUS "OnnxRuntime_LIBS: ${OnnxRuntime_LIBS}")
|
||||
@@ -15,7 +16,7 @@ endif (OnnxRuntime_FOUND)
|
||||
|
||||
## opencv 库
|
||||
|
||||
set(OpenCV_DIR "${CMAKE_SOURCE_DIR}/../../sdk/native/jni")
|
||||
set(OpenCV_DIR "${MODULE_DIR}/src/sdk/native/jni")
|
||||
find_package(OpenCV REQUIRED)
|
||||
if (OpenCV_FOUND)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user