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