RedEnvelope/gradle/jni-heads.gradle

458 lines
15 KiB
Groovy
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 配置
def outputDir = project.file("cpp/header")
def configFile = project.file("config/jni-classes.txt")
// 日志函数
def log(msg) {
println "[JNI] $msg"
}
def logError(msg) {
println "[JNI ERROR] $msg"
}
def logWarn(msg) {
println "[JNI WARN] $msg"
}
// 创建配置任务
tasks.register('createJniConfig') {
group = 'jni'
description = '创建 JNI 配置文件模板'
doLast {
configFile.parentFile.mkdirs()
if (!configFile.exists()) {
configFile.text = """# JNI 头文件生成配置
# 每行一个类全限定名,例如:
# com.example.MyNativeClass
# com.example.NativeUtils
# 或者使用正则表达式自动匹配:
# #auto:.*Native.*
"""
log "配置文件已创建: ${configFile.absolutePath}"
log "请编辑此文件并添加包含 native 方法的类"
}
}
}
// 生成头文件任务
tasks.register('generateJniHeaders') {
group = 'jni'
description = '生成 JNI 头文件'
dependsOn 'compileJava'
doLast {
// 确保目录存在
outputDir.mkdirs()
// 读取配置
def targetClasses = []
if (configFile.exists()) {
configFile.eachLine { line ->
def trimmed = line.trim()
if (trimmed && !trimmed.startsWith('#')) {
targetClasses.add(trimmed)
}
}
}
if (targetClasses.isEmpty()) {
logError "没有配置任何 JNI 类"
logError "请先运行: ./gradlew createJniConfig"
logError "然后编辑 ${configFile.absolutePath} 添加类名"
return
}
log "开始生成 JNI 头文件..."
log "目标类 (${targetClasses.size()} 个):"
targetClasses.eachWithIndex { className, i ->
log " ${i + 1}. $className"
}
// 准备类路径
def classesDir = project.sourceSets.main.output.classesDirs.singleFile
def classpath = project.configurations.runtimeClasspath.asPath +
File.pathSeparator +
classesDir.absolutePath
// 查找对应的 Java 源文件
def sourceFiles = []
def sourceDirs = project.sourceSets.main.java.srcDirs
targetClasses.each { className ->
def found = false
def relativePath = className.replace('.', '/') + '.java'
sourceDirs.each { srcDir ->
def sourceFile = new File(srcDir, relativePath)
if (sourceFile.exists()) {
sourceFiles.add(sourceFile)
found = true
log "找到源文件: ${sourceFile.absolutePath}"
}
}
if (!found) {
logWarn "警告: 未找到类 $className 的源文件"
}
}
if (sourceFiles.isEmpty()) {
logError "错误: 未找到任何源文件"
logError "请确保源文件存在于 src/main/java/ 目录中"
return
}
// 使用 javac -h 命令(正确的方式)
def javaHome = System.getProperty('java.home')
def javacPath = "${javaHome}/bin/javac"
if (!new File(javacPath).exists()) {
javacPath = "${javaHome}/bin/javac"
}
log "使用 javac -h 生成头文件..."
// 构建临时目录用于编译输出
def tempOutputDir = new File(project.buildDir, "tmp/jni-headers")
tempOutputDir.mkdirs()
try {
// 方式1逐个类生成更可靠
def successCount = 0
def failCount = 0
sourceFiles.each { sourceFile ->
try {
// 构建 javac 命令
def processArgs = [
javacPath,
'-h', outputDir.absolutePath, // 头文件输出目录
'-cp', classpath, // 类路径
'-d', tempOutputDir.absolutePath, // 类文件输出目录
sourceFile.absolutePath // 源文件
]
log "处理: ${sourceFile.name}"
def process = processArgs.execute()
def stdout = new StringBuilder()
def stderr = new StringBuilder()
process.consumeProcessOutput(stdout, stderr)
def exitCode = process.waitFor()
if (exitCode == 0) {
successCount++
if (stdout.length() > 0) {
log " 输出: ${stdout.toString().trim()}"
}
} else {
failCount++
logError " 处理失败: ${sourceFile.name}"
if (stderr.length() > 0) {
logError " 错误: ${stderr.toString().trim()}"
}
}
} catch (Exception e) {
failCount++
logError " 处理异常: ${e.message}"
}
}
log "处理完成: 成功 ${successCount} 个, 失败 ${failCount} 个"
if (successCount > 0) {
// 检查生成了哪些头文件
def headerFiles = outputDir.listFiles({ dir, name -> name.endsWith('.h') } as FilenameFilter)
if (headerFiles && headerFiles.size() > 0) {
log "=" * 60
log "JNI 头文件生成成功!"
log "=" * 60
log "输出目录: ${outputDir.absolutePath}"
log "生成的头文件 (${headerFiles.size()} 个):"
headerFiles.sort { it.name }.each { file ->
def size = file.length()
def sizeStr = size < 1024 ? "${size} B" : "${String.format("%.1f", size / 1024.0)} KB"
log " ✓ ${file.name} ($sizeStr)"
// 显示文件开头几行
try {
def lines = file.readLines()
if (lines.size() > 0) {
def headerGuard = lines.find { it.contains('#ifndef') }
if (headerGuard) {
log " 头文件保护: ${headerGuard.trim()}"
}
}
} catch (Exception e) {
// 忽略读取错误
}
}
log "=" * 60
log "🎉 头文件已成功生成!"
log ""
log "使用建议:"
log " 1. 将生成的头文件复制到你的 C/C++ 项目中"
log " 2. 在 C/C++ 源文件中包含这些头文件"
log " 3. 实现头文件中声明的 JNI 函数"
log ""
log "示例 C++ 代码:"
log " #include \"${headerFiles[0].name}\""
log " JNIEXPORT void JNICALL Java_com_example_MyClass_nativeMethod(JNIEnv* env, jobject obj) {"
log " // 你的实现代码"
log " }"
} else {
logWarn "警告: 未生成任何 .h 头文件"
logWarn "可能的原因:"
logWarn " 1. 源文件中没有 native 方法声明"
logWarn " 2. javac 版本不支持 -h 选项"
logWarn " 3. 类路径配置不正确"
}
} else {
logError "所有处理都失败,未生成任何头文件"
}
} finally {
// 清理临时目录
tempOutputDir.deleteDir()
}
}
}
// 备选方案:使用传统 javah如果 javac -h 失败)
tasks.register('generateJniHeadersLegacy') {
group = 'jni'
description = '使用传统 javah 生成 JNI 头文件'
dependsOn 'compileJava'
doLast {
outputDir.mkdirs()
def targetClasses = []
if (configFile.exists()) {
configFile.eachLine { line ->
def trimmed = line.trim()
if (trimmed && !trimmed.startsWith('#')) {
targetClasses.add(trimmed)
}
}
}
if (targetClasses.isEmpty()) {
logError "没有配置任何 JNI 类"
return
}
log "使用传统 javah 生成头文件..."
def classesDir = project.sourceSets.main.output.classesDirs.singleFile
def classpath = project.configurations.runtimeClasspath.asPath +
File.pathSeparator +
classesDir.absolutePath
def javaHome = System.getProperty('java.home')
def javahPath = "${javaHome}/bin/javah"
if (!new File(javahPath).exists()) {
javahPath = "${javaHome}/../bin/javah"
}
if (!new File(javahPath).exists()) {
logError "找不到 javah 工具"
logError "请使用 Java 8-9 或使用 generateJniHeaders 任务"
return
}
def processArgs = [javahPath, '-classpath', classpath, '-d', outputDir.absolutePath]
processArgs.addAll(targetClasses)
log "执行命令: ${processArgs.join(' ')}"
def process = processArgs.execute()
def exitCode = process.waitFor()
if (exitCode == 0) {
def files = outputDir.listFiles({ dir, name -> name.endsWith('.h') } as FilenameFilter)
log "生成成功!创建了 ${files?.size() ?: 0} 个头文件"
if (files) {
files.each { log " - ${it.name}" }
}
} else {
logError "生成失败"
}
}
}
// 扫描 native 方法任务
tasks.register('scanForNativeMethods') {
group = 'jni'
description = '扫描项目中的 native 方法'
doLast {
log "扫描项目中可能包含 native 方法的类..."
def sourceDirs = project.sourceSets.main.java.srcDirs
def foundClasses = []
sourceDirs.each { srcDir ->
if (srcDir.exists()) {
srcDir.eachFileRecurse(groovy.io.FileType.FILES) { file ->
if (file.name.endsWith('.java')) {
def content = file.text
if (content.contains('native ') || content.contains(' native')) {
// 提取类名
def packageMatch = content =~ /package\s+([\w.]+)\s*;/
def classMatch = content =~ /class\s+(\w+)/
if (packageMatch.find() && classMatch.find()) {
def packageName = packageMatch.group(1)
def className = classMatch.group(1)
def fullClassName = "${packageName}.${className}"
if (!foundClasses.contains(fullClassName)) {
foundClasses.add(fullClassName)
log "发现: $fullClassName"
}
}
}
}
}
}
}
if (foundClasses.isEmpty()) {
log "未发现包含 native 方法的类"
} else {
log "=" * 60
log "发现 ${foundClasses.size()} 个可能包含 native 方法的类:"
foundClasses.sort().eachWithIndex { cls, i ->
log " ${i + 1}. $cls"
}
log ""
log "你可以将这些类添加到 ${configFile.name} 中"
}
}
}
// 清理任务
tasks.register('cleanJniHeaders', Delete) {
group = 'jni'
description = '清理 JNI 头文件'
delete outputDir
doLast {
log "已清理 JNI 头文件目录: ${outputDir.absolutePath}"
}
}
// 验证任务
tasks.register('verifyJniSetup') {
group = 'jni'
description = '验证 JNI 配置'
doLast {
log "验证 JNI 配置..."
log "Java 版本: ${System.getProperty('java.version')}"
log "Java Home: ${System.getProperty('java.home')}"
// 检查 javac
def javaHome = System.getProperty('java.home')
def javacPath = "${javaHome}/bin/javac"
def javacExists = new File(javacPath).exists() || new File("${javaHome}/../bin/javac").exists()
log "javac 工具: ${javacExists ? '找到 ✓' : '未找到 ✗'}"
// 检查 javah传统方式
def javahPath = "${javaHome}/bin/javah"
def javahExists = new File(javahPath).exists() || new File("${javaHome}/../bin/javah").exists()
log "javah 工具: ${javahExists ? '找到 ✓' : '未找到 ✗'}"
// 检查配置文件
if (configFile.exists()) {
def classes = configFile.readLines()
.findAll { it.trim() && !it.trim().startsWith('#') }
log "配置文件: 已找到 (${classes.size()} 个类)"
if (classes.size() > 0) {
classes.each { log " - $it" }
}
} else {
log "配置文件: 未找到 ✗"
}
// 检查输出目录
log "输出目录: ${outputDir.absolutePath}"
}
}
// 帮助任务
tasks.register('jniHelp') {
group = 'help'
description = 'JNI 帮助'
doLast {
println """
${'=' * 70}
JNI 头文件生成系统
${'=' * 70}
📋 概述:
为包含 native 方法的 Java 类生成 C/C++ 头文件。
🚀 快速开始:
1. ./gradlew createJniConfig # 创建配置文件
2. 编辑 config/jni-classes.txt # 添加你的 JNI 类
3. ./gradlew generateJniHeaders # 生成头文件(推荐)
4. 头文件输出到: cpp/header/
🔧 可用任务:
jniHelp - 显示此帮助
createJniConfig - 创建配置文件
generateJniHeaders - 生成头文件(现代方式)
generateJniHeadersLegacy - 传统方式Java 8-9
scanForNativeMethods - 扫描 native 方法
verifyJniSetup - 验证配置
cleanJniHeaders - 清理生成的文件
📝 配置文件格式 (config/jni-classes.txt):
# 注释
com.example.MyNativeClass # 直接指定类名
#auto:.*Native.* # 自动匹配(以 #auto: 开头)
⚠️ 注意事项:
• 确保类中包含 native 方法声明
• 先编译项目再生成头文件
• 对于 Java 10+ 使用 generateJniHeaders
• 对于 Java 8-9 使用 generateJniHeadersLegacy
🔍 调试:
• 运行 verifyJniSetup 检查环境
• 运行 scanForNativeMethods 发现 native 类
• 确保源文件中有 native 关键字
${'=' * 70}
"""
}
}
// 可选:自动集成到构建
// tasks.named('build') {
// dependsOn tasks.named('generateJniHeaders')
// }
// 清理时包含 JNI 头文件
tasks.named('clean') {
dependsOn tasks.named('cleanJniHeaders')
}
log "JNI 模块已加载"
log "输出目录: ${outputDir.absolutePath}"
log "配置文件: ${configFile.absolutePath}"
log "使用 ./gradlew jniHelp 查看详细帮助"