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