• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3"""
4Copyright (C) 2022 Huawei Device Co., Ltd.
5SPDX-License-Identifier: GPL-2.0
6
7Unless required by applicable law or agreed to in writing, software
8distributed under the License is distributed on an "AS IS" BASIS,
9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10See the License for the specific language governing permissions and
11limitations under the License.
12"""
13
14import logging
15import os
16import re
17import select
18import sys
19import subprocess
20import shlex
21import time
22
23
24ignores = [
25    "include/trace/events/eas_sched.h: warning: format '%d' expects argument of type 'int', but argument 9 has type 'long unsigned int' [-Wformat=]",
26    "drivers/block/zram/zram_drv.c: warning: left shift count >= width of type",
27    "include/trace/events/eas_sched.h: note: in expansion of macro",
28    "include/trace/events/eas_sched.h: note: format string is defined here",
29    "include/trace/trace_events.h: note: in expansion of macro",
30    "mm/vmscan.c: warning: suggest parentheses around assignment used as truth value [-Wparentheses]",
31    "lib/bitfield_kunit.c: warning: the frame size of \d+ bytes is larger than \d+ bytes",
32    "drivers/mmc/host/sdhci-esdhc-imx.c: warning: 'sdhci_esdhc_imx_probe_nondt' defined but not used",
33]
34
35
36class Reporter:
37    def __init__(self, arch, path):
38        self.arch = arch
39        self.path = path
40
41
42    def normpath(self, filename):
43        if re.search("^[\.]+[^/]", filename):
44            filename = re.sub("^[\.]+", "", filename)
45        return os.path.normpath(filename)
46
47
48    def format_title(self, title):
49        title = re.sub("\u2018", "'", title)
50        title = re.sub("\u2019", "'", title)
51
52        return title.strip()
53
54
55    def report_build_warning(self, filename, regex, details):
56        report = {}
57
58        if len(details) == 0 or filename is None:
59            return report
60
61        if not os.path.exists(os.path.join(self.path, filename)):
62            return report
63
64        line = details[0]
65        try:
66            warning = re.search(regex, line).group(0)
67        except GetBuildWaringErr as e:
68            print('Except>>>', details)
69            return report
70
71        report = {
72            'title': self.format_title("%s: %s" % (filename, warning)),
73            'filename': filename,
74            'report': '\n'.join(details),
75            'nr': line.split(':')[1],
76        }
77
78        return report
79
80
81    def parse_build_warning(self, blocks, regex, title_regex):
82        issues = {}
83        reports = []
84        details = []
85        filename = None
86        unused = False
87
88        for line in blocks:
89            attrs = line.split(':')
90            if len(attrs) < 5 and filename is None:
91                continue
92            if line.startswith(' ') or len(attrs) < 2:
93                if unused is True:
94                    details.append(line)
95                continue
96            if not regex in line:
97                unused = False
98                continue
99            unused = True
100            newfile = self.normpath(attrs[0])
101            if newfile != filename:
102                if len(details) and filename:
103                    if filename in issues:
104                        issues[filename].extend(details)
105                    else:
106                        issues[filename] = details
107                filename = newfile
108                details = []
109
110            details.append(line)
111
112        if len(details) and filename:
113            if filename in issues:
114                issues[filename].extend(details)
115            else:
116                issues[filename] = details
117
118        for filename, details in issues.items():
119            report = self.report_build_warning(filename, title_regex, details)
120            if not report is None:
121                reports.append(report)
122
123        return reports
124
125
126    def parse(self, content):
127        blocks = content.split('\n')
128        reports = []
129
130        patterns = (
131            ('[-Wunused-but-set-variable]', 'warning: .* set but not used'),
132            ('[-Wunused-but-set-parameter]', 'warning: .* set but not used'),
133            ('[-Wunused-const-variable=]', 'warning: .* defined but not used'),
134            ('[-Wold-style-definition]', 'warning: .* definition'),
135            ('[-Wold-style-declaration]', 'warning: .* declaration'),
136            ('[-Wmaybe-uninitialized]', 'warning: .* uninitialized'),
137            ('[-Wtype-limits]', 'warning: .* always (false|true)'),
138            ('[-Wunused-function]', 'warning: .* defined but not used'),
139            ('[-Wsequence-point]', 'warning: .* may be undefined'),
140            ('[-Wformat=]', 'warning: format.*'),
141            ('[-Wunused-variable]', 'warning: [^\[]*'),
142            ('[-Wframe-larger-than=]', 'warning: the frame size [^\[]*'),
143            ('[-Wshift-count-overflow]', 'warning: left shift count >= width of type'),
144            ('definition or declaration', 'warning: .* declared inside parameter list will not be visible outside of this definition or declaration'),
145            ('character', 'warning: missing terminating .* character'),
146            ('in expansion of macro', 'note: in expansion of macro'),
147            ('note: format string is defined here', 'note: format string is defined here'),
148            ('[-Wparentheses]', 'suggest parentheses around assignment used as truth value'),
149        )
150
151        for regex, title_regex in patterns:
152            items = self.parse_build_warning(blocks, regex, title_regex)
153            if items is None:
154                continue
155
156            reports.extend(items)
157
158        return reports
159
160
161def exec_cmd(command_list, shell=False, show_output=False, cwd=None):
162    if isinstance(command_list, str):
163        command_list = shlex.split(command_list)
164    elif not isinstance(command_list, list):
165        raise f"command_list to exec_cmd need to be a list or string"
166    command_list = ['nice'] + [str(s) for s in command_list]
167
168    print(f"cwd: '{cwd}'")
169    print(f"cmd: '{command_list}'")
170    start = time.time()
171    proc = subprocess.Popen(
172        command_list if not shell else ' '.join(command_list),
173        cwd=cwd,
174        shell=shell,
175        stdout=subprocess.PIPE,
176        stderr=subprocess.PIPE,
177        bufsize=1,
178        universal_newlines=True,
179    )
180
181    outmsg = ""
182    errmsg = ""
183    poller = select.epoll()
184    poller.register(proc.stdout, select.EPOLLIN)
185    poller.register(proc.stderr, select.EPOLLIN)
186    while proc.poll() is None:
187        for fd, event in poller.poll():
188            if event is not select.EPOLLIN:
189                continue
190            if fd == proc.stdout.fileno():
191                line = proc.stdout.readline()
192                if show_output is True:
193                    print(">> [stdout] %s", line.strip('\n'))
194                outmsg += line
195            elif fd == proc.stderr.fileno():
196                line = proc.stderr.readline()
197                if show_output is True:
198                    print(">> [stderr] %s", line.strip('\n'))
199                errmsg += line
200
201    for line in proc.stdout.readlines():
202        if show_output is True:
203            print(">> [stdout] %s", line.strip('\n'))
204        outmsg += line
205    for line in proc.stderr.readlines():
206        if show_output is True:
207            print(">> [stderr] %s", line.strip('\n'))
208        errmsg += line
209
210    ret = proc.wait()
211    print(f"Returned {ret} in {int(time.time() - start)} seconds")
212
213    return outmsg, errmsg, ret
214
215
216def make_cmd(cmd, arch, cross_compile, knl_path):
217    make = f"{cmd} ARCH={arch} CROSS_COMPILE={cross_compile}"
218    outmsg, errmsg, ret = exec_cmd(make, cwd=knl_path)
219    if ret:
220        print(f'"{make}" errors --> \n {errmsg}')
221        return ret, f'"{make}" errors --> \n {errmsg}'
222
223    return ret, f'"{make}" success!'
224
225
226def make_config(arch, config, corss_compile, knl_path):
227    make = f"make {config} ARCH={arch} CROSS_COMPILE={corss_compile}"
228    outmsg, errmsg, ret = exec_cmd(make, cwd=knl_path)
229    if ret:
230        print(f'"{make}" errors --> \n {errmsg}')
231        return ret, f'"{make}" errors --> \n {errmsg}'
232
233    return ret, f'"{make}" success!'
234
235
236def make_j(arch, cross_compile, knl_path):
237    make = f'make -j{os.cpu_count()} ARCH={arch} CROSS_COMPILE={cross_compile}'
238    outmsg, errmsg, ret = exec_cmd(make, cwd=knl_path)
239    if ret:
240        print(f'"{make}" errors --> \n {errmsg}')
241        return ret, f'"{make}" errors --> \n {errmsg}'
242    elif len(errmsg) > 0:
243        print(f'"{make}" warnings --> \n {errmsg}')
244        result = "success"
245        reporter = Reporter(arch, knl_path)
246        known_issue = "\nKnown issue:\n"
247        for report in reporter.parse(errmsg):
248            if ignores and [i for i in ignores if re.match(i, report['title'])]:
249                known_issue = known_issue + report['title'] + "\n"
250                known_issue = known_issue + report['report'] + "\n"
251                continue
252            result = 'failed'
253
254        print(known_issue)
255        new_issue = "\nNew Issue:\n"
256        if result == "failed":
257            for report in reporter.parse(errmsg):
258                if ignores and [i for i in ignores if re.match(i, report['title'])]:
259                    continue
260                new_issue = new_issue + report['title'] + "\n"
261                new_issue = new_issue + report['report'] + "\n"
262            print(new_issue)
263            return 2, f'"{make}" warning --> \n {new_issue}'
264
265        return ret, f'"{make}" warnings in ignores --> \n {known_issue}'
266
267    return ret, f'"{make}" success!'
268
269
270def cp_config(arch, config, config_path, knl_path):
271    if os.path.exists(config_path.format(arch, config)):
272        cp = f'cp ' + config_path.format(arch, config) + ' ' + os.path.join(knl_path, 'arch', arch, 'configs', config)
273        outmsg, errmsg, ret = exec_cmd(cp)
274        if ret:
275            print(f'"{cp}" errors --> \n {errmsg}')
276            return ret, f'"{cp}" errors --> \n {errmsg}'
277    else:
278        print(f'"{config_path.format(arch, config)}" not exists!')
279        return ret, f'"{config_path.format(arch, config)}" not exists!'
280
281    return ret, f'"{cp}" success!'
282
283
284def get_logger(filename):
285    log_format = '%(asctime)s %(name)s %(levelname)s %(message)s'
286    log_date_format = '%Y-%m-%d %H:%M:%S'
287    logging.basicConfig(
288        level=logging.INFO,
289        filename=filename,
290        format=log_format,
291        datefmt=log_date_format
292    )
293    logger = logging.getLogger(__name__)
294    return logger
295
296
297def build(arch, config, config_path, cross_compile, knl_path, logger):
298    ret, msg = make_cmd('make defconfig', arch, cross_compile, knl_path)
299    if ret:
300        logger.error(msg)
301        return ret, msg
302
303    ret, msg = make_cmd('make oldconfig', arch, cross_compile, knl_path)
304    if ret:
305        logger.error(msg)
306        return ret, msg
307
308    ret, msg = make_cmd('make clean', arch, cross_compile, knl_path)
309    if ret:
310        logger.error(msg)
311        return ret, msg
312
313    ret, msg = make_j(arch, cross_compile, knl_path)
314    if ret:
315        logger.error(msg)
316        return ret, msg
317
318    ret, msg = cp_config(arch, config, config_path, knl_path)
319    if ret:
320        logger.error(msg)
321        return ret, msg
322    else:
323        ret, msg = make_config(arch, config, cross_compile, knl_path)
324        if ret:
325            logger.error(msg)
326            return ret, msg
327
328        ret, msg = make_cmd('make clean', arch, cross_compile, knl_path)
329        if ret:
330            logger.error(msg)
331            return ret, msg
332
333        ret, msg = make_j(arch, cross_compile, knl_path)
334        if ret:
335            logger.error(msg)
336            return ret, msg
337
338    ret, msg = make_cmd('make allmodconfig', arch, cross_compile, knl_path)
339    if ret:
340        logger.error(msg)
341        return ret, msg
342
343    sed = f'sed -i s/^.*CONFIG_FRAME_WARN.*$/CONFIG_FRAME_WARN=2048/ .config'
344    outmsg, errmsg, ret = exec_cmd(sed, cwd=knl_path)
345
346    ret, msg = make_cmd('make clean', arch, cross_compile, knl_path)
347    if ret:
348        logger.error(msg)
349        return ret, msg
350
351    ret, msg = make_j(arch, cross_compile, knl_path)
352    if ret:
353        logger.error(msg)
354        return ret, msg
355
356    return 0, f'build success!'
357
358
359def main():
360    config_path = './kernel/linux/config/linux-5.10/arch/{0}/configs/{1}'
361    knl_path = './kernel/linux/linux-5.10'
362    log_path = os.getcwd()
363    now_date = time.strftime("%Y%m%d%H%M%S", time.localtime())
364    log_file = os.path.join(log_path, 'kernel_build_test.log')
365    logger = get_logger(log_file)
366
367    arch = 'arm'
368    config = 'hispark_taurus_standard_defconfig'
369    cross_compile = '../../../prebuilts/gcc/linux-x86/arm/gcc-linaro-7.5.0-arm-linux-gnueabi/bin/arm-linux-gnueabi-'
370    arm_ret, arm_msg = build(arch, config, config_path, cross_compile, knl_path, logger)
371
372    arch = 'arm64'
373    config = 'rk3568_standard_defconfig'
374    cross_compile = '../../../prebuilts/gcc/linux-x86/aarch64/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-'
375    arm64_ret, arm64_msg = build(arch, config, config_path, cross_compile, knl_path, logger)
376
377    print(f'arm_ret: {arm_ret}, arm64_ret: {arm64_ret}')
378    if any([arm_ret, arm64_ret]):
379        print('kernel build test failed!')
380        exit(arm_ret or arm64_ret)
381
382    print('kernel build test success.')
383    exit(0)
384
385
386if __name__ == "__main__":
387    main()
388