458 lines
15 KiB
Groovy
458 lines
15 KiB
Groovy
// 配置
|
||
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 查看详细帮助" |