-
-
Notifications
You must be signed in to change notification settings - Fork 90
feat: migrate from nodejs-mobile to termux-nodejs standalone process #623
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| import java.security.MessageDigest | ||
| import java.net.URI | ||
| import org.apache.commons.compress.archivers.tar.TarArchiveInputStream | ||
| import org.apache.commons.compress.archivers.tar.TarArchiveEntry | ||
|
|
||
| buildscript { | ||
| repositories { | ||
| mavenCentral() | ||
| } | ||
| dependencies { | ||
| classpath("org.apache.commons:commons-compress:1.28.0") | ||
| classpath("org.tukaani:xz:1.12") | ||
| } | ||
| } | ||
|
|
||
| abstract class DownloadNodejsTask : DefaultTask() { | ||
|
|
||
| private val targetArchitectures = listOf("aarch64", "arm", "x86_64") | ||
| private val architectureMap = mapOf( | ||
| "aarch64" to "arm64-v8a", | ||
| "arm" to "armeabi-v7a", | ||
| "x86_64" to "x86_64", | ||
| ) | ||
|
|
||
| @TaskAction | ||
| fun run() { | ||
| val baseUrl = "https://github.com/nini22P/termux-nodejs/releases/latest/download" | ||
|
|
||
| val libnodeRoot = project.file("src/main/jniLibs") | ||
| val assetsDir = project.file("src/main/assets") | ||
|
|
||
| if (!assetsDir.exists()) assetsDir.mkdirs() | ||
|
|
||
| val sha256Content = fetch("$baseUrl/sha256.txt") | ||
| val expectedHashes = parseSha256Content(sha256Content, targetArchitectures) | ||
|
|
||
| for (archKey in targetArchitectures) { | ||
| val fileName = expectedHashes.keys.find { it.contains(archKey) } ?: continue | ||
| val expectedHash = expectedHashes[fileName]!! | ||
| val tarFile = File(assetsDir, fileName) | ||
|
|
||
| val abi = architectureMap[archKey]!! | ||
| val archDir = File(libnodeRoot, abi) | ||
|
|
||
| val isTarInvalid = !tarFile.exists() || calculateSha256(tarFile) != expectedHash | ||
| if (isTarInvalid) { | ||
| downloadFile("$baseUrl/$fileName", tarFile) | ||
| if (calculateSha256(tarFile) != expectedHash) throw GradleException("SHA256 failed: $fileName") | ||
| } | ||
|
|
||
| extractBin(tarFile, archDir) | ||
| } | ||
| } | ||
|
|
||
| private fun extractBin(tarFile: File, archDir: File) { | ||
| val binPrefix = "./data/data/com.termux/files/usr/bin/" | ||
|
|
||
| val inputStream = tarFile.inputStream().buffered() | ||
|
|
||
| val decompressed = when { | ||
| tarFile.name.endsWith(".xz") -> | ||
| org.apache.commons.compress.compressors.xz.XZCompressorInputStream(inputStream) | ||
| tarFile.name.endsWith(".gz") -> | ||
| java.util.zip.GZIPInputStream(inputStream) | ||
| else -> inputStream | ||
| } | ||
|
|
||
| decompressed.use { comp -> | ||
| TarArchiveInputStream(comp).use { tis -> | ||
| var entry: TarArchiveEntry? = tis.nextEntry | ||
| while (entry != null) { | ||
| val name = entry.name | ||
| if (name.startsWith(binPrefix) | ||
| && !entry.isDirectory | ||
| && !entry.isSymbolicLink | ||
| ) { | ||
| val fileName = name.substringAfterLast("/") | ||
| val targetFile = File(archDir, "lib$fileName.so") | ||
|
|
||
| val bytes = tis.readBytes() | ||
|
|
||
| val newHash = calculateSha256(bytes) | ||
|
|
||
| if (targetFile.exists()) { | ||
| val oldHash = calculateSha256(targetFile) | ||
|
|
||
| if (oldHash == newHash) { | ||
| entry = tis.nextEntry | ||
| continue | ||
| } | ||
| } | ||
|
|
||
| archDir.mkdirs() | ||
| targetFile.writeBytes(bytes) | ||
|
|
||
| println("Extracted: ${tarFile} -> $targetFile") | ||
| } | ||
|
|
||
| entry = tis.nextEntry | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private fun fetch(url: String): String { | ||
| println("Fetching: $url") | ||
| return URI.create(url).toURL().openStream().bufferedReader().use { it.readText() } | ||
| } | ||
|
|
||
| private fun downloadFile(url: String, target: File) { | ||
| println("Downloading: $url") | ||
| URI.create(url).toURL().openStream().use { input -> | ||
| target.outputStream().use { output -> input.copyTo(output) } | ||
| } | ||
| } | ||
|
Comment on lines
+117
to
+133
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 使用 private fun fetch(url: String): String {
println("Fetching: $url")
val connection = URI.create(url).toURL().openConnection()
connection.connectTimeout = 15000
connection.readTimeout = 15000
return connection.getInputStream().bufferedReader().use { it.readText() }
}
private fun downloadFile(url: String, target: File) {
println("Downloading: $url")
val connection = URI.create(url).toURL().openConnection()
connection.connectTimeout = 15000
connection.readTimeout = 15000
connection.getInputStream().use { input ->
target.outputStream().use { output -> input.copyTo(output) }
}
} |
||
|
|
||
| private fun parseSha256Content(content: String, architectures: List<String>): Map<String, String> { | ||
| val hashes = mutableMapOf<String, String>() | ||
| content.lines().forEach { line -> | ||
| val parts = line.trim().split(Regex("\\s+")) | ||
| if (parts.size >= 2) { | ||
| val hash = parts[0] | ||
| val name = parts[1].removePrefix("*") | ||
| if (architectures.any { name.contains(it) }) hashes[name] = hash | ||
| } | ||
| } | ||
| return hashes | ||
| } | ||
|
|
||
| private fun calculateSha256(file: File): String = calculateSha256(file.readBytes()) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
private fun calculateSha256(file: File): String {
val digest = MessageDigest.getInstance("SHA-256")
file.inputStream().use { fis ->
val buffer = ByteArray(8192)
var bytesRead = fis.read(buffer)
while (bytesRead != -1) {
digest.update(buffer, 0, bytesRead)
bytesRead = fis.read(buffer)
}
}
return digest.digest().joinToString("") { "%02x".format(it) }
} |
||
|
|
||
| private fun calculateSha256(data: ByteArray): String { | ||
| val digest = MessageDigest.getInstance("SHA-256") | ||
| return digest.digest(data).joinToString("") { "%02x".format(it) } | ||
| } | ||
| } | ||
|
|
||
| tasks.register<DownloadNodejsTask>("downloadNodejs") | ||
|
|
||
| tasks.named("preBuild") { | ||
| dependsOn("downloadNodejs") | ||
| } | ||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在 Gradle 配置/执行阶段,直接通过网络请求获取
sha256.txt。如果开发者处于离线状态(无网络连接),即使本地已经下载好了 Node.js 压缩包和解压了二进制文件,整个 Gradle 构建也会因为网络请求失败而中断。建议增加异常捕获:如果网络请求失败,且本地对应的
libnode.so已经存在,则跳过下载和解压步骤,允许离线构建顺利进行。