add native gen tool

Signed-off-by: gou-jingjing <goujingjing@kaihong.com>
This commit is contained in:
gou-jingjing 2024-04-02 13:56:44 +08:00
parent f730eed763
commit bea45c4e7b
13 changed files with 551 additions and 286 deletions

View File

@ -65,6 +65,7 @@ Note:If the text contains special characters, please escape them according to th
<filefilterlist>
<filefilter name="defaultFilter" desc="Files not to check">
<filteritem type="filename" name="*.mp4" desc="mp4 files"/>
<filteritem type="filename" name="*.py" desc="python files"/>
<filteritem type="filepath" name="src/generator/src/com/sk/dialog/.*." desc=""/>
<filteritem type="filepath" name="napi_IntelliJ_plugin/generator/src/com/sk/dialog/.*." desc=""/>
<filteritem type="filepath" name="test/storytest/test.py" desc="GPL or BSD license"/>

View File

@ -18,13 +18,27 @@ Native生成工具支持两种入口分别是命令行、IntelliJ插件使
2.在命令行使用 以下命令运行脚本
```
node ./tool/commandLine/src/main.js 接口文件路径
node ./tool/commandLine/src/main.js -f 接口文件路径
```
其中,参数详情如下:
-f, 待转换的.h文件
-t, 可选参数工程目录下测试用例文件Ability.test.ets文件路径默认路径为.h文件所在工程目录下的
Ability.test.ets文件路径
-i, 可选参数工程目录下ts声明文件index.s.ts文件路径默认路径为.h文件所在工程目录下的
index.d.ts文件路径
-o, 可选参数,工程目录下生成的.cpp文件所在文件夹路径若该目录下不存在.cpp文件则会创建test.cpp文件默认路径为.h所在工程目录./src/main/cpp路径下
例如:
```
node ./tool/commandLine/src/main.js E:\napi_generator_aboutTest\napi_240329\napi_generator\examples\napitutorials\entry\src\main\cpp\test.h
node ./tool/commandLine/src/main.js -f E:\napi_generator_aboutTest\napi_240329\napi_generator\examples\napitutorials\entry\src\main\cpp\test.h
```
3.运行成功后命令行会打印出 Generate success并在./entry/src/main/cpp会生成test.cpp文件其中是接口napi层模板在./entry/src/main/cpp/types/libentry/index.d.ts文件中会追加写入生成的ts接口在./entrysrc/ohosTest/ets/test/Ability.test.ets生成接口测试代码模板。用户根据自身需求在test.cpp中增加业务代码并在Ability.test.ets中增加合适断言之后即可连接开发板并运行测试用例测试验证生成napi代码是否正确。例如
@ -38,7 +52,7 @@ res = value0 + value1;
在Ability.test.ets文件中增加断言
```
expect(result).assertEqual(2.3+3.2)
expect(result).assertEqual(2+3)
```
连接开发板运行Ability.test.ets中的测试用例

View File

@ -0,0 +1,35 @@
{
"directFunction": {
"indexTemplete": "export const %s:(%s) => %s;\n",
"cppFuncTemplete": "#include \"napi/native_api.h\"\n[include_replace]\n\n[body_replace]\n\nEXTERN_C_START\nstatic napi_value Init(napi_env env, napi_value exports)\n{\n napi_property_descriptor desc[] = {\n [init_replace]\n};\nnapi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);\nreturn exports;\n}\nEXTERN_C_END\n\nstatic napi_module demoModule = {\n .nm_version = 1,\n .nm_flags = 0,\n .nm_filename = nullptr,\n .nm_register_func = Init,\n .nm_modname = \"entry\",\n .nm_priv = ((void*)0),\n .reserved = { 0 },\n};\n\nextern \"C\" __attribute__((constructor)) void RegisterEntryModule(void)\n{\n napi_module_register(&demoModule);\n}",
"cppFuncDetails": {
"funcInitTemplete": "{ \"%s\" , nullptr, %s, nullptr, nullptr, nullptr, napi_default, nullptr },",
"funcBodyTemplete": "static napi_value [funcName](napi_env env, napi_callback_info info)\n{\n[func_getParam_replace]\n [func_return_replace]\n }\n",
"funcGetParamTemplete" : "size_t requireArgc = [param_length];\n size_t argc = [param_length];\n napi_value args[[param_length]] = {nullptr};\n napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);\n [getParam_replace]\n ",
"funcReturnTemplete" : "[return_type_define]\n // Todo\n // eg. res = value0 + value1;\n napi_value result;\n [return_replace]\n return result;\n",
"paramGenTemplete": " napi_valuetype valuetype%s;\n napi_typeof(env, args[%s], &valuetype%s);\n %s value%s;\n [getParam_replace]",
"funcParamType": {
"int32_t": "napi_get_value_int32(env, args[%s], &value%s);\n",
"int64_t": "napi_get_value_int64(env, args[%s], &value%s);\n",
"uint32_t": "napi_get_value_uint32(env, args[%s], &value%s);\n",
"int": "",
"double": "napi_get_value_double(env, args[%s], &value%s);\n",
"bool": "napi_get_value_bool(env, args[%s], &value%s);\n",
"string": "char buf[1024];\n size_t results;\n napi_get_value_string_utf8(env, args[%s], buf, 1024, &results);\n value%s = buf;\n"
},
"funcReturnType": {
"int32_t": "napi_create_int32(env, res, &result);\n",
"int64_t": "napi_create_int64(env, res, &result);\n",
"uint32_t": "napi_create_uint32(env, res, &result);\n",
"int": "",
"size_t": "",
"double": "napi_create_double(env, res, &result);\n",
"bool": "napi_get_boolean(env, res, &result);\n",
"string": "napi_create_string_utf8(env, res, NAPI_AUTO_LENGTH, &result);\n"
}
},
"abilityTestTemplete": "it('assertContain_[random_number]', 0, () => {\n // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.\n hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');\n\n [func_direct_testCase]\n\n // Defines a variety of assertion methods, which are used to declare expected boolean conditions.\n // 断言 如expect(result).assertEqual(2+3)\n })\n"
},
"asyncFunction": {
}
}

View File

@ -14,11 +14,47 @@
*/
const fs = require('fs');
const path = require('path')
const stdio = require("stdio");
const main = require("./TsGen/tsMain");
let ops = stdio.getopt({
// 输入的.h文件路径必填
'filename': { key: 'f', args: 1, description: ".h file", default: "" },
// 可选参数,可设置默认值
'testFilename': { key: 't', args: 1, description: "Ability.test.ets file", default: "" },
'indexFilename': { key: 'i', args: 1, description: "index.d.ts file", default: "" },
'outCppPath': { key: 'o', args: 1, description: ".cpp dir path", default: "" },
});
// 获取命令行参数 .h文件路径
const filePath = process.argv[2] || '';
let out = './entry/src/main/cpp/types/libentry/'
let filePath = ops.filename
let testFilePath = ops.testFilename
let tsFilePath = ops.indexFilename
let cppFilePath = ops.outCppPath
// 读取文件内容 判断参数是否为空
if (filePath !== '') {
let fileDir = path.resolve(filePath, '..');
let indexFile = findIndexDTS(fileDir);
// 若用户没有提供路径 则程序提供默认路径
if (!tsFilePath) {
tsFilePath = indexFile
}
if (!testFilePath) {
let rootPath = path.resolve(indexFile, '..', '..', '..', '..', '..');
testFilePath = path.join(rootPath, 'ohosTest/ets/test/Ability.test.ets');
}
if(!cppFilePath) {
let rootPath = path.resolve(indexFile, '..', '..', '..');
cppFilePath = path.join(rootPath, 'test.cpp');
}
console.info("filePath: " + filePath)
console.info("testFilePath: " + testFilePath)
console.info("tsFilePath: " + tsFilePath)
console.info("cppFilePath: " + cppFilePath)
main.doGenerate(filePath, testFilePath, tsFilePath, cppFilePath);
}
// 这个函数接收一个目录的绝对路径作为参数
function findIndexDTS(currentDir) {
@ -47,12 +83,3 @@ function findIndexDTS(currentDir) {
console.log('index.d.ts not found in any checked directory.');
return null;
}
// 读取文件内容
if (filePath !== '') {
let fileDir = path.resolve(filePath, '..');
let out = findIndexDTS(fileDir);
if (out !== null) {
main.doGenerate(filePath, out);
}
}

View File

@ -12,66 +12,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// const { paramGenerate } = require("./param_generate");
// const { returnGenerate } = require("./return_generate");
const { NapiLog } = require("../tools/NapiLog");
const util = require('util');
const path = require('path')
const path = require('path');
const fs = require("fs");
const { writeFile } = require("../tools/tool");
const re = require("../tools/re");
const LENGTH = 10;
const TWO_DECIMAL = 2;
let cppTemplete = `
#include "napi/native_api.h"
[include_replace]
static napi_value [funcName](napi_env env, napi_callback_info info)
{
[body_replace]
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
[init_replace]
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "entry",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
napi_module_register(&demoModule);
}`
let bodyTemplete = `
size_t requireArgc = [param_length];
size_t argc = [param_length];
napi_value args[[param_length]] = {nullptr};
napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
[getParme_replace]
[return_type_define]
// Todo
// eg. res = value0 + value1;
napi_value result;
[return_replace]
return result;
`
// 暂不考虑interface情况
function generateDirectFunction(params, tsFuncName, indexPath) {
function generateDirectFunction(params, tsFuncName, cppFilePath, directFuncJson) {
let funcInfo = {
"name": "",
"params": [],
@ -83,16 +34,15 @@ function generateDirectFunction(params, tsFuncName, indexPath) {
for(let i in includes) {
includes_replace += util.format('#include %s\n', includes[i])
}
// console.info("includes_replace: " + includes_replace)
// 获取注册的方法名字 (只读取了一个方法 当前只支持一个方法的转换)
funcInfo.name = params.functions[0].name
let funcName_replace = funcInfo.name.substring(0,1).toUpperCase() + funcInfo.name.substring(1,funcInfo.name.length)
// console.info("funcName_replace: " + funcName_replace)
let serialNum = tsFuncName.substring(0,6)
let funcName_replace = serialNum + funcInfo.name.substring(0,1).toUpperCase() + funcInfo.name.substring(1,funcInfo.name.length)
// 方法的注册
let init_replace = util.format('{ "%s" , nullptr, %s, nullptr, nullptr, nullptr, napi_default, nullptr }', tsFuncName, funcName_replace)
// console.info("init_replace: " + init_replace)
let initTemplete = directFuncJson.cppFuncDetails.funcInitTemplete
let init_replace = util.format(initTemplete, tsFuncName, funcName_replace)
// 分析方法
funcInfo.retType = params.functions[0].rtnType
@ -101,77 +51,116 @@ function generateDirectFunction(params, tsFuncName, indexPath) {
let param = createParam(parseParams[i])
funcInfo.params.push(param)
}
// 生成
let paramGenTemplete = `
napi_valuetype valuetype%s;
napi_typeof(env, args[%s], &valuetype%s);
%s value%s;
[getParam_replace]
`
let paramGenTemplete = directFuncJson.cppFuncDetails.paramGenTemplete
let funcParamType = directFuncJson.cppFuncDetails.funcParamType
let paramGenResult = ''
// napi 获取参数
for(let i = 0; i < funcInfo.params.length; i++) {
let paramGen = util.format(paramGenTemplete, i, i, i, funcInfo.params[i].type, i)
if (funcInfo.params[i].type === 'double' || funcInfo.params[i].type === 'double_t' || funcInfo.params[i].type === 'float') {
let getParam = util.format('napi_get_value_double(env, args[%s], &value%s);', i, i)
console.info("funcInfo.params[i].type.substring(0,10): " + funcInfo.params[i].type.substring(0,10))
let paramType = funcInfo.params[i].type === 'size_t'? 'int64_t': funcInfo.params[i].type
let paramGen = util.format(paramGenTemplete, i, i, i, paramType, i)
if (funcInfo.params[i].type === 'double') {
let getParam = util.format(funcParamType.double, i, i)
paramGen = replaceAll(paramGen, '[getParam_replace]', getParam);
paramGenResult += paramGen;
} else if (funcInfo.params[i].type === 'uint32_t') {
let getParam = util.format('napi_get_value_uint32(env, args[%s], &value%s);', i, i)
let getParam = util.format(funcParamType.uint32_t, i, i)
paramGen = replaceAll(paramGen, '[getParam_replace]', getParam);
paramGenResult += paramGen;
} else if (funcInfo.params[i].type === 'int32_t') {
let getParam = util.format('napi_get_value_int32(env, args[%s], &value%s);', i, i)
let getParam = util.format(funcParamType.int32_t, i, i)
paramGen = replaceAll(paramGen, '[getParam_replace]', getParam);
paramGenResult += paramGen;
} else if (funcInfo.params[i].type === 'int64_t') {
let getParam = util.format('napi_get_value_int64_t(env, args[%s], &value%s);', i, i)
} else if (funcInfo.params[i].type === 'int64_t' || funcInfo.params[i].type === 'size_t') {
let getParam = util.format(funcParamType.int64_t, i, i)
paramGen = replaceAll(paramGen, '[getParam_replace]', getParam);
paramGenResult += paramGen;
} else if (funcInfo.params[i].type === 'bool') {
let getParam = util.format('napi_get_value_bool(env, args[%s], &value%s);', i, i)
let getParam = util.format(funcParamType.bool, i, i)
paramGen = replaceAll(paramGen, '[getParam_replace]', getParam);
paramGenResult += paramGen;
} else if (funcInfo.params[i].type === 'std::string' || funcInfo.params[i].type.substring(0,10) === 'const char') {
let getParam = util.format(funcParamType.string, i, i)
paramGen = replaceAll(paramGen, '[getParam_replace]', getParam);
paramGenResult += paramGen;
}
}
// console.info("paramGenResult: " + paramGenResult)
// 返回值处理
let retGenResult = ''
let funcReturnType = directFuncJson.cppFuncDetails.funcReturnType
if (funcInfo.retType === 'uint32_t') {
retGenResult = 'napi_create_uint32(env, res, &result);'
} else if (funcInfo.retType === 'double' || funcInfo.retType === 'double_t' || funcInfo.retType === 'float') {
retGenResult = 'napi_create_double(env, res, &result);'
retGenResult = funcReturnType.uint32_t
} else if (funcInfo.retType === 'double') {
retGenResult = funcReturnType.double
} else if (funcInfo.retType === 'int32_t') {
retGenResult = 'napi_create_int32(env, res, &result);'
} else if (funcInfo.retType === 'int64_t') {
retGenResult = 'napi_create_int64(env, res, &result);'
retGenResult = funcReturnType.int32_t
} else if (funcInfo.retType === 'int64_t' || funcInfo.retType === 'size_t') {
retGenResult = funcReturnType.int64_t
} else if (funcInfo.retType === 'bool') {
retGenResult = 'napi_get_boolean(env, res, &result);'
retGenResult = funcReturnType.bool
} else if (funcInfo.retType === 'std::string' || funcInfo.retType.substring(0,10) === 'const char') {
retGenResult = funcReturnType.string
}
// console.info("retGenResult: " + retGenResult)
let body_replace = replaceAll(bodyTemplete, '[param_length]', funcInfo.params.length)
body_replace = replaceAll(body_replace, '[getParme_replace]', paramGenResult)
if(funcInfo.retType !== 'void') {
body_replace = replaceAll(body_replace, '[return_type_define]', funcInfo.retType + ' res;')
let bodyTemplete = directFuncJson.cppFuncDetails.funcBodyTemplete
let body_replace = replaceAll(bodyTemplete, '[funcName]', funcName_replace)
let funcGetParamTemplete = directFuncJson.cppFuncDetails.funcGetParamTemplete
let genParam_replace = replaceAll(funcGetParamTemplete, '[param_length]', funcInfo.params.length)
genParam_replace = replaceAll(genParam_replace, '[getParam_replace]', paramGenResult)
if (funcInfo.params.length !== 0) {
body_replace = replaceAll(body_replace, '[func_getParam_replace]', genParam_replace)
} else {
body_replace = replaceAll(body_replace, '[return_type_define]', '')
body_replace = replaceAll(body_replace, '[func_getParam_replace]', '')
}
if(funcInfo.retType !== 'void') {
let returnType = funcInfo.retType === 'std::string'? 'const char *': funcInfo.retType
returnType = returnType === 'size_t'? 'int64_t': returnType
let funcReturnTemplete = directFuncJson.cppFuncDetails.funcReturnTemplete
let func_return_replace = replaceAll(funcReturnTemplete, '[return_type_define]', returnType + ' res;')
func_return_replace = replaceAll(func_return_replace, '[return_replace]', retGenResult)
body_replace = replaceAll(body_replace, '[func_return_replace]', func_return_replace)
} else {
body_replace = replaceAll(body_replace, '[func_return_replace]', '')
}
body_replace = replaceAll(body_replace, '[return_replace]', retGenResult)
// console.info("body_replace: " + body_replace)
let cppContent = replaceAll(cppTemplete, '[include_replace]', includes_replace)
cppContent = replaceAll(cppContent, '[funcName]', funcName_replace)
cppContent = replaceAll(cppContent, '[body_replace]', body_replace)
cppContent = replaceAll(cppContent, '[init_replace]', init_replace)
// console.info("Last cppContent: " + cppContent)
// 将内容写入cpp文件
let rootPath = path.resolve(indexPath, '..', '..', '..');
let cppFilePath = path.join(rootPath, 'test.cpp');
// console.info("cppFilePath: " + cppFilePath)
// 先判断cppFilePath是否存在,若存在则追加写入内容
if (fs.existsSync(cppFilePath)) {
// 读取cpp文件内容
const cppFileContent = fs.readFileSync(cppFilePath, 'utf8');
let includePosition = cppFileContent.indexOf('#include');
let includes = includes_replace.split('\n')
let newIncludes = ""
for (let i = 0; i < includes.length; i++) {
if (cppFileContent.indexOf(includes[i]) < 0) {
newIncludes += includes[i] + '\n'
}
}
let newCppFileContent = cppFileContent
if (newIncludes !== "") {
// 追加写入#include
newCppFileContent = newCppFileContent.slice(0, includePosition) + newIncludes + newCppFileContent.slice(includePosition);
}
// 追加写入方法体
let funcPosition = newCppFileContent.indexOf('EXTERN_C_START')
newCppFileContent = newCppFileContent.slice(0, funcPosition) + body_replace + newCppFileContent.slice(funcPosition);
// 追加写入 方法的初始化
let initPosition = newCppFileContent.indexOf('napi_property_descriptor desc[] = {') + 'napi_property_descriptor desc[] = {'.length;
newCppFileContent = newCppFileContent.slice(0, initPosition) + '\n ' + init_replace + newCppFileContent.slice(initPosition);
writeFile(cppFilePath, newCppFileContent)
} else {
let cppTemplete = directFuncJson.cppFuncTemplete
let cppContent = replaceAll(cppTemplete, '[include_replace]', includes_replace)
cppContent = replaceAll(cppContent, '[body_replace]', body_replace)
cppContent = replaceAll(cppContent, '[init_replace]', init_replace)
// 第一次生成
writeFile(cppFilePath, cppContent)
}
}
function replaceAll(s, sfrom, sto) {
while (s.indexOf(sfrom) >= 0) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd.
* Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@ -16,51 +16,11 @@ const { NapiLog } = require("../tools/NapiLog");
const util = require('util');
const { writeFile, generateRandomInteger } = require("../tools/tool");
const path = require('path')
const fs = require("fs");
const LENGTH = 10;
const TWO_DECIMAL = 2;
let abilityTestTemplete = `
import hilog from '@ohos.hilog';
import testNapi from 'libentry.so';
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function abilityTest() {
describe('ActsAbilityTest', () => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
})
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
})
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
})
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
})
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
let a = 'abc';
let b = 'b';
[func_direct_testCase]
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
// 断言 如expect(result).assertEqual(2+3)
})
})
}
`
const SERIAL = 5;
const MODTWO = 2;
// 随机生成浮点数值
function generateRandomArbitrary(min, max, fixed) {
@ -77,7 +37,20 @@ function generateRandomBoolValue() {
return randomBool;
}
function generateFuncTestCase(params, tsFuncName, indexPath) {
// 随机生成字符串
function generateRandomString(length) {
let result = '';
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
function generateFuncTestCase(params, tsFuncName, testFilePath, directFuncJson) {
let funcInfo = {
"name": "",
"params": [],
@ -92,7 +65,6 @@ function generateFuncTestCase(params, tsFuncName, indexPath) {
funcInfo.retType = params.functions[0].rtnType
let funcParamDefine = ''
let funcParamUse = ''
// let funcParamExpect = ''
// 判断函数有几个参数,依次给参数赋值
for(let i = 0; i < funcInfo.params.length; i++) {
if (getTestType(funcInfo.params[i].type) === 'int') {
@ -104,6 +76,9 @@ function generateFuncTestCase(params, tsFuncName, indexPath) {
} else if (getTestType(funcInfo.params[i].type) === 'bool') {
funcParamDefine += util.format('let %s = %s\n', funcInfo.params[i].name, generateRandomBoolValue())
funcParamUse += funcInfo.params[i].name + ', '
} else if (getTestType(funcInfo.params[i].type) === 'string') {
funcParamDefine += util.format('let %s = "%s"\n', funcInfo.params[i].name, generateRandomString(LENGTH))
funcParamUse += funcInfo.params[i].name + ', '
}
}
// 去除调用参数的最后一个','
@ -114,17 +89,39 @@ function generateFuncTestCase(params, tsFuncName, indexPath) {
// 加 hilog 打印
let hilogContent = util.format('hilog.info(0x0000, "testTag", "Test NAPI %s: ", result);', tsFuncName)
let func_test_replace = funcParamDefine + callFunc + hilogContent
let abilityTestTemplete = directFuncJson.abilityTestTemplete
// 替换random_number
let serialNum = tsFuncName.substring(0,SERIAL)
console.info("serialNum: " + serialNum)
let funcTestContent = replaceAll(abilityTestTemplete,'[func_direct_testCase]', func_test_replace)
// let filePath = './entry/src/ohosTest/ets/test/Ability.test.ets'
funcTestContent = replaceAll(funcTestContent, '[random_number]', serialNum)
//console.info("funcTestContent: " + funcTestContent)
// 将内容写入Ability.test.ets文件
// __dirname //当前文件的绝对路径
// ../
// let relativeFilePath = './entry/src/ohosTest/ets/test/Ability.test.ets'
let rootPath = path.resolve(indexPath, '..', '..', '..', '..', '..');
let filePath = path.join(rootPath, 'ohosTest/ets/test/Ability.test.ets');
// console.info("filePath: " + filePath)
writeFile(filePath, funcTestContent)
// 1.追加写入import模块 写在第一个import之前
// 2.追加写入测试用例
// writeFile(testFilePath, funcTestContent)
const importContent = "import testNapi from 'libentry.so';"
writeTestFile(testFilePath, importContent, funcTestContent)
}
function writeTestFile(filePath, importContent, funcTestContent) {
// 读取原本文件内容
const fileContent = fs.readFileSync(filePath, 'utf8');
const importPosition = fileContent.indexOf('import ');
let newFileContent = fileContent;
// 判断是否有该import语句,没有则添加
if (fileContent.indexOf(importContent) < 0) {
const newImportStatement = importContent + '\n';
newFileContent = fileContent.slice(0, importPosition) + newImportStatement + fileContent.slice(importPosition);
}
// 追加写入测试用例
let testCasePosition = newFileContent.lastIndexOf('})\n}')
//console.info("testCasePosition: " + testCasePosition)
newFileContent = newFileContent.slice(0, testCasePosition) + funcTestContent + newFileContent.slice(testCasePosition);
writeFile(filePath, newFileContent)
}
function replaceAll(s, sfrom, sto) {
@ -135,20 +132,23 @@ function replaceAll(s, sfrom, sto) {
}
function getTestType(type) {
if (type === 'uint32_t' || type === 'int32_t' || type === 'int16_t' || type === 'int64_t' || type === 'int') {
if (type === 'uint32_t' || type === 'int32_t' || type === 'int16_t' ||
type === 'int64_t' || type === 'int' || type === 'size_t') {
return 'int'
} else if (type === 'double_t' || type === 'double' || type === 'float') {
return 'float'
} else if (type === 'bool') {
return 'bool'
} else if (type === 'std::string' || type.substring(0,10) === 'const char') {
return 'string'
}
}
function getJsType(type) {
if (type === 'uint32_t' || type === 'int32_t' || type === 'int16_t' || type === 'int64_t' ||
type === 'int' || type === 'double_t' || type === 'double' || type === 'float') {
type === 'int' || type === 'double_t' || type === 'double' || type === 'float' || type === 'size_t') {
return 'number'
} else if (type === 'const char *' || type === 'std::string') {
} else if (type.substring(0,10) === 'const char' || type === 'std::string') {
return 'string'
} else if (type === 'bool') {
return 'boolean'

View File

@ -13,58 +13,63 @@
* limitations under the License.
*/
const fs = require('fs');
const MOVE_EIGHTEEN = 18;
const MOVE_TWELVE = 12;
const MOVE_SIX = 6;
function utf8ArrayToStr(array) {
var out, i, len, c;
var char2, char3;
let char2, char3;
out = "";
len = array.length;
i = 0;
let outStr = "";
let len = array.length;
let i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
let ch = array[i++];
switch (ch >> 4) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
outStr += String.fromCharCode(ch);
break;
case 12: case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
outStr += String.fromCharCode(((ch & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) |
outStr += String.fromCharCode(((ch & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}
return out;
return outStr;
}
function stringToUint8Array(string, options = { stream: false }) {
if (options.stream) {
throw new Error(`Failed to encode: the 'stream' option is unsupported.`);
function stringToUint8Array(string, option = { streamBool: false }) {
if (option.streamBool) {
throw new Error(`Failed to encode: the 'streamBool' option is unsupported.`);
}
let pos = 0;
const len = string.length;
let at = 0; // output position
let tlen = Math.max(32, len + (len >> 1) + 7); // 1.5x size
let target = new Uint8Array((tlen >> 3) << 3); // ... but at 8 byte offset
let position = 0;
// output position
let atPos = 0;
// 1.5x size
let tlength = Math.max(32, len + (len >> 1) + 7);
// ... but atPos 8 byte offset
let target = new Uint8Array((tlength >> 3) << 3);
while (pos < len) {
let value = string.charCodeAt(pos++);
while (position < len) {
let value = string.charCodeAt(position++);
let isContinue = false;
if (value >= 0xd800 && value <= 0xdbff) {
if (pos < len) {// high surrogate
const extra = string.charCodeAt(pos);
if (position < len) {// high surrogate
const extra = string.charCodeAt(position);
if ((extra & 0xfc00) === 0xdc00) {
++pos;
++position;
value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
}
}
@ -75,42 +80,42 @@ function stringToUint8Array(string, options = { stream: false }) {
if (!isContinue) {
// expand the buffer if we couldn't write 4 bytes
if (at + 4 > target.length) {
tlen += 8; // minimum extra
tlen *= (1.0 + (pos / string.length) * 2); // take 2x the remaining
tlen = (tlen >> 3) << 3; // 8 byte offset
if (atPos + 4 > target.length) {
tlength += 8; // minimum extra
tlength *= (1.0 + (position / string.length) * 2); // take 2x the remaining
tlength = (tlength >> 3) << 3; // 8 byte offset
target = uint8Array(tlen, target);
target = uint8Array(tlength, target);
}
let calculateResult = calculate(value, target, at)
let calculateResult = calculate(value, target, atPos)
isContinue = calculateResult[0]
target = calculateResult[1]
at = calculateResult[2]
atPos = calculateResult[2]
}
}
return target.slice(0, at);
return target.slice(0, atPos);
}
function calculate(value, target, at) {
function calculate(val, target, at) {
let isContinue = false
if ((value & 0xffffff80) === 0) { // 1-byte
target[at++] = value; // ASCII
if ((val & 0xffffff80) === 0) { // 1-byte
target[at++] = val; // ASCII
isContinue = true;
} else if ((value & 0xfffff800) === 0) { // 2-byte
target[at++] = ((value >> 6) & 0x1f) | 0xc0;
} else if ((value & 0xffff0000) === 0) { // 3-byte
target[at++] = ((value >> 12) & 0x0f) | 0xe0;
target[at++] = ((value >> 6) & 0x3f) | 0x80;
} else if ((value & 0xffe00000) === 0) { // 4-byte
target[at++] = ((value >> 18) & 0x07) | 0xf0;
target[at++] = ((value >> 12) & 0x3f) | 0x80;
target[at++] = ((value >> 6) & 0x3f) | 0x80;
} else if ((val & 0xffe00000) === 0) { // 4-byte
target[at++] = ((val >> MOVE_EIGHTEEN) & 0x07) | 0xf0;
target[at++] = ((val >> MOVE_TWELVE) & 0x3f) | 0x80;
target[at++] = ((val >> MOVE_SIX) & 0x3f) | 0x80;
} else if ((val & 0xffff0000) === 0) { // 3-byte
target[at++] = ((val >> MOVE_TWELVE) & 0x0f) | 0xe0;
target[at++] = ((val >> MOVE_SIX) & 0x3f) | 0x80;
} else if ((val & 0xfffff800) === 0) { // 2-byte
target[at++] = ((val >> MOVE_SIX) & 0x1f) | 0xc0;
} else {
isContinue = true;
}
if (!isContinue) {
target[at++] = (value & 0x3f) | 0x80;
target[at++] = (val & 0x3f) | 0x80;
}
return [isContinue, target, at]
}
@ -151,9 +156,21 @@ function generateRandomInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* 获取Json文件内容
* @returns
*/
function getJsonCfg(jsonFilePath) {
let jsonCfg = null; // json 配置文件
let jsonFile = fs.readFileSync(jsonFilePath, { encoding: "utf8" });
jsonCfg = JSON.parse(jsonFile);
return jsonCfg;
}
module.exports = {
readFile,
writeFile,
appendWriteFile,
generateRandomInteger
generateRandomInteger,
getJsonCfg
}

View File

@ -18,15 +18,15 @@ function search(ss, data) {
ss = replaceAll(ss, "\\.", "\\.")
let reg = new RegExp(ss);
let tt = reg.exec(data);
if (tt == null) return null;
if (tt === null || tt === undefined) return null;
let ret = { "regs": [] }
for (let i = 0; i < tt.length; i++) {
let p = data.indexOf(tt[i]);
if (tt[i] == null) {
ret["regs"].push([-1, -1])
if (tt[i] === null || tt[i] === undefined) {
ret.regs.push([-1, -1])
}
else {
ret["regs"].push([p, p + tt[i].length])
ret.regs.push([p, p + tt[i].length])
}
}
@ -35,7 +35,7 @@ function search(ss, data) {
function match(ss, data) {
let tt = search(ss, data)
if (tt != null && tt.regs[0][0] == 0) return tt;
if ((tt !== null && tt !== undefined) && tt.regs[0][0] === 0) return tt;
return null;
}

View File

@ -13,12 +13,13 @@
* limitations under the License.
*/
const { NapiLog } = require("../tools/NapiLog");
const { writeFile, appendWriteFile, generateRandomInteger } = require("../tools/Tool");
const { writeFile, appendWriteFile, generateRandomInteger, getJsonCfg } = require("../tools/Tool");
const path = require('path')
const re = require("../tools/re");
const fs = require("fs");
const os = require("os");
const util = require('util');
const readline = require('readline');
const { generateDirectFunction } = require('../napiGen/functionDirect')
const { generateFuncTestCase } = require('../napiGen/functionDirectTest')
@ -62,7 +63,7 @@ function isStringType(cType) {
}
function isBoolType(cType) {
if (cType == 'bool') {
if (cType === 'bool') {
return true
}
return false
@ -121,7 +122,6 @@ function getJsTypeFromC(cType, typeInfo) {
}
let jsType = basicC2js(basicCtype)
if (typeInfo.array) {
// 替换原先的无用format
jsType = util.format("Array<%s>", jsType)
}
return jsType
@ -176,8 +176,8 @@ function analyzeRootFunction(rootInfo, parseResult) {
let parseFunctions = parseResult.functions
// console.info("parseFunctions: " + JSON.stringify(parseFunctions))
for(var i = 0; i < parseFunctions.length; ++i) {
// 普通方法生成模板
let funcInfo = createFuncInfo(parseFunctions[i], false)
rootInfo.functions.push(funcInfo)
}
}
@ -190,52 +190,121 @@ function getTab(tabLv) {
return tab
}
function genFunction(func, tabLv, needDeclare = false) {
let tab = getTab(tabLv)
function genFunction(func, funcJson) {
let funcPrefix = func.isClassFunc ? "" : "function "
let funcParams = ""
for (var i = 0; i < func.params.length; ++i) {
funcParams += i > 0 ? ", " : ""
funcParams += func.params[i].name + ": " + func.params[i].type
}
let declareStr = needDeclare ? "declare " : ""
let indexTemplete = funcJson.directFunction.indexTemplete
tsFuncName = 'KH' + generateRandomInteger(MIN_RANDOM, MAX_RANDOM) + '_' + func.name
return util.format("export const %s:(%s) => %s;\n", tsFuncName, funcParams, func.retType)
return util.format(indexTemplete, tsFuncName, funcParams, func.retType)
}
function genTsContent(rootInfo) {
function genTsContent(rootInfo, funcJson) {
let tsContent = rootInfo.needCallback ? "import { AsyncCallback, Callback } from './../basic';\n\n" : ""
for(var i = 0; i < rootInfo.functions.length; ++i) {
tsContent += genFunction(rootInfo.functions[i], 0, true)
tsContent += genFunction(rootInfo.functions[i], funcJson)
}
return tsContent
}
function doGenerate(hFilePath, destDir) {
let parseResult = parseFileAll(hFilePath)
// console.info("parseResult: " + JSON.stringify(parseResult))
function removeMarco(hFilePath, tempFilePath) {
// 创建读取文件的流
const fileStream = fs.createReadStream(hFilePath);
// 创建逐行读取的接口
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
// 存储处理后的文件内容
let processedContent = '';
// 逐行读取文件内容并处理
rl.on('line', (line) => {
let tt = re.match('[A-Z_]+\([A-Za-z_ *]+\)', line)
console.info("tt: " + JSON.stringify(tt))
if (tt) {
console.info("before line: " + line)
let removeContent = re.getReg(line, tt.regs[0])
line = line.substring(removeContent.length + 1, line.length)
let index = line.indexOf(') ')
console.info("index: " + index)
if (index >= 0) {
line = line.substring(0, index) + line.substring(index + 1, line.length)
}
}
processedContent += line + '\n';
});
// 完成读取操作
rl.on('close', () => {
console.log("processedContent: " + processedContent);
writeFile(tempFilePath, processedContent)
});
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function doGenerate(hFilePath, testFilePath, tsFilePath, cppFilePath) {
let random = generateRandomInteger(MIN_RANDOM, MAX_RANDOM)
let tempFileName = '../temp_' + random + '.h'
let tempFilePath = path.join(hFilePath, tempFileName)
removeMarco(hFilePath, tempFilePath)
while(!fs.existsSync(tempFilePath)) {
await sleep(20); // 延迟 20 毫秒
}
const fileContent = fs.readFileSync(tempFilePath, 'utf8');
console.info("fileContent: " + fileContent)
let parseResult = parseFileAll(tempFilePath)
console.info("parseResult.functions: " + JSON.stringify(parseResult.functions))
let rootInfo = {
"functions": [],
"needCallback": false
}
// 普通方法生成一个模板模板
analyzeRootFunction(rootInfo, parseResult)
// 如果是class生成一个模板
// analyzeRootClass()
// 读取Json文件
let funcJsonPath = path.join(__dirname, '../function.json');
console.info("funcJsonPath: " + funcJsonPath)
let funcJson = getJsonCfg(funcJsonPath);
let hfileName = path.basename(hFilePath, ".h")
let tsFilePath = destDir
let tsContent = genTsContent(rootInfo)
let tsContent = genTsContent(rootInfo, funcJson)
console.info("tsContent: " + tsContent)
appendWriteFile(tsFilePath, '\n' + tsContent)
// 调用napi转换的方法
generateDirectFunction(parseResult, tsFuncName, destDir)
generateDirectFunction(parseResult, tsFuncName, cppFilePath, funcJson.directFunction)
// 生成测试用例
generateFuncTestCase(parseResult, tsFuncName, destDir)
generateFuncTestCase(parseResult, tsFuncName, testFilePath, funcJson.directFunction)
// 删除生成的中间文件
clearTmpFile(tempFilePath)
console.info('Generate success')
}
function clearTmpFile(filePath) {
try {
fs.unlinkSync(filePath);
} catch (err) {
console.error(err);
}
}
module.exports = {
doGenerate
}

View File

@ -2,7 +2,7 @@
## 工具代码框架介绍
native生成工具由由C++语法解释器和代码生成器两部分组成。C++语法解释器解析用户输入的.h文件内容通过C++语法解析将文件内容分解为类、方法、入参、成员属性等元素代码生成器根据从语法解析器得到的这些元素转换为对应的typescript语法的接口、方法、参数代码生成.ts文件内容同时通过语法解析器得到的元素生成.h文件对应的napi框架代码和接口调用测试代码。
native生成工具由由C++语法解释器和代码生成器两部分组成。C++语法解释器解析用户输入的.h文件内容通过C++语法解析将文件内容分解为类、方法、入参、成员属性等元素代码生成器根据从语法解析器得到的这些元素转换为对应的typescript语法的接口、方法、参数代码生成.ts文件内容同时通过语法解析器得到的元素生成.h文件对应的napi框架代码和接口调用测试代码。native生成工具支持命令行和IntelliJ插件本文主要介绍IntellIJ插件。
## 工具开发

View File

@ -50,7 +50,7 @@ res = value0 + value1;
在Ability.test.ets文件中增加断言
```
expect(result).assertEqual(2.3+3.2)
expect(result).assertEqual(2+3)
```
连接开发板运行Ability.test.ets中的测试用例

View File

@ -31,6 +31,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.regex.Pattern;
import java.nio.charset.StandardCharsets;
/**
* 项目文件入口
@ -40,8 +42,9 @@ import java.io.InputStreamReader;
* @version: v1.0.0
* @since 2024-03-29
*/
public class GenDTS extends AnAction {
private static final Logger LOG = Logger.getInstance(GenDTS.class);
public class GenDts extends AnAction {
private static final Logger LOG = Logger.getInstance(GenDts.class);
private boolean generateSuccess = true;
private String sErrorMessage = "";
@ -58,10 +61,135 @@ public class GenDTS extends AnAction {
return;
}
String destPath = file.getPath();
// 异步执行
runFun(destPath);
}
@Override
public void update(AnActionEvent event) {
// 根据所选文件名判断是否显示生成菜单项
VirtualFile file = event.getData(PlatformDataKeys.VIRTUAL_FILE);
if (file == null) {
event.getPresentation().setEnabledAndVisible(false);
} else {
event.getPresentation().setEnabledAndVisible(patternFileName(file.getName()));
}
}
/**
* 正则匹配所选文件名是否符合规范
*
* @param fileName 文件名
* @return boolean 是否匹配
*/
public static boolean patternFileName(String fileName) {
String pattern = "(([a-z_A-Z0-9]+).h)";
return Pattern.matches(pattern, fileName);
}
/**
* 获取生成成功结果文件
*
* @param process 进程ID
*/
private void genResultLog(Process process) {
BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getErrorStream(),
StandardCharsets.UTF_8));
BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream(),
StandardCharsets.UTF_8));
String sErr = getErrorResult(stdError);
String sOut;
if (TextUtils.isEmpty(sErr)) {
sOut = genInputLog(stdInput);
if (!generateIsSuccess(sOut)) {
sErrorMessage = sOut;
}
return;
}
generateSuccess = false;
sErrorMessage = sErr;
}
/**
* 获取生成文本内容
*
* @param stdInput input buff
* @return 返回当前输入框内容
*/
private String genInputLog(BufferedReader stdInput) {
StringBuilder sOut = new StringBuilder();
while (true) {
String sTmp;
try {
if ((sTmp = stdInput.readLine()) == null) {
break;
}
sOut.append(sTmp).append(getNewline());
} catch (IOException ioException) {
LOG.error(" genResultLog stdInput error" + ioException);
}
}
return sOut.toString();
}
/**
* 获取生成失败结果文件
*
* @param stdError error buff
* @return ErrorResult
*/
private String getErrorResult(BufferedReader stdError) {
StringBuilder sErr = new StringBuilder();
while (true) {
String sTmp;
try {
if ((sTmp = stdError.readLine()) == null) {
break;
}
sErr.append(sTmp).append(getNewline());
} catch (IOException ioException) {
LOG.error(" genResultLog stdInput error" + ioException);
}
}
return sErr.toString();
}
/**
* 获取换行符
*
* @return 换行符
*/
public static String getNewline() {
return System.getProperty("line.separator");
}
private boolean generateIsSuccess(String sOut) {
generateSuccess = sOut.contains("success") || TextUtils.isEmpty(sOut);
return generateSuccess;
}
static class StreamConsumer extends Thread {
InputStream is;
StreamConsumer(InputStream is) {
super.setName("StreamConsumer");
this.is = is;
}
@Override
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
LOG.error("StreamConsumer" + line);
}
} catch (IOException ioException) {
LOG.error("StreamConsumer io error" + ioException);
}
}
}
private boolean callExtProcess(String command) throws IOException, InterruptedException {
if (TextUtils.isEmpty(command)) {
@ -69,40 +197,20 @@ public class GenDTS extends AnAction {
return false;
}
Process process = Runtime.getRuntime().exec(command);
// 读取输出流正常输出
new Thread(() -> {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
// 读取错误流错误输出
new Thread(() -> {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.err.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
// 等待进程结束
int exitCode = process.waitFor();
System.out.println("Process exited with code: " + exitCode);
genResultLog(process);
StreamConsumer errConsumer = new StreamConsumer(process.getErrorStream());
StreamConsumer outputConsumer = new StreamConsumer(process.getInputStream());
errConsumer.start();
outputConsumer.start();
if (!generateSuccess) {
GenNotification.notifyMessage(null, sErrorMessage, "提示", NotificationType.ERROR);
return false;
}
errConsumer.join();
outputConsumer.join();
return true;
}
@ -175,6 +283,7 @@ public class GenDTS extends AnAction {
/**
* 生成命令行指令
*
* @param hFilePath .h文件路径
* @return 返回命令行执行内容
*/
private String genCommand(String hFilePath) {
@ -193,13 +302,15 @@ public class GenDTS extends AnAction {
File file = new File(tmpDirFile);
String command = file.toString();
command += " " + hFilePath;
command += " -f " + hFilePath;
// 判断用户是否输入了 "-o"+cpp文件路径 "-i"+dts文件路径"-t"+test文件路径 从界面获取
return command;
}
/**
* 执行主程序入口
*
* @param hFilePath .h文件路径
* @return 执行状态
*/
public boolean runFun(String hFilePath) {

View File

@ -13,6 +13,7 @@
* limitations under the License.
*/
package com.sk.na.utils;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationGroupManager;
@ -20,13 +21,14 @@ import com.intellij.notification.NotificationGroup;
import com.intellij.notification.Notifications;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
/**
* 通知框
*
* @author: liulongc digitalchina.com
* @author: goujingjing
* @see: tool conversion plug-in
* @version: v1.0.0
* @since 2022-05-27
* @since 2024-04-02
*/
public class GenNotification {