* Copyright (c) Huawei Device Co., Ltd. 2024-2025. All rights reserved.
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require('fs');
const path = require('path');
const CommandUtil = require('./CommandUtil');
const json5 = require('json5');
const idePath = path.resolve(process.env.DEVECO_SDK_HOME, '..');
const rootPath = path.resolve(__dirname, '..');
const hdcPath = path.resolve(idePath, 'sdk/default/openharmony/toolchains/hdc.exe');
const nodePath = path.resolve(idePath, 'tools/node');
const hvigorPath = path.resolve(idePath, 'tools/hvigor/bin/hvigorw.js');
process.env.JAVA_HOME = path.resolve(idePath, 'jbr');
process.env.PATH = `${process.env.PATH};${nodePath};${process.env.JAVA_HOME}\\bin`;
const isNeedDeploy = process.argv.find((arg) => arg === '--deploy');
const isNeedClean = process.argv.find((arg) => arg === '--clean');
const isRelease = process.argv.find((arg) => arg === '--release');
const isDaemon = process.argv.find((arg) => arg === '--daemon');
const product = process.argv.find((arg) => arg.startsWith('--product'))?.split('=')?.[1] ?? 'phone';
const productParam = 'default';
const moduleArg = process.argv.find((arg) => arg.startsWith('--module'))?.split('=')?.[1];
const buildProfile = json5.parse(fs.readFileSync(path.resolve(rootPath, 'build-profile.json5'), 'utf-8'));
const PHONE_MODULE_CONFIG = {
basecommon: '/system/app/SceneBoard/basecommon.hsp',
'phone_sceneboard': '/system/app/SceneBoard/SceneBoard.hap',
'default_notificationmanagement': '/system/app/SceneBoard/SceneBoard_NotificationManagement.hap',
engineservice: '/system/app/SceneBoard/SceneBoard_EngineService.hap',
'themeservice_core': '/system/app/SceneBoard/SceneBoard_ThemeServiceCore.hap',
themecomponent: '/system/app/SceneBoard/SceneBoard_ThemeComponent.hap',
};
const PAD_MODULE_CONFIG = {
basecommon: '/system/app/SceneBoard/basecommon.hsp',
pcresourcesoverlay: '/system/app/SceneBoard/pcresourcesoverlay.hsp',
'pad_sceneboard': '/system/app/SceneBoard/SceneBoard.hap',
'default_notificationmanagement': '/system/app/SceneBoard/SceneBoard_NotificationManagement.hap',
engineservice: '/system/app/SceneBoard/SceneBoard_EngineService.hap',
'themeservice_core': '/system/app/SceneBoard/SceneBoard_ThemeServiceCore.hap',
themecomponent: '/system/app/SceneBoard/SceneBoard_ThemeComponent.hap',
};
const PC_MODULE_CONFIG = {
'pc_sceneboard': '/system/app/SceneBoard/SceneBoard.hap',
'default_notificationmanagement': '/system/app/SceneBoard/SceneBoard_NotificationManagement.hap',
engineservice: '/system/app/SceneBoard/SceneBoard_EngineService.hap',
'themeservice_core': '/system/app/SceneBoard/SceneBoard_ThemeServiceCore.hap',
themecomponent: '/system/app/SceneBoard/SceneBoard_ThemeComponent.hap',
};
async function deployModules(mods, moduleConfig) {
await buildModules(mods, moduleConfig);
if (!isNeedDeploy) {
return;
}
await CommandUtil.run([hdcPath, 'target', 'mount'], {
stderr: process.stderr,
stdout: process.stdout,
});
ignoreAutoReboot();
for (const mod of mods) {
const modOutputPath = getModOutputPath(mod, moduleConfig[mod]);
await sendFile(modOutputPath, moduleConfig[mod]);
}
}
async function sendFile(srcPath, targetPath) {
if (!fs.existsSync(srcPath)) {
console.error(`File ${srcPath} is not exists`);
process.exit(-1);
}
const srcFileSize = fs.statSync(srcPath).size;
let count = 0;
const tempPath = '/data/local/tmp/' + path.basename(targetPath);
CommandUtil.runSync([hdcPath, 'shell', 'rm', tempPath, '-rf']);
console.log(CommandUtil.runSync([hdcPath, 'file', 'send', srcPath, tempPath]));
while (true) {
CommandUtil.runSync([hdcPath, 'shell', 'rm', '-rf', targetPath]);
CommandUtil.runSync([hdcPath, 'shell', 'cp', tempPath, targetPath]);
const targetFileSize = getFileSize(targetPath);
if (targetFileSize === srcFileSize) {
break;
}
if (++count >= 3) {
console.error(`Send file to ${targetPath} failed, srcFileSize: ${srcFileSize}, targetFizeSize: ${targetFileSize}`);
process.exit(1);
}
}
}
function getFileSize(targetPath) {
const output = CommandUtil.runSync([hdcPath, 'shell', 'wc', '-c', targetPath]);
return Number(output.split(' ')[0]);
}
function getBuildArgs(task, mods) {
let args = `${isNeedClean ? 'clean ' : ''}${task} --mode module`;
args += ` -p module=${mods.map((mod) => `${mod}@${getModTarget(mod)}`)} --stacktrace --parallel --incremental`;
args += ` -p product=${productParam}`;
args += isDaemon ? '--daemon' : ' --no-daemon';
if (isRelease) {
args += ` -p buildMode=release`;
}
return args;
}
async function ignoreAutoReboot() {
const amsServiceConfig = await CommandUtil.run([
hdcPath,
'shell',
'cat /system/etc/ams_service_config.json',
], {
stderr: undefined,
stdout: undefined,
});
if (amsServiceConfig.includes('"supportSCBCrashReboot": true,')) {
console.log('amsServiceConfig:', amsServiceConfig);
await CommandUtil.run([
hdcPath,
'shell',
`sed -i 's/\\"supportSCBCrashReboot\\": true,/\\"supportSCBCrashReboot\\": false,/g' /system/etc/ams_service_config.json`
]);
}
}
async function buildModules(mods, moduleConfig) {
const hspMods = mods.filter((mod) => moduleConfig[mod].endsWith('.hsp'));
const hapMods = mods.filter((mod) => moduleConfig[mod].endsWith('.hap'));
if (hspMods.length) {
await CommandUtil.run([process.execPath, hvigorPath, ...getBuildArgs('assembleHsp', hspMods).split(' ')], {
cwd: rootPath,
stderr: process.stderr,
stdout: process.stdout,
});
}
if (hapMods.length) {
await CommandUtil.run([process.execPath, hvigorPath, ...getBuildArgs('assembleHap', hapMods).split(' ')], {
cwd: rootPath,
stderr: process.stderr,
stdout: process.stdout,
});
}
}
function getModTarget(mod) {
return 'default';
}
function getModOutputPath(mod, targetPath) {
const srcPath = buildProfile.modules.find((m) => m.name === mod).srcPath;
const target = getModTarget(mod);
const extname = path.extname(targetPath);
return path.resolve(rootPath, srcPath, `build/${productParam}/outputs/${target}/${mod}-${target}-signed${extname}`);
}
async function restartSceneBoard() {
if (!isNeedDeploy) {
return;
}
await CommandUtil.run([hdcPath, 'shell', `echo ENABLED > /etc/windowscene.config`]);
await CommandUtil.run([hdcPath, 'shell', `kill -9 $(pidof com.ohos.sceneboard)`]);
}
async function rebootDevice() {
if (!isNeedDeploy) {
return;
}
await CommandUtil.run([hdcPath, 'shell', `param set persist.bms.test-upgrade true`]);
await CommandUtil.run([hdcPath, 'shell', `sync /system/bin/udevadm trigger`]);
await CommandUtil.run([hdcPath, 'shell', `reboot`]);
}
async function backup() {
const output = CommandUtil.runSync([hdcPath, 'shell', `ls /data/local/tmp/SceneBoardBak`]);
if (output.includes('No such file or directory')) {
await CommandUtil.run([hdcPath, 'shell', `cp -r /system/app/SceneBoard /data/local/tmp/SceneBoardBak`]);
}
}
(async () => {
const begin = Date.now();
const moduleConfig = product === 'pc' ? PC_MODULE_CONFIG :
product === 'pad' ? PAD_MODULE_CONFIG : PHONE_MODULE_CONFIG;
await backup();
if (moduleArg) {
await deployModules(moduleArg.split(','), moduleConfig);
await restartSceneBoard();
} else {
await deployModules(Object.keys(moduleConfig), moduleConfig);
await rebootDevice();
}
console.log(`${new Date().toLocaleString()}: ${isNeedDeploy ? 'Deploy' : 'Build'} complete in ${Date.now() - begin} ms`);
})();