1/* 2* Copyright (c) 2022 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*/ 15const { Logger } = require('./logger'); 16const fs = require('fs'); 17const path = require('path'); 18const { Tool } = require('./tool'); 19 20class AnalyzeCommand { 21 constructor() { 22 23 } 24 25 static isCmd(cmd, name) { 26 let cmdName = cmd.split(' ')[0]; 27 let v1 = cmdName.endsWith(name); 28 let v2 = cmdName.endsWith(name + '.exe'); 29 return v1 || v2; 30 } 31 32 static COMPILE_CMDS = { 33 'clang': 0, 34 'ar': 1, 35 'clang++': 2 36 }; 37 38 static COLLECT_COMMANDS = []; 39 static storeCommands() { 40 fs.writeFileSync(path.join(Tool.CURRENT_TOOL_PATH, 'cmds.txt'), 41 AnalyzeCommand.COLLECT_COMMANDS.join('\n'), { encoding: 'utf8' }); 42 } 43 static getCompileCmdId(cmd) { 44 let cmdName = cmd.split(' ')[0]; 45 for (let c in AnalyzeCommand.COMPILE_CMDS) { 46 if (cmdName.endsWith(c) || cmdName.endsWith(c + '.exe')) { 47 return AnalyzeCommand.COMPILE_CMDS[c];//返回命令ID 48 } 49 } 50 return -1; 51 } 52 static analyze(cmd) { 53 let cmds; 54 if (cmd.indexOf('&&') >= 0) { 55 cmds = cmd.split('&&'); 56 } 57 else { 58 cmds = [cmd]; 59 } 60 let result = []; 61 Tool.backupd(0); 62 for (let c of cmds) { 63 let ret = AnalyzeCommand.analyzeOneCmd(c); 64 if (ret) { 65 result.push(...ret); 66 } 67 } 68 Tool.recoverd(0); 69 return result; 70 } 71 static exAnalyzeCmake(cmd) { 72 let ss = cmd.split(' '); 73 if (ss.indexOf('-P') > 0) {//需要 cmake执行脚本,在这里直接执行 or 移到BUILD.gn里面执行 74 const childProcess = require('child_process'); 75 childProcess.execSync(cmd); 76 AnalyzeCommand.COLLECT_COMMANDS.push(cmd); 77 return false; 78 } 79 let cmakeLinkScriptOffset = ss.indexOf('cmake_link_script'); 80 if (cmakeLinkScriptOffset >= 0) {//需要 这里可能要做一些错误判断 81 let cmakeLinkScriptFile = ss[cmakeLinkScriptOffset + 1]; 82 let cmakeLinkScriptData = fs.readFileSync(path.join(process.cwd(), cmakeLinkScriptFile), 83 { encoding: 'utf8' }); 84 let cmds = cmakeLinkScriptData.split('\n');//link.txt中可能有多条命令链接 85 let rets = []; 86 for (let c of cmds) { 87 let r = AnalyzeCommand.analyzeOneCmd(c); 88 if (r) { 89 rets.push(...r); 90 } 91 } 92 if (rets.length > 0) { 93 return rets; 94 } 95 else { 96 return false; 97 } 98 } 99 return false; 100 } 101 static isCmdScriptWithVersion(cmd, cmdType) { 102 let cmdName = cmd.split(' ')[0]; 103 let pos = cmdName.lastIndexOf('/'); 104 let scrType = cmdName.substring(pos + 1, cmdName.length); 105 return scrType.startsWith(cmdType); 106 } 107 108 static analyzeOneCmd(cmd) { 109 while (cmd.startsWith('\n') || cmd.startsWith(' ')) { 110 cmd = cmd.substring(1); 111 } 112 if (cmd.length <= 0) { 113 return false; 114 } 115 if (cmd.match('^make(\\[\\d+\\]:)|: (Entering)|(Leaving) directory')) {//跳过进出目录的log 116 //需要 改变工作目录 117 return false; 118 } 119 if (cmd.startsWith('cd ')) { 120 let t = AnalyzeCommand.splitString(cmd); 121 Tool.pushd(t[1]);//改变工作目录 122 return false; 123 } 124 if (AnalyzeCommand.isCmd(cmd, 'ccache')) {//去掉ccache头 125 cmd = cmd.substring(cmd.indexOf('ccache') + 'ccache'.length); 126 return AnalyzeCommand.analyzeOneCmd(cmd); 127 } 128 if (AnalyzeCommand.isCmd(cmd, 'cmake')) {//跳过cmake的log,需要解析link命令行 129 return AnalyzeCommand.exAnalyzeCmake(cmd); 130 } 131 if (AnalyzeCommand.isCmd(cmd, 'make') || 132 AnalyzeCommand.isCmd(cmd, 'ranlib')) {//跳过这些命令 133 return false; 134 } 135 if (AnalyzeCommand.getCompileCmdId(cmd) >= 0) {//解析编译命令行 136 AnalyzeCommand.COLLECT_COMMANDS.push(cmd); 137 return [AnalyzeCommand.analyzeCompileCommand(cmd)]; 138 } 139 if (AnalyzeCommand.isCmd(cmd, 'perl') || 140 AnalyzeCommand.isCmd(cmd, 'autoreconf') || 141 AnalyzeCommand.isCmdScriptWithVersion(cmd, 'python')) { 142 // 需要即时执行(可能会生成依赖源文件),如果不执行,后续编译命令可能会报错,找不到源文件 143 Logger.info(cmd); 144 const childProcess = require('child_process'); 145 childProcess.execSync(cmd); 146 return false; 147 } 148 Logger.err('未解析的命令行:' + cmd); 149 return false; 150 } 151 152 static resultTemplete() {//解析命令行之后的结果模板 153 return { 154 type: 0, //0 compile command,1 other command 155 workDir: process.cwd(), 156 command: '', 157 inputs: [], 158 target: '', 159 isLink: false, //是否编译,.a/.o/可执行程序,需要生成目标 160 includes: [], 161 defines: [ 162 '_XOPEN_SOURCE=600', //ohos的编译环境缺失宏 163 'FE_TONEAREST=0x00000000', 164 'FE_UPWARD=0x00400000', 165 'FE_DOWNWARD=0x00800000', 166 'FE_TOWARDZERO=0x00c00000', 167 ], 168 cflags: [ 169 '-Wno-implicit-function-declaration', 170 '-Wno-unused-function', 171 '-Wno-comments', //允许注释后面有个\ 172 '-Wno-string-conversion', //允许char*当做bool使用 173 '-Wno-header-hygiene', //不检测命名空间污染 174 '-frtti', //支持typeid(xxx) 175 '-fexceptions', //支持try catch 176 ], //c和c++选项 177 cflagsCc: [], //c++选项 178 cflagsC: [], //c选项 179 }; 180 } 181 182 static splitString(s) {//按空格分割字符串 183 let ret = []; 184 let startp = -1; 185 let isContinuChar = 0; 186 for (let p = 0; p < s.length; p++) { 187 if (s[p] === '\"' && s[p - 1] !== '\\') { 188 isContinuChar = 1 - isContinuChar; 189 } 190 if (startp >= 0) { 191 if (s[p] === ' ' && isContinuChar === 0) { 192 ret.push(s.substring(startp, p)); 193 startp = -1; 194 } 195 } 196 else if (s[p] !== ' ') { 197 startp = p; 198 } 199 } 200 if (startp >= 0) { 201 ret.push(s.substring(startp)); 202 } 203 return ret; 204 } 205 206 static mockTarget(t) { 207 if (t.target) { 208 fs.writeFileSync(t.target, ' '); 209 } 210 } 211 static clangCheck1(e) { 212 let ss = ['--sysroot=', 213 '-pthread', 214 '-Qunused-arguments', 215 '-ffunction-sections', 216 '-fdata-sections', 217 '-fvisibility=hidden', 218 '-fvisibility-inlines-hidden', 219 '-O3', 220 '-Os', 221 '-fPIC', 222 '-pedantic', 223 '-fwrapv', 224 '-shared', 225 '-lm', 226 '-lpthread', 227 '-lz', 228 '-MD', 229 '-isystem' 230 ]; 231 for (let s of ss) { 232 if (e.startsWith(s) || e === '-w') { 233 return true; 234 } 235 } 236 return false; 237 } 238 static clangCheck2(local, e) { 239 if (e.startsWith('-MT') || e.startsWith('-MF')) { 240 if (e.length === 3) { 241 local.p++; 242 } 243 return true; 244 } 245 246 if (e.startsWith('-s')) { 247 if (e.length === 2) { 248 local.p++; 249 } 250 return true; 251 } 252 return false; 253 } 254 static clangCheck3(local, e) { 255 if (e.startsWith('-D')) {//需要记录到defines里面的参数 256 //需要 是否-D开头的,全部记录到defines里面 257 if (e.length === 2) {//-D xxx 258 local.ret.defines.push(local.eles[local.p++]); 259 } 260 else {//-Dxxx 261 local.ret.defines.push(e.substring(2)); 262 } 263 return true; 264 } 265 return false; 266 } 267 static clangCheck4(local, e) { 268 if (e.startsWith('-I')) {//需要记录到includes的参数 269 if (e.length === 2) {//-I xxx 270 local.ret.includes.push(local.eles[local.p++]); 271 } 272 else {//-Ixxx 273 local.ret.includes.push(e.substring(2)); 274 } 275 return true; 276 } 277 return false; 278 } 279 static validCFlag(cflag, allowedFlag) { 280 for (let i = 0; i < allowedFlag.length; ++i) { 281 if (cflag.startsWith(allowedFlag[i])) { 282 return true; 283 } 284 } 285 return false; 286 } 287 static clangCheck5(local, e) { 288 if (this.validCFlag(e, Tool.getAllowedC().compileflag) || (e === '-D__clang__')) { 289 local.ret.cflags.push(e); //需要记录到flags里面的参数 290 return true; 291 } 292 return false; 293 } 294 static clangCheck6(local, e) { 295 if (e === '-o') { 296 if (e.length === 2) {//-o xxx 297 local.ret.target = local.eles[local.p++]; 298 } 299 else {//-oxxx 300 local.ret.target = e.substring(2); 301 } 302 if (local.ret.target.endsWith('.a') || 303 local.ret.target.endsWith('.so') || 304 (!e.endsWith('.c') && !e.endsWith('.o'))) { 305 local.ret.isLink = true; 306 } 307 return true; 308 } 309 return false; 310 } 311 312 static clangCheck7(local, e) { 313 if (this.validSuffix(e, Tool.getAllowedC().fileSuffix)) { 314 local.ret.inputs.push(e); 315 return true; 316 } 317 if (e.endsWith('.rsp')) { 318 clangCheck7RspEnds(e, local); 319 return true; 320 } 321 return false; 322 } 323 324 static analyzeCcClang(cmd) { 325 let local = { 326 ret: AnalyzeCommand.resultTemplete(), 327 eles: AnalyzeCommand.splitString(cmd), 328 p: 0, 329 }; 330 331 while (local.p < local.eles.length) { 332 let e = local.eles[local.p++]; 333 if (e.endsWith('clang') || e.endsWith('clang.exe')) { 334 local.ret.command = e; 335 } 336 else if (AnalyzeCommand.clangCheck1(e)) { } 337 else if (AnalyzeCommand.clangCheck2(local, e)) { } 338 else if (e.startsWith('-Wl,--dynamic-linker,') || e.startsWith('-rdynamic')) {//-----直接忽略的链接参数 339 local.ret.isLink = true; 340 } 341 else if (AnalyzeCommand.clangCheck3(local, e)) { } 342 else if (AnalyzeCommand.clangCheck4(local, e)) { } 343 else if (AnalyzeCommand.clangCheck5(local, e)) { } 344 else if (e.startsWith('-std=')) { 345 local.ret.cflagsCc.push(e); 346 } 347 else if (e.startsWith('-W')) {//需要 -W开头的怎么处理,-W -Wall -Werror=return-type -Wno-unnamed-type-template-args 348 if (e.startsWith('-Wno-')) { 349 local.ret.cflags.push(e); 350 } 351 } 352 else if (AnalyzeCommand.clangCheck6(local, e)) { } 353 else if (e === '-c') {//编译 354 local.ret.isLink = false; 355 } 356 else if (AnalyzeCommand.clangCheck7(local, e)) { } 357 else { 358 Logger.err(cmd + '\nclang未解析参数 ' + e); 359 process.exit(); 360 } 361 } 362 Logger.info('----clang-----' + local.ret.workDir + '\n\t' + local.ret.isLink + ',' + local.ret.target); 363 return local.ret; 364 } 365 static analyzeCcAr(cmd) { 366 if (cmd.endsWith('\r')) { 367 cmd = cmd.substring(0, cmd.length - 1); 368 } 369 let ret = AnalyzeCommand.resultTemplete(); 370 let eles = AnalyzeCommand.splitString(cmd); 371 ret.isLink = true; 372 let p = 0; 373 while (p < eles.length) { 374 let e = eles[p++]; 375 if (e.endsWith('ar') || e.endsWith('ar.exe')) { 376 ret.command = e; 377 } 378 else if (e.endsWith('.a')) { 379 ret.target = e; 380 } 381 else if (e.endsWith('.o')) { 382 ret.inputs.push(e); 383 } 384 else if (e === 'qc') { 385 } 386 else { 387 Logger.err(cmd + '\nar未解析参数 ' + e); 388 process.exit(); 389 } 390 } 391 Logger.info('---ar----' + ret.workDir + '\n\t' + ret.isLink + ',' + ret.target); 392 return ret; 393 } 394 395 static clangxxCheck1(e) { 396 let ss = ['--sysroot=', 397 '-pthread', 398 '-Qunused-arguments', 399 '-ffunction-sections', 400 '-fdata-sections', 401 '-fvisibility=hidden', 402 '-fvisibility-inlines-hidden', 403 '-funwind-tables', 404 '-fwrapv', 405 '-O3', 406 '-fPIC', 407 '-shared', 408 '-ldl', 409 '-lm', 410 '-lpthread', 411 '-lrt', 412 '-fPIE', 413 '-g', 414 '-ftemplate-depth=1024', 415 '-pedantic-errors' 416 ]; 417 for (let s of ss) { 418 if (e.startsWith(s)) { 419 return true; 420 } 421 } 422 if (e === '-w') {//-----直接忽略的编译参数(和链接参数) 423 return true; 424 } 425 return false; 426 } 427 static clangxxCheck2(local, e) { 428 if (e.startsWith('-isystem')) {//需要 不清楚这个有什么用 429 if (e === '-isystem') {//-isystem xxxx 430 local.ret.includes.push(local.eles[local.p++]); 431 } 432 else {//-Ixxx 433 local.ret.includes.push(e.substring(2)); 434 } 435 return true; 436 } 437 return false; 438 } 439 static clangxxCheck3(local, e) { 440 if (e.startsWith('-D')) {//需要记录到defines里面的参数 441 //需要 是否-D开头的,全部记录到defines里面 442 if (e.length === 2) {//-D xxx 443 local.ret.defines.push(local.eles[local.p++]); 444 } 445 else {//-Dxxx 446 local.ret.defines.push(e.substring(2)); 447 } 448 return true; 449 } 450 return false; 451 } 452 static clangxxCheck4(local, e) { 453 if (e.startsWith('-I')) {//需要记录到includes的参数 454 if (e.length === 2) {//-I xxx 455 local.ret.includes.push(local.eles[local.p++]); 456 } 457 else {//-Ixxx 458 local.ret.includes.push(e.substring(2)); 459 } 460 return true; 461 } 462 return false; 463 } 464 static clangxxCheck5(local, e) { 465 if (this.validCFlag(e, Tool.getAllowedCxx().compileflag)) { 466 local.ret.cflags.push(e); //需要记录到flags里面的参数 467 return true; 468 } 469 return false; 470 } 471 static clangxxCheck6(local, e) { 472 if (e.startsWith('-Xclang')) {//透传参数 473 let v = local.eles[local.p++]; 474 if (v !== '-emit-pch') {//需要丢弃这个选项 475 local.ret.cflags.push(e); 476 local.ret.cflags.push(v); 477 } 478 return true; 479 } 480 return false; 481 } 482 static clangxxCheck7(local, e) { 483 if (e.startsWith('-W')) {//需要 -W开头的怎么处理,-W -Wall -Werror=return-type -Wno-unnamed-type-template-args 484 if (e.startsWith('-Wno-')) { 485 local.ret.cflags.push(e); 486 } 487 return true; 488 } 489 return false; 490 } 491 static clangxxCheck8(local, e) { 492 if (e === '-o') { 493 if (e.length === 2) {//-o xxx 494 local.ret.target = local.eles[local.p++]; 495 } 496 else {//-oxxx 497 local.ret.target = e.substring(2); 498 } 499 if (local.ret.target.endsWith('.a') || 500 local.ret.target.endsWith('.so') || 501 (!e.endsWith('.c') && !e.endsWith('.o'))) { 502 local.ret.isLink = true; 503 } 504 return true; 505 } 506 return false; 507 } 508 static validSuffix(filePath, allowedSuffix) { 509 for (let i = 0; i < allowedSuffix.length; ++i) { 510 if (filePath.endsWith(allowedSuffix[i])) { 511 return true; 512 } 513 } 514 if (filePath.search(/\.so[\d\.]*$/) > 0) { 515 return true; 516 } 517 return false; 518 } 519 static clangxxCheck9(local, e) { 520 if (this.validSuffix(e, Tool.getAllowedCxx().fileSuffix) || (e.indexOf('.so.') > 0)) { 521 local.ret.inputs.push(e); 522 return true; 523 } 524 if (e.endsWith('.rsp')) { 525 console.log(Tool.CURRENT_DIR); 526 let rspth = path.join(Tool.CURRENT_DIR, e.substring(1)); 527 let data = fs.readFileSync(rspth, { encoding: 'utf8' }); 528 if (data.endsWith('\r\n')) { 529 data = data.substring(0, data.length - 2); 530 } 531 let datas = data.split(' '); 532 let pp = ['.c', 533 '.o', 534 '.o"', 535 '.a', 536 '.S', 537 '.so' 538 ]; 539 clangxxCheck9Func(datas, pp, local); 540 return true; 541 } 542 return false; 543 } 544 static analyzeCcClangxx(cmd) { 545 if (cmd.indexOf('\"')) { 546 cmd = cmd.replace(/\"/g, ''); 547 } 548 let local = { 549 ret: AnalyzeCommand.resultTemplete(), 550 eles: AnalyzeCommand.splitString(cmd), 551 p: 0, 552 }; 553 while (local.p < local.eles.length) { 554 let e = local.eles[local.p++]; 555 if (e.endsWith('clang++') || e.endsWith('clang++.exe')) { 556 local.ret.command = e; 557 } 558 else if (AnalyzeCommand.clangxxCheck1(e)) { } 559 else if (e.startsWith('-fno-rtti')) { 560 local.ret.cflags.splice(local.ret.cflags.indexOf('-frtti'), 1); 561 } 562 else if (e.startsWith('-fno-exceptions')) { 563 local.ret.cflags.splice(local.ret.cflags.indexOf('-fexceptions'), 1); 564 } 565 else if (AnalyzeCommand.clangxxCheck2(local, e)) { } 566 else if (AnalyzeCommand.clangxxCheck3(local, e)) { } 567 else if (AnalyzeCommand.clangxxCheck4(local, e)) { } 568 else if (AnalyzeCommand.clangxxCheck5(local, e)) { } 569 else if (AnalyzeCommand.clangxxCheck6(local, e)) { } 570 else if (e.startsWith('-std=')) { 571 local.ret.cflagsCc.push(e); 572 } 573 else if (AnalyzeCommand.clangxxCheck7(local, e)) { } 574 else if (AnalyzeCommand.clangxxCheck8(local, e)) { } 575 else if (e === '-c') {//编译 576 local.ret.isLink = false; 577 } 578 else if (AnalyzeCommand.clangxxCheck9(local, e)) { } 579 else { 580 Logger.err(cmd + '\nclang++未解析参数 ' + e); 581 process.exit(); 582 } 583 } 584 Logger.info('---clang++----' + local.ret.workDir + '\n\t' + local.ret.isLink + ',' + local.ret.target); 585 return local.ret; 586 } 587 588 static analyzeCompileCommand(cmd) { 589 //整理命令行 590 while (cmd.indexOf('\\\n') >= 0) {//去除\换行 591 cmd = cmd.replace('\\\n', ''); 592 } 593 while (cmd.indexOf('\t') >= 0) {//所有tab换成空格 594 cmd = cmd.replace('\t', ' '); 595 } 596 while (cmd.endsWith('\n') || cmd.endsWith(' ')) { 597 cmd = cmd.substring(0, cmd.length - 1); 598 } 599 let ret = null; 600 switch (AnalyzeCommand.getCompileCmdId(cmd)) { 601 case AnalyzeCommand.COMPILE_CMDS.clang: 602 ret = AnalyzeCommand.analyzeCcClang(cmd); 603 break; 604 case AnalyzeCommand.COMPILE_CMDS.ar: 605 ret = AnalyzeCommand.analyzeCcAr(cmd); 606 break; 607 case AnalyzeCommand.COMPILE_CMDS.clang++: 608 ret = AnalyzeCommand.analyzeCcClangxx(cmd); 609 break; 610 } 611 if (ret) { 612 AnalyzeCommand.mockTarget(ret);//解析出的目标,touch一个出来,否则会出现不同Makefile中依赖无法找到的问题 613 return ret; 614 } 615 Logger.err('解析编译命令行失败:' + cmd); 616 return false; 617 } 618} 619 620function clangCheck7RspEnds(e, local) { 621 console.log(Tool.CURRENT_DIR); 622 let rspth = path.join(Tool.CURRENT_DIR, e.substring(1)); 623 let data = fs.readFileSync(rspth, { encoding: 'utf8' }); 624 if (data.endsWith('\r\n')) { 625 data = data.substring(0, data.length - 2); 626 } 627 let datas = data.split(' '); 628 for (let d of datas) { 629 for (let s of ss) { 630 if (d.endsWith(s)) { 631 local.ret.inputs.push(d); 632 } 633 } 634 } 635} 636 637function clangxxCheck9Func(datas, pp, local) { 638 for (let d of datas) { 639 for (let p of pp) { 640 if (d.endsWith(p)) { 641 local.ret.inputs.push(d); 642 } 643 } 644 } 645} 646 647module.exports = { 648 AnalyzeCommand 649};