* Copyright (c) 2026 Huawei Technologies Co., Ltd.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const { execSync } = require('node:child_process');
const fs = require('node:fs');
const fse = require('fs-extra');
const { glob } = require('glob');
const pathUtils = require('node:path');
const HARMONY_PATH = '../tester/harmony';
const RNOH_PATH = `${HARMONY_PATH}/react_native_openharmony`;
const MAIN_PATH = `${RNOH_PATH}/src/main`;
const GLOG_HEADER_DIR = `${RNOH_PATH}/.cxx/header-gen`;
* @param {string} directory
* @param {string} filename
*/
async function findFirstFile(directory, filename) {
try {
const files = await glob(`${directory}/**/${filename}`, { nodir: true });
return files.length > 0 ? files[0] : null;
} catch (err) {
console.error(`Error searching for ${filename}: ${err}`);
return null;
}
}
* Filters files in a specified directory based on their extensions.
* @param {string} dir
* @param {string[]} exts e.g., ['h', 'hpp']
* @param {string} target
*/
function filterFileByExt(dir, exts = [], target) {
const files = fs.readdirSync(dir);
for (let i = 0, len = files.length; i < len; i++) {
const item = files[i];
const stat = fs.lstatSync(`${dir}/${item}`);
if (stat.isDirectory()) {
filterFileByExt(`${dir}/${item}`, exts, `${target}/${item}`);
continue;
}
exts = exts.map(ext => ext.trim().toLowerCase());
const ext = item.split('.').pop()?.toLowerCase();
if (ext && exts.includes(ext)) {
const source = `${dir}/${item}`;
const dest = `${target}/${item}`;
try {
fse.copySync(source, dest);
} catch (err) {
console.error(`[failed!]: copy ${source} -> ${dest}\n\n${err}`);
}
}
}
}
function toCMakePath(path) {
return path.replace(/\\/g, '/');
}
async function generateGlogHeaders() {
const glogSourceDir = `${MAIN_PATH}/cpp/third-party/glog`;
fse.removeSync(GLOG_HEADER_DIR);
fse.ensureDirSync(GLOG_HEADER_DIR);
const DEVECO_SDK_HOME = process.env.DEVECO_SDK_HOME;
const basePath = pathUtils.join(DEVECO_SDK_HOME, '.');
const ohosArch = process.env.OHOS_GLOG_CMAKE_ARCH || 'arm64-v8a';
const nativeRoot = pathUtils.join(
DEVECO_SDK_HOME,
'default',
'openharmony',
'native'
);
const toolchainFile = pathUtils.join(
nativeRoot,
'build',
'cmake',
'ohos.toolchain.cmake'
);
const cmakePath = await findFirstFile(
basePath,
'cmake' + (process.platform === 'win32' ? '.exe' : '')
);
const ninjaPath = await findFirstFile(
basePath,
'ninja' + (process.platform === 'win32' ? '.exe' : '')
);
if (!fs.existsSync(toolchainFile)) {
console.error(
`Missing OHOS CMake toolchain (expected at ${toolchainFile}). Check DEVECO_SDK_HOME and OpenHarmony native SDK install.`
);
process.exit(1);
}
const cmakeCommand = `"${cmakePath}" -S ${toCMakePath(glogSourceDir)} -B ${toCMakePath(GLOG_HEADER_DIR)} \
-G "Ninja" \
-DCMAKE_MAKE_PROGRAM="${toCMakePath(ninjaPath)}" \
-DCMAKE_SYSTEM_NAME=OHOS \
-DCMAKE_SYSTEM_VERSION=1 \
-DCMAKE_TOOLCHAIN_FILE="${toCMakePath(toolchainFile)}" \
-DOHOS_ARCH=${ohosArch} \
-DOHOS_STL=c++_shared \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_CXX_STANDARD_REQUIRED=ON \
-DWITH_GFLAGS=OFF \
-DWITH_THREADS=OFF \
-DBUILD_TESTING=OFF`;
execSync(cmakeCommand, { stdio: 'inherit' });
}
function prepareBuildHar() {
fse.copySync('./scripts/resources/build-profile_project.json5', `${HARMONY_PATH}/build-profile.json5`);
fse.copySync('./scripts/resources/build-profile_module.json5', `${RNOH_PATH}/build-profile.json5`);
filterFileByExt(`${MAIN_PATH}/cpp`, ['h', 'hpp', 'ipp'], `${MAIN_PATH}/include`);
fse.copySync(
`${MAIN_PATH}/cpp/third-party/folly/CMake/folly-config.h.cmake`,
`${MAIN_PATH}/include/third-party/folly/CMake/folly-config.h.cmake`
);
fse.copySync('./scripts/resources/react-native-harmony.cmake', `${MAIN_PATH}/include/react-native-harmony.cmake`);
['logging.h', 'raw_logging.h', 'stl_logging.h', 'vlog_is_on.h'].forEach(filePath => {
try {
fse.copySync(
`${GLOG_HEADER_DIR}/glog/${filePath}`,
`${MAIN_PATH}/include/third-party/glog/src/glog/${filePath}`
);
} catch (err) {
console.error(
`[failed!]: copy ${GLOG_HEADER_DIR}/glog/${filePath}\n\n${err}`
);
}
});
fse.copySync(`${MAIN_PATH}/cpp/RNOHAppNapiBridge.cpp`, `${MAIN_PATH}/include/RNOHAppNapiBridge.cpp`);
}
* 复制符号文件
* @param {string} symDirPath 符号文件所在目录路径
* @param {string} outputDir 输出目录路径
*/
function copySymFiles(symDirPath, outputDir) {
try {
console.log(`Copying symbol files to: ${outputDir}`);
if (!fs.existsSync(symDirPath)) {
console.log(`Symbol directory not found: ${symDirPath}`);
return;
}
const symFiles = fs.readdirSync(symDirPath).filter(file => file.endsWith('.sym'));
if (symFiles.length === 0) {
console.log('No .sym files found in symbol directory');
return;
}
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
console.log(`Found ${symFiles.length} .sym files`);
symFiles.forEach(symFile => {
const srcPath = pathUtils.join(symDirPath, symFile);
const destPath = pathUtils.join(outputDir, symFile);
fs.copyFileSync(srcPath, destPath);
console.log(`Copied ${symFile}`);
});
} catch (err) {
console.error(`Failed to copy symbol files: ${err}`);
}
}
(async () => {
const DEVECO_SDK_HOME = process.env.DEVECO_SDK_HOME;
if (!DEVECO_SDK_HOME) {
console.error('DEVECO_SDK_HOME is undefined');
process.exit(1);
}
const basePath = pathUtils.join(DEVECO_SDK_HOME, '..');
const nodePath = await findFirstFile(
basePath,
'node' + (process.platform === 'win32' ? '.exe' : '')
);
const hvigorwPath = await findFirstFile(basePath, 'hvigorw.js');
try {
await generateGlogHeaders();
prepareBuildHar();
const buildSourceHarCommand = `"${nodePath}" "${hvigorwPath}" --mode module -p product=default -p module=react_native_openharmony@default -p buildMode=debug assembleHar --analyze=false --parallel --incremental --no-daemon`;
execSync(buildSourceHarCommand, { stdio: 'inherit', cwd: HARMONY_PATH });
fs.copyFileSync(
`${RNOH_PATH}/build/default/outputs/default/react_native_openharmony.har`,
'./react_native_openharmony.har'
);
copySymFiles(`${RNOH_PATH}/src/main/cpp/third-party/prebuilt/debug/arm64-v8a`, './symbols/debug');
const cleanBuildCommand = `"${nodePath}" "${hvigorwPath}" clean --mode module -p module=react_native_openharmony`;
execSync(cleanBuildCommand, { stdio: 'inherit', cwd: HARMONY_PATH });
const buildReleaseHarCommand = `"${nodePath}" "${hvigorwPath}" --mode module -p product=default -p module=react_native_openharmony@default -p buildMode=release -p debuggable=false assembleHar --analyze=false --parallel --incremental --no-daemon`;
execSync(buildReleaseHarCommand, { stdio: 'inherit', cwd: HARMONY_PATH });
fs.copyFileSync(
`${RNOH_PATH}/build/default/outputs/default/react_native_openharmony.har`,
'./react_native_openharmony_release.har'
);
copySymFiles(`${RNOH_PATH}/build/default/intermediates/cmake/default/obj/arm64-v8a`, './symbols/release');
copySymFiles(`${RNOH_PATH}/src/main/cpp/third-party/prebuilt/release/arm64-v8a`, './symbols/release');
execSync(cleanBuildCommand, { stdio: 'inherit', cwd: HARMONY_PATH });
const buildByteCodeHarCommand = `"${nodePath}" "${hvigorwPath}" --mode module -p product=release2 -p module=react_native_openharmony@default -p buildMode=release -p debuggable=false assembleHar --analyze=false --parallel --incremental --no-daemon`;
execSync(buildByteCodeHarCommand, { stdio: 'inherit', cwd: HARMONY_PATH });
fs.copyFileSync(
`${RNOH_PATH}/build/release2/outputs/default/react_native_openharmony.har`,
'./react_native_openharmony_release2.har'
);
copySymFiles(`${RNOH_PATH}/build/release2/intermediates/cmake/default/obj/arm64-v8a`, './symbols/release2');
copySymFiles(`${RNOH_PATH}/src/main/cpp/third-party/prebuilt/release/arm64-v8a`, './symbols/release2');
process.exit(0);
} catch (error) {
console.error('Command execution failed:', error);
process.exit(1);
}
})();