1#
2# File      : gcc.py
3# This file is part of RT-Thread RTOS
4# COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
5#
6#  This program is free software; you can redistribute it and/or modify
7#  it under the terms of the GNU General Public License as published by
8#  the Free Software Foundation; either version 2 of the License, or
9#  (at your option) any later version.
10#
11#  This program is distributed in the hope that it will be useful,
12#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14#  GNU General Public License for more details.
15#
16#  You should have received a copy of the GNU General Public License along
17#  with this program; if not, write to the Free Software Foundation, Inc.,
18#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# Change Logs:
21# Date           Author       Notes
22# 2018-05-22     Bernard      The first version
23# 2023-11-03     idings       return file path in GetHeader
24
25import os
26import re
27import platform
28import subprocess
29
30def GetGCCRoot(rtconfig):
31    exec_path = rtconfig.EXEC_PATH
32    prefix = rtconfig.PREFIX
33
34    if prefix.endswith('-'):
35        prefix = prefix[:-1]
36
37    if exec_path == '/usr/bin':
38        root_path = os.path.join('/usr/lib', prefix)
39    else:
40        root_path = os.path.join(exec_path, '..', prefix)
41
42    return root_path
43
44# https://stackoverflow.com/questions/4980819/what-are-the-gcc-default-include-directories
45# https://stackoverflow.com/questions/53937211/how-can-i-parse-gcc-output-by-regex-to-get-default-include-paths
46def match_pattern(pattern, input, start = 0, stop = -1, flags = 0):
47    length = len(input)
48
49    if length == 0:
50        return None
51
52    end_it = max(0, length - 1)
53
54    if start >= end_it:
55        return None
56
57    if stop<0:
58        stop = length
59
60    if stop <= start:
61        return None
62
63    for it in range(max(0, start), min(stop, length)):
64        elem = input[it]
65        match = re.match(pattern, elem, flags)
66        if match:
67            return it
68
69def GetGccDefaultSearchDirs(rtconfig):
70    start_pattern = r' *#include <\.\.\.> search starts here: *'
71    end_pattern = r' *End of search list\. *'
72
73    gcc_cmd = os.path.join(rtconfig.EXEC_PATH, rtconfig.CC)
74    device_flags = rtconfig.DEVICE.split()
75    command = [gcc_cmd] + device_flags + ['-xc', '-E', '-v', os.devnull]
76
77    # if gcc_cmd can not access , return empty list
78    if not os.access(gcc_cmd, os.X_OK):
79        return []
80
81    if(platform.system() == 'Windows'):
82        child = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
83    else:
84        child = subprocess.Popen(' '.join(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
85
86    stdout = child.communicate()
87    stdout_string = (b''.join(stdout)).decode()
88    lines = stdout_string.splitlines()
89
90    start_it = match_pattern(start_pattern, lines)
91    if start_it == None:
92        return []
93
94    end_it = match_pattern(end_pattern, lines, start_it)
95    if end_it == None:
96        return []
97
98    # theres no paths between them
99    if (end_it - start_it) == 1:
100        return []
101
102    return lines[start_it + 1 : end_it]
103
104def GetHeader(rtconfig, filename):
105    include_dirs = GetGccDefaultSearchDirs(rtconfig)
106    for directory in include_dirs:
107        fn = os.path.join(directory, filename).strip()
108        if os.path.isfile(fn):
109            return fn
110
111    # fallback to use fixed method if can't autodetect
112    root = GetGCCRoot(rtconfig)
113    fn = os.path.join(root, 'include', filename)
114    if os.path.isfile(fn):
115        return fn
116
117    # Usually the cross compiling gcc toolchain has directory as:
118    #
119    # bin
120    # lib
121    # share
122    # arm-none-eabi
123    #    bin
124    #    include
125    #    lib
126    #    share
127    prefix = rtconfig.PREFIX
128    if prefix.endswith('-'):
129        prefix = prefix[:-1]
130
131    fn = os.path.join(root, prefix, 'include', filename)
132    if os.path.isfile(fn):
133        return fn
134
135    return None
136
137# GCC like means the toolchains which are compatible with GCC
138def GetGCCLikePLATFORM():
139    return ['gcc', 'armclang', 'llvm-arm']
140
141def GetPicoLibcVersion(rtconfig):
142    version = None
143    try:
144        rtconfig.PREFIX
145    except:
146        return version
147
148    # get version from picolibc.h
149    fn = GetHeader(rtconfig, 'picolibc.h')
150
151    if fn:
152        f = open(fn, 'r')
153        if f:
154            for line in f:
155                if line.find('__PICOLIBC_VERSION__') != -1 and line.find('"') != -1:
156                    version = re.search(r'\"([^"]+)\"', line).groups()[0]
157            f.close()
158
159    return version
160
161def GetNewLibVersion(rtconfig):
162    version = None
163
164    try:
165        rtconfig.PREFIX
166    except:
167        return version
168
169    # if find picolibc.h, use picolibc
170    fn = GetHeader(rtconfig, 'picolibc.h')
171    if fn:
172        return version
173
174    # get version from _newlib_version.h file
175    fn = GetHeader(rtconfig, '_newlib_version.h')
176
177    # get version from newlib.h
178    if not fn:
179        fn = GetHeader(rtconfig, 'newlib.h')
180
181    if fn:
182        f = open(fn, 'r')
183        for line in f:
184            if line.find('_NEWLIB_VERSION') != -1 and line.find('"') != -1:
185                version = re.search(r'\"([^"]+)\"', line).groups()[0]
186        f.close()
187
188    return version
189
190# FIXME: there is no musl version or musl macros can be found officially
191def GetMuslVersion(rtconfig):
192    version = None
193
194    try:
195        rtconfig.PREFIX
196    except:
197        return version
198
199    if 'musl' in rtconfig.PREFIX:
200        version = 'unknown'
201    return version
202
203def GCCResult(rtconfig, str):
204    result = ''
205
206    def checkAndGetResult(pattern, string):
207        if re.search(pattern, string):
208            return re.search(pattern, string).group(0)
209        return None
210
211    gcc_cmd = os.path.join(rtconfig.EXEC_PATH, rtconfig.CC)
212
213    # use temp file to get more information
214    f = open('__tmp.c', 'w')
215    if f:
216        f.write(str)
217        f.close()
218
219        # '-fdirectives-only',
220        if(platform.system() == 'Windows'):
221            child = subprocess.Popen([gcc_cmd, '-E', '-P', '__tmp.c'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
222        else:
223            child = subprocess.Popen(gcc_cmd + ' -E -P __tmp.c', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
224
225        stdout, stderr = child.communicate()
226
227        # print(stdout)
228        if stderr != '' and stderr != b'':
229            print(stderr)
230
231        have_fdset = 0
232        have_sigaction = 0
233        have_sigevent = 0
234        have_siginfo = 0
235        have_sigval = 0
236        version = None
237        stdc = '1989'
238        posix_thread = 0
239
240        for line in stdout.split(b'\n'):
241            line = line.decode()
242            if re.search('fd_set', line):
243                have_fdset = 1
244
245            # check for sigal
246            if re.search('struct[ \t]+sigaction', line):
247                have_sigaction = 1
248            if re.search('struct[ \t]+sigevent', line):
249                have_sigevent = 1
250            if re.search('siginfo_t', line):
251                have_siginfo = 1
252            if re.search('union[ \t]+sigval', line):
253                have_sigval = 1
254
255            if re.search(r'char\* version', line):
256                version = re.search(r'"([^"]+)"', line).groups()[0]
257
258            if re.findall(r'iso_c_visible = \d+', line):
259                stdc = re.findall(r'\d+', line)[0]
260
261            if re.findall('pthread_create', line):
262                posix_thread = 1
263
264        if have_fdset:
265            result += '#define HAVE_FDSET 1\n'
266
267        if have_sigaction:
268            result += '#define HAVE_SIGACTION 1\n'
269        if have_sigevent:
270            result += '#define HAVE_SIGEVENT 1\n'
271        if have_siginfo:
272            result += '#define HAVE_SIGINFO 1\n'
273        if have_sigval:
274            result += '#define HAVE_SIGVAL 1\n'
275
276        if version:
277            result += '#define GCC_VERSION_STR "%s"\n' % version
278
279        result += '#define STDC "%s"\n' % stdc
280
281        if posix_thread:
282            result += '#define LIBC_POSIX_THREADS 1\n'
283
284        os.remove('__tmp.c')
285    return result
286