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 console.log(Tool.CURRENT_DIR); 319 let rspth = path.join(Tool.CURRENT_DIR, e.substring(1)); 320 let data = fs.readFileSync(rspth, { encoding: "utf8" }); 321 if (data.endsWith("\r\n")) { 322 data = data.substring(0, data.length - 2); 323 } 324 let datas = data.split(" "); 325 for (let d of datas) { 326 for (let s of ss) { 327 if (d.endsWith(s)) { 328 local.ret.inputs.push(d); 329 } 330 } 331 } 332 return true; 333 } 334 return false; 335 } 336 337 static analyzeCcClang(cmd) { 338 let local = { 339 ret: AnalyzeCommand.resultTemplete(), 340 eles: AnalyzeCommand.splitString(cmd), 341 p: 0, 342 } 343 344 while (local.p < local.eles.length) { 345 let e = local.eles[local.p++]; 346 if (e.endsWith("clang") || e.endsWith("clang.exe")) { 347 local.ret.command = e; 348 } 349 else if (AnalyzeCommand.clangCheck1(e)) { } 350 else if (AnalyzeCommand.clangCheck2(local, e)) { } 351 else if (e.startsWith("-Wl,--dynamic-linker,") || e.startsWith("-rdynamic")) {//-----直接忽略的链接参数 352 local.ret.isLink = true; 353 } 354 else if (AnalyzeCommand.clangCheck3(local, e)) { } 355 else if (AnalyzeCommand.clangCheck4(local, e)) { } 356 else if (AnalyzeCommand.clangCheck5(local, e)) { } 357 else if (e.startsWith("-std=")) { 358 local.ret.cflagsCc.push(e); 359 } 360 else if (e.startsWith("-W")) {//需要 -W开头的怎么处理,-W -Wall -Werror=return-type -Wno-unnamed-type-template-args 361 if (e.startsWith("-Wno-")) { 362 local.ret.cflags.push(e); 363 } 364 } 365 else if (AnalyzeCommand.clangCheck6(local, e)) { } 366 else if (e == "-c") {//编译 367 local.ret.isLink = false; 368 } 369 else if (AnalyzeCommand.clangCheck7(local, e)) { } 370 else { 371 Logger.err(cmd + "\nclang未解析参数 " + e); 372 process.exit(); 373 } 374 } 375 Logger.info("----clang-----" + local.ret.workDir + "\n\t" + local.ret.isLink + "," + local.ret.target) 376 return local.ret; 377 } 378 static analyzeCcAr(cmd) { 379 if (cmd.endsWith("\r")) { 380 cmd = cmd.substring(0, cmd.length - 1); 381 } 382 let ret = AnalyzeCommand.resultTemplete(); 383 let eles = AnalyzeCommand.splitString(cmd); 384 ret.isLink = true; 385 let p = 0; 386 while (p < eles.length) { 387 let e = eles[p++]; 388 if (e.endsWith("ar") || e.endsWith("ar.exe")) { 389 ret.command = e; 390 } 391 else if (e.endsWith(".a")) { 392 ret.target = e; 393 } 394 else if (e.endsWith(".o")) { 395 ret.inputs.push(e); 396 } 397 else if (e == "qc") { 398 } 399 else { 400 Logger.err(cmd + "\nar未解析参数 " + e); 401 process.exit(); 402 } 403 } 404 Logger.info("---ar----" + ret.workDir + "\n\t" + ret.isLink + "," + ret.target); 405 return ret; 406 } 407 408 static clangxxCheck1(e) { 409 let ss = ["--sysroot=", 410 "-pthread", 411 "-Qunused-arguments", 412 "-ffunction-sections", 413 "-fdata-sections", 414 "-fvisibility=hidden", 415 "-fvisibility-inlines-hidden", 416 "-funwind-tables", 417 "-fwrapv", 418 "-O3", 419 "-fPIC", 420 "-shared", 421 "-ldl", 422 "-lm", 423 "-lpthread", 424 "-lrt", 425 "-fPIE", 426 "-g", 427 "-ftemplate-depth=1024", 428 "-pedantic-errors" 429 ]; 430 for (let s of ss) { 431 if (e.startsWith(s)) { 432 return true; 433 } 434 } 435 if (e == "-w") {//-----直接忽略的编译参数(和链接参数) 436 return true; 437 } 438 return false; 439 } 440 static clangxxCheck2(local, e) { 441 if (e.startsWith("-isystem")) {//需要 不清楚这个有什么用 442 if (e == "-isystem") {//-isystem xxxx 443 local.ret.includes.push(local.eles[local.p++]); 444 } 445 else {//-Ixxx 446 local.ret.includes.push(e.substring(2)); 447 } 448 return true; 449 } 450 return false; 451 } 452 static clangxxCheck3(local, e) { 453 if (e.startsWith("-D")) {//需要记录到defines里面的参数 454 //需要 是否-D开头的,全部记录到defines里面 455 if (e.length == 2) {//-D xxx 456 local.ret.defines.push(local.eles[local.p++]); 457 } 458 else {//-Dxxx 459 local.ret.defines.push(e.substring(2)); 460 } 461 return true; 462 } 463 return false; 464 } 465 static clangxxCheck4(local, e) { 466 if (e.startsWith("-I")) {//需要记录到includes的参数 467 if (e.length == 2) {//-I xxx 468 local.ret.includes.push(local.eles[local.p++]); 469 } 470 else {//-Ixxx 471 local.ret.includes.push(e.substring(2)); 472 } 473 return true; 474 } 475 return false; 476 } 477 static clangxxCheck5(local, e) { 478 if (this.validCFlag(e, Tool.getAllowedCxx().compileflag)) { 479 local.ret.cflags.push(e); //需要记录到flags里面的参数 480 return true; 481 } 482 return false; 483 } 484 static clangxxCheck6(local, e) { 485 if (e.startsWith("-Xclang")) {//透传参数 486 let v = local.eles[local.p++]; 487 if (v != "-emit-pch") {//需要丢弃这个选项 488 local.ret.cflags.push(e); 489 local.ret.cflags.push(v); 490 } 491 return true; 492 } 493 return false; 494 } 495 static clangxxCheck7(local, e) { 496 if (e.startsWith("-W")) {//需要 -W开头的怎么处理,-W -Wall -Werror=return-type -Wno-unnamed-type-template-args 497 if (e.startsWith("-Wno-")) { 498 local.ret.cflags.push(e); 499 } 500 return true; 501 } 502 return false; 503 } 504 static clangxxCheck8(local, e) { 505 if (e == "-o") { 506 if (e.length == 2) {//-o xxx 507 local.ret.target = local.eles[local.p++]; 508 } 509 else {//-oxxx 510 local.ret.target = e.substring(2); 511 } 512 if (local.ret.target.endsWith(".a") || 513 local.ret.target.endsWith(".so") || 514 (!e.endsWith(".c") && !e.endsWith(".o"))) { 515 local.ret.isLink = true; 516 } 517 return true; 518 } 519 return false; 520 } 521 static validSuffix(filePath, allowedSuffix) { 522 for (let i = 0; i < allowedSuffix.length; ++i) { 523 if (filePath.endsWith(allowedSuffix[i])) { 524 return true; 525 } 526 } 527 if (filePath.search(/\.so[\d\.]*$/) > 0) { 528 return true; 529 } 530 return false; 531 } 532 static clangxxCheck9(local, e) { 533 if (this.validSuffix(e, Tool.getAllowedCxx().fileSuffix) || (e.indexOf(".so.") > 0)) { 534 local.ret.inputs.push(e); 535 return true; 536 } 537 if (e.endsWith(".rsp")) { 538 console.log(Tool.CURRENT_DIR); 539 let rspth = path.join(Tool.CURRENT_DIR, e.substring(1)); 540 let data = fs.readFileSync(rspth, { encoding: "utf8" }); 541 if (data.endsWith("\r\n")) { 542 data = data.substring(0, data.length - 2); 543 } 544 let datas = data.split(" "); 545 let pp = [".c", 546 ".o", 547 '.o"', 548 ".a", 549 ".S", 550 ".so" 551 ]; 552 for (let d of datas) { 553 for (let p of pp) { 554 if (d.endsWith(p)) { 555 local.ret.inputs.push(d); 556 } 557 } 558 } 559 return true; 560 } 561 return false; 562 } 563 static analyzeCcClangxx(cmd) { 564 if (cmd.indexOf("\"")) { 565 cmd = cmd.replace(/\"/g, ""); 566 } 567 let local = { 568 ret: AnalyzeCommand.resultTemplete(), 569 eles: AnalyzeCommand.splitString(cmd), 570 p: 0, 571 } 572 while (local.p < local.eles.length) { 573 let e = local.eles[local.p++]; 574 if (e.endsWith("clang++") || e.endsWith("clang++.exe")) { 575 local.ret.command = e; 576 } 577 else if (AnalyzeCommand.clangxxCheck1(e)) { } 578 else if (e.startsWith("-fno-rtti")) { 579 local.ret.cflags.splice(local.ret.cflags.indexOf("-frtti"), 1); 580 } 581 else if (e.startsWith("-fno-exceptions")) { 582 local.ret.cflags.splice(local.ret.cflags.indexOf("-fexceptions"), 1); 583 } 584 else if (AnalyzeCommand.clangxxCheck2(local, e)) { } 585 else if (AnalyzeCommand.clangxxCheck3(local, e)) { } 586 else if (AnalyzeCommand.clangxxCheck4(local, e)) { } 587 else if (AnalyzeCommand.clangxxCheck5(local, e)) { } 588 else if (AnalyzeCommand.clangxxCheck6(local, e)) { } 589 else if (e.startsWith("-std=")) { 590 local.ret.cflagsCc.push(e); 591 } 592 else if (AnalyzeCommand.clangxxCheck7(local, e)) { } 593 else if (AnalyzeCommand.clangxxCheck8(local, e)) { } 594 else if (e == "-c") {//编译 595 local.ret.isLink = false; 596 } 597 else if (AnalyzeCommand.clangxxCheck9(local, e)) { } 598 else { 599 Logger.err(cmd + "\nclang++未解析参数 " + e); 600 process.exit(); 601 } 602 } 603 Logger.info("---clang++----" + local.ret.workDir + "\n\t" + local.ret.isLink + "," + local.ret.target) 604 return local.ret; 605 } 606 607 static analyzeCompileCommand(cmd) { 608 //整理命令行 609 while (cmd.indexOf("\\\n") >= 0) {//去除\换行 610 cmd = cmd.replace("\\\n", ""); 611 } 612 while (cmd.indexOf("\t") >= 0) {//所有tab换成空格 613 cmd = cmd.replace("\t", " "); 614 } 615 while (cmd.endsWith("\n") || cmd.endsWith(" ")) { 616 cmd = cmd.substring(0, cmd.length - 1); 617 } 618 let ret = null; 619 switch (AnalyzeCommand.getCompileCmdId(cmd)) { 620 case AnalyzeCommand.COMPILE_CMDS["clang"]: 621 ret = AnalyzeCommand.analyzeCcClang(cmd); 622 break; 623 case AnalyzeCommand.COMPILE_CMDS["ar"]: 624 ret = AnalyzeCommand.analyzeCcAr(cmd); 625 break; 626 case AnalyzeCommand.COMPILE_CMDS["clang++"]: 627 ret = AnalyzeCommand.analyzeCcClangxx(cmd); 628 break; 629 } 630 if (ret) { 631 AnalyzeCommand.mockTarget(ret);//解析出的目标,touch一个出来,否则会出现不同Makefile中依赖无法找到的问题 632 return ret; 633 } 634 Logger.err("解析编译命令行失败:" + cmd); 635 return false; 636 } 637} 638module.exports = { 639 AnalyzeCommand 640}