• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}