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