• 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            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};