1/* 2* Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development Co., Ltd. 3* Licensed under the Apache License, Version 2.0 (the "License"); 4* you may not use this file except in compliance with the License. 5* You may obtain a copy of the License at 6* 7* http://www.apache.org/licenses/LICENSE-2.0 8* 9* Unless required by applicable law or agreed to in writing, software 10* distributed under the License is distributed on an "AS IS" BASIS, 11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12* See the License for the specific language governing permissions and 13* limitations under the License. 14*/ 15 16import * as vscode from 'vscode'; 17import * as fs from 'fs'; 18import * as https from 'https'; 19import * as zlib from 'zlib'; 20import * as tar from 'tar'; 21import { Logger } from './common/log'; 22 23const WINDOWS_START = vscode.l10n.t('Starting compilation on Windows.'); 24const TERMINAL_TITLE = vscode.l10n.t('OpenHarmony Cross Compile'); 25const LINUX_START = vscode.l10n.t('Starting compilation on Linux.'); 26 27export function checkNative(platform: string, nativePath: string): boolean { 28 if (platform === "win32") { 29 const cmakePath = nativePath.concat("/build-tools/cmake/bin/cmake.exe"); 30 const toolchainPath = nativePath.concat("/build/cmake/ohos.toolchain.cmake"); 31 const clangPath = nativePath.concat("/llvm/bin/clang.exe"); 32 const arPath = nativePath.concat("/llvm/bin/llvm-ar.exe"); 33 const ranlibPath = nativePath.concat("/llvm/bin/llvm-ranlib.exe"); 34 return fs.existsSync(cmakePath) && fs.existsSync(toolchainPath) && fs.existsSync(clangPath) && fs.existsSync(arPath) && fs.existsSync(ranlibPath); 35 } else { 36 const cmakePath = nativePath.concat("/build-tools/cmake/bin/cmake"); 37 const toolchainPath = nativePath.concat("/build/cmake/ohos.toolchain.cmake"); 38 const clangPath = nativePath.concat("/llvm/bin/clang"); 39 const arPath = nativePath.concat("/llvm/bin/llvm-ar"); 40 const ranlibPath = nativePath.concat("/llvm/bin/llvm-ranlib"); 41 return fs.existsSync(cmakePath) && fs.existsSync(toolchainPath) && fs.existsSync(clangPath) && fs.existsSync(arPath) && fs.existsSync(ranlibPath); 42 } 43} 44 45// 下载url所指示的sdk文件,到destination所指示的文件中 46export function downloadSdk(url: string, destination: string, progress: vscode.Progress<{ increment: number, message?: string }>): Promise<void> { 47 return new Promise((resolve, reject) => { 48 // 创建写入文件流 49 const file = fs.createWriteStream(destination); 50 https.get(url, (response) => { 51 if (response.statusCode === 200) { 52 const totalSize = parseInt(String(response.headers['content-length'])); 53 Logger.getInstance().debug(`totalSize: ${totalSize}`); 54 let downloadedSize = 0; 55 response.on('data', (chunk) => { 56 // 设置response的data事件,当每接收一个数据块时,计算下载进度并报告 57 downloadedSize += chunk.length; 58 const percentage = (downloadedSize / totalSize) * 100; 59 progress.report({ increment: ((chunk.length / totalSize) * 100 * 0.8), message: vscode.l10n.t('Downloading SDK ... {0}%', percentage.toFixed(2)) }); 60 }); 61 response.pipe(file); 62 file.on('finish', () => { 63 file.close(); 64 resolve(); 65 }); 66 } else { 67 if (response.statusCode) { 68 vscode.window.showErrorMessage(vscode.l10n.t('Connection failed! Statuscode: {0}', response.statusCode)); 69 reject(new Error(vscode.l10n.t('Failed to get \'{0}\' ({1})', url, response.statusCode))); 70 } 71 72 } 73 }).on('error', (err) => { 74 fs.unlink(destination, () => reject(err)); 75 }); 76 }); 77} 78 79// 提取filePath所指示的.tar.gz文件,到destination所指示的文件夹中 80export function extractTarGz(filePath: string, destination: string): Promise<void> { 81 return new Promise((resolve, reject) => { 82 fs.createReadStream(filePath) 83 .pipe(zlib.createGunzip()) 84 .pipe(tar.extract({ 85 cwd: destination 86 })) 87 .on('finish', () => resolve()) 88 .on('error', (err) => reject(err)); 89 }); 90} 91 92// 利用终端命令,提取filePath所指示的.zip文件,到destination所指示的文件夹中 93export function extractZip(platform: string, terminal: vscode.Terminal, filePath: string, destination: string): Promise<void> { 94 return new Promise((resolve, reject) => { 95 if (platform === "win32") { 96 terminal.sendText(`Expand-Archive -Path \"${filePath}\" -DestinationPath \"${destination}\" -Force`); 97 terminal.processId?.then( 98 () => { 99 resolve(); 100 }, 101 (err) => { 102 vscode.window.showErrorMessage(vscode.l10n.t('Error extracting file: {0}', err)); 103 reject(err); 104 } 105 ); 106 107 } else { 108 terminal.sendText(`unzip ${filePath} -d ${destination}`); 109 terminal.processId?.then( 110 () => { 111 resolve(); 112 }, 113 (err) => { 114 vscode.window.showErrorMessage(vscode.l10n.t('Error extracting file: {0}', err)); 115 reject(err); 116 } 117 ); 118 } 119 }); 120} 121 122// windows系统下对三方库进行交叉编译 123function crossCompile_win32(terminal: vscode.Terminal | undefined, thirdPartyPath: string, configPath: string, compileTool: string, ohArchitecture: string[], nativePath: string, ohCrossCompilePath: string): Promise<void> { 124 return new Promise((resolve, reject) => { 125 126 vscode.window.showInformationMessage(WINDOWS_START); 127 if (terminal === undefined) { 128 // 若使用本地的sdk,不进行解压操作,则terminal为undefined,在编译前进行创建 129 terminal = terminal = vscode.window.createTerminal({ 130 name: TERMINAL_TITLE, 131 }); 132 terminal.show(); 133 } else { 134 // 若使用下载的sdk,解压完要切换到三方库目录所在的驱动器盘符,以便进行后续编译操作 135 const driveLetter = thirdPartyPath.split('/')[0]; 136 terminal.sendText(`if ($?) {${driveLetter}}`); 137 } 138 139 const configContent = JSON.parse(fs.readFileSync(configPath, 'utf8')); 140 141 // 若配置文件中actions为空,则根据settings设置actions 142 if (configContent.actions === undefined || configContent.actions.length === 0) { 143 let actions = new Array(); 144 for (let arch of ohArchitecture) { 145 // 对每个目标系统架构,先组装出commands为空的action 146 let action = { 147 compileTool: compileTool, 148 ohArchitecture: arch, 149 nativePath: nativePath, 150 thirdPartyPath: thirdPartyPath, 151 installPath: `${ohCrossCompilePath}/${arch}/installed`, 152 cwd: "", 153 commands: [] 154 }; 155 if (compileTool === "cmake") { 156 action.cwd = `${ohCrossCompilePath}/${arch}`; 157 } else { 158 action.cwd = `${thirdPartyPath}`; 159 } 160 actions.push(action); 161 } 162 configContent.actions = actions; 163 } 164 165 // 对配置文件中每个action,若其commands为空,则组装出默认命令 166 for (let action of configContent.actions) { 167 vscode.window.showInformationMessage(vscode.l10n.t('Compiled files of {0} system will be installed at {1}. ', action.ohArchitecture, action.installPath)); 168 if (action.commands === undefined || action.commands.length === 0) { 169 let commands = new Array(); 170 if (action.compileTool === "cmake") { 171 commands.push({ 172 command: `cd ${action.cwd}`, 173 arguments: [] 174 }); 175 commands.push({ 176 command: `${action.nativePath}/build-tools/cmake/bin/cmake.exe`, 177 arguments: [ 178 "-G \"MinGW Makefiles\"", 179 "-DCMAKE_SH=\"CMAKE_SH-NOTFOUND\"", 180 `-DCMAKE_TOOLCHAIN_FILE=${action.nativePath}/build/cmake/ohos.toolchain.cmake`, 181 `-DCMAKE_INSTALL_PREFIX=${action.installPath}`, 182 `-DOHOS_ARCH=${action.ohArchitecture}`, 183 "../..", 184 "-L" 185 ] 186 }); 187 commands.push({ 188 command: "mingw32-make", 189 arguments: [] 190 }); 191 commands.push({ 192 command: "mingw32-make install", 193 arguments: [] 194 }); 195 } else if (action.compileTool === "make") { 196 let target: string; 197 let ld: string; 198 if (action.ohArchitecture === "arm64-v8a") { 199 target = "aarch64-linux-ohos"; 200 ld = "ld64.lld.exe"; 201 } else { 202 target = "arm-linux-ohos"; 203 ld = "ld.lld.exe"; 204 } 205 commands.push({ 206 command: `cd ${action.cwd}`, 207 arguments: [] 208 }); 209 if (ohArchitecture.length > 1) { 210 commands.push({ 211 command: "mingw32-make clean", 212 arguments: [] 213 }); 214 } 215 commands.push({ 216 command: "mingw32-make", 217 arguments: [ 218 `CC=\"${action.nativePath}/llvm/bin/clang.exe --target=${target}\"`, 219 `LD=${action.nativePath}/llvm/bin/${ld}`, 220 `AR=${action.nativePath}/llvm/bin/llvm-ar.exe`, 221 `AS=${action.nativePath}/llvm/bin/llvm-as.exe`, 222 `RANDLIB=${action.nativePath}/llvm/bin/llvm-ranlib.exe`, 223 `STRIP=${action.nativePath}/llvm/bin/llvm-strip.exe` 224 ] 225 }); 226 commands.push({ 227 command: "mingw32-make install", 228 arguments: [ 229 `PREFIX=${action.installPath}` 230 ] 231 }); 232 } 233 action.commands = commands; 234 } 235 } 236 fs.writeFileSync(configPath, JSON.stringify(configContent, null, 4), 'utf8'); 237 238 // 把所有actions的命令拼接在一起,送入终端执行 239 let finalCommand = ""; 240 for (let action of configContent.actions) { 241 for (let item of action.commands) { 242 finalCommand = finalCommand.concat(item.command); 243 for (let i = 0; i <= item.arguments.length - 1; i++) { 244 finalCommand = finalCommand.concat(` ${item.arguments[i]}`); 245 } 246 finalCommand = finalCommand.concat(" ; "); 247 } 248 } 249 terminal.sendText(finalCommand); 250 Logger.getInstance().debug(finalCommand); 251 terminal.processId.then( 252 () => { 253 resolve(); 254 }, 255 (err) => { 256 vscode.window.showErrorMessage(vscode.l10n.t('Error occured while compiling. Error: {0}', err)); 257 reject(err); 258 } 259 ); 260 }); 261} 262 263// linux系统下对三方库进行交叉编译 264function crossCompile_linux(terminal: vscode.Terminal | undefined, thirdPartyPath: string, configPath: string, compileTool: string, ohArchitecture: string[], nativePath: string, ohCrossCompilePath: string): Promise<void> { 265 return new Promise((resolve, reject) => { 266 vscode.window.showInformationMessage(LINUX_START); 267 if (terminal === undefined) { 268 // 若使用本地的sdk,不进行解压操作,则terminal为undefined,在编译前进行创建 269 terminal = terminal = vscode.window.createTerminal({ 270 name: TERMINAL_TITLE, 271 }); 272 terminal.show(); 273 } 274 const configContent = JSON.parse(fs.readFileSync(configPath, 'utf8')); 275 276 // 若配置文件中actions为空,则根据settings设置actions 277 if (configContent.actions === undefined || configContent.actions.length === 0) { 278 let actions = new Array(); 279 for (let arch of ohArchitecture) { 280 // 对每个目标系统架构,先组装出commands为空的action 281 let action = { 282 compileTool: compileTool, 283 ohArchitecture: arch, 284 nativePath: nativePath, 285 thirdPartyPath: thirdPartyPath, 286 installPath: `${ohCrossCompilePath}/${arch}/installed`, 287 cwd: "", 288 commands: [] 289 }; 290 if (compileTool === "cmake") { 291 action.cwd = `${ohCrossCompilePath}/${arch}`; 292 } else { 293 action.cwd = `${thirdPartyPath}`; 294 } 295 actions.push(action); 296 } 297 configContent.actions = actions; 298 } 299 300 // 对配置文件中每个action,若其commands为空,则组装出默认命令 301 for (let action of configContent.actions) { 302 vscode.window.showInformationMessage(vscode.l10n.t('Compiled files of {0} system will be installed at {1}. ', action.ohArchitecture, action.installPath)); 303 if (action.commands === undefined || action.commands.length === 0) { 304 let commands = new Array(); 305 if (action.compileTool === "cmake") { 306 commands.push({ 307 command: `cd ${action.cwd}`, 308 arguments: [] 309 }); 310 commands.push({ 311 command: `${action.nativePath}/build-tools/cmake/bin/cmake`, 312 arguments: [ 313 `-DCMAKE_TOOLCHAIN_FILE=${action.nativePath}/build/cmake/ohos.toolchain.cmake`, 314 `-DCMAKE_INSTALL_PREFIX=${action.installPath}`, 315 `-DOHOS_ARCH=${action.ohArchitecture}`, 316 "../..", 317 "-L" 318 ] 319 }); 320 commands.push({ 321 command: "make", 322 arguments: [] 323 }); 324 commands.push({ 325 command: "make install", 326 arguments: [] 327 }); 328 } else if (action.compileTool === "make") { 329 let target: string; 330 let ld: string; 331 if (action.ohArchitecture === "arm64-v8a") { 332 target = "aarch64-linux-ohos"; 333 ld = "ld64.lld"; 334 } else { 335 target = "arm-linux-ohos"; 336 ld = "ld.lld"; 337 } 338 commands.push({ 339 command: `cd ${action.cwd}`, 340 arguments: [] 341 }); 342 if (ohArchitecture.length > 1) { 343 commands.push({ 344 command: "make clean", 345 arguments: [] 346 }); 347 } 348 commands.push({ 349 command: "make", 350 arguments: [ 351 `CC=\"${action.nativePath}/llvm/bin/clang --target=${target}\"`, 352 `LD=${action.nativePath}/llvm/bin/${ld}`, 353 `AR=${action.nativePath}/llvm/bin/llvm-ar`, 354 `AS=${action.nativePath}/llvm/bin/llvm-as`, 355 `RANDLIB=${action.nativePath}/llvm/bin/llvm-ranlib`, 356 `STRIP=${action.nativePath}/llvm/bin/llvm-strip` 357 ] 358 }); 359 commands.push({ 360 command: "make install", 361 arguments: [ 362 `PREFIX=${action.installPath}` 363 ] 364 }); 365 } else if (action.compileTool === "configure") { 366 let target: string; 367 let ld: string; 368 if (action.ohArchitecture === "arm64-v8a") { 369 target = "aarch64-linux-ohos"; 370 ld = "ld64.lld"; 371 } else { 372 target = "arm-linux-ohos"; 373 ld = "ld.lld"; 374 } 375 commands.push({ 376 command: `cd ${action.cwd}`, 377 arguments: [] 378 }); 379 if (ohArchitecture.length > 1) { 380 commands.push({ 381 command: "make clean", 382 arguments: [] 383 }); 384 } 385 commands.push({ 386 command: "./configure", 387 arguments: [ 388 `--host=${target}`, 389 `--prefix=${action.installPath}`, 390 391 //针对libcoap库的编译选项 392 // `--disable-documentation`, 393 // `--disable-dtls`, 394 395 `CFLAGS=\"-pthread\"`, 396 `LDFLAGS=\"-pthread\"`, 397 `CC=\"${action.nativePath}/llvm/bin/clang --target=${target}\"`, 398 `LD=${action.nativePath}/llvm/bin/${ld}`, 399 `AR=${action.nativePath}/llvm/bin/llvm-ar`, 400 `AS=${action.nativePath}/llvm/bin/llvm-as`, 401 `RANDLIB=${action.nativePath}/llvm/bin/llvm-ranlib`, 402 `STRIP=${action.nativePath}/llvm/bin/llvm-strip` 403 ] 404 }); 405 commands.push({ 406 command: "make", 407 arguments: [] 408 }); 409 commands.push({ 410 command: "make install", 411 arguments: [] 412 }); 413 } 414 action.commands = commands; 415 } 416 } 417 fs.writeFileSync(configPath, JSON.stringify(configContent, null, 4), 'utf8'); 418 419 // 把所有actions的命令拼接在一起,送入终端执行 420 let finalCommand = ""; 421 for (let action of configContent.actions) { 422 for (let item of action.commands) { 423 finalCommand = finalCommand.concat(item.command); 424 for (let i = 0; i <= item.arguments.length - 1; i++) { 425 finalCommand = finalCommand.concat(` ${item.arguments[i]}`); 426 } 427 finalCommand = finalCommand.concat(" ; "); 428 } 429 } 430 terminal.sendText(finalCommand); 431 Logger.getInstance().debug(finalCommand); 432 terminal.processId.then( 433 () => { 434 resolve(); 435 }, 436 (err) => { 437 vscode.window.showErrorMessage(vscode.l10n.t('Error occured while compiling. Error: {0}', err)); 438 reject(err); 439 } 440 ); 441 }); 442} 443 444 445export function crossCompile(platform: string, terminal: vscode.Terminal | undefined, configPath: string, thirdPartyPath: string, compileTool: string, ohArchitecture: string[], nativePath: string, ohCrossCompilePath: string) { 446 if (platform === "win32") { 447 crossCompile_win32(terminal, thirdPartyPath, configPath, compileTool, ohArchitecture, nativePath, ohCrossCompilePath); 448 } else { 449 crossCompile_linux(terminal, thirdPartyPath, configPath, compileTool, ohArchitecture, nativePath, ohCrossCompilePath); 450 } 451}