1#!/usr/bin/env python3 2# Copyright 2021 The Pigweed Authors 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may not 5# use this file except in compliance with the License. You may obtain a copy of 6# the License at 7# 8# https://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations under 14# the License. 15"""Generates flags needed for an ARM build using clang. 16 17Using clang on Cortex-M cores isn't intuitive as the end-to-end experience isn't 18quite completely in LLVM. LLVM doesn't yet provide compatible C runtime 19libraries or C/C++ standard libraries. To work around this, this script pulls 20the missing bits from an arm-none-eabi-gcc compiler on the system path. This 21lets clang do the heavy lifting while only relying on some headers provided by 22newlib/arm-none-eabi-gcc in addition to a small assortment of needed libraries. 23 24To use this script, specify what flags you want from the script, and run with 25the required architecture flags like you would with gcc: 26 27 python -m pw_toolchain.clang_arm_toolchain --cflags -- -mthumb -mcpu=cortex-m3 28 29The script will then print out the additional flags you need to pass to clang to 30get a working build. 31""" 32 33import argparse 34import sys 35import subprocess 36 37from pathlib import Path 38from typing import List, Dict, Tuple 39 40_ARM_COMPILER_PREFIX = 'arm-none-eabi' 41_ARM_COMPILER_NAME = _ARM_COMPILER_PREFIX + '-gcc' 42 43 44def _parse_args() -> argparse.Namespace: 45 """Parses arguments for this script, splitting out the command to run.""" 46 47 parser = argparse.ArgumentParser(description=__doc__) 48 parser.add_argument( 49 '--gn-scope', 50 action='store_true', 51 help=("Formats the output like a GN scope so it can be ingested by " 52 "exec_script()")) 53 parser.add_argument('--cflags', 54 action='store_true', 55 help=('Include necessary C flags in the output')) 56 parser.add_argument('--ldflags', 57 action='store_true', 58 help=('Include necessary linker flags in the output')) 59 parser.add_argument( 60 'clang_flags', 61 nargs=argparse.REMAINDER, 62 help='Flags to pass to clang, which can affect library/include paths', 63 ) 64 parsed_args = parser.parse_args() 65 66 assert parsed_args.clang_flags[0] == '--', 'arguments not correctly split' 67 parsed_args.clang_flags = parsed_args.clang_flags[1:] 68 return parsed_args 69 70 71def _compiler_info_command(print_command: str, cflags: List[str]) -> str: 72 command = [_ARM_COMPILER_NAME] 73 command.extend(cflags) 74 command.append(print_command) 75 result = subprocess.run( 76 command, 77 stdout=subprocess.PIPE, 78 stderr=subprocess.STDOUT, 79 ) 80 result.check_returncode() 81 return result.stdout.decode().rstrip() 82 83 84def get_gcc_lib_dir(cflags: List[str]) -> Path: 85 return Path(_compiler_info_command('-print-libgcc-file-name', 86 cflags)).parent 87 88 89def get_compiler_info(cflags: List[str]) -> Dict[str, str]: 90 compiler_info: Dict[str, str] = {} 91 compiler_info['gcc_libs_dir'] = str(get_gcc_lib_dir(cflags)) 92 compiler_info['sysroot'] = _compiler_info_command('-print-sysroot', cflags) 93 compiler_info['version'] = _compiler_info_command('-dumpversion', cflags) 94 compiler_info['multi_dir'] = _compiler_info_command( 95 '-print-multi-directory', cflags) 96 return compiler_info 97 98 99def get_cflags(compiler_info: Dict[str, str]): 100 # TODO(amontanez): Make newlib-nano optional. 101 cflags = [ 102 # TODO(amontanez): For some reason, -stdlib++-isystem and 103 # -isystem-after work, but emit unused argument errors. This is the only 104 # way to let the build succeed. 105 '-Qunused-arguments', 106 # Disable all default libraries. 107 "-nodefaultlibs", 108 '--target=arm-none-eabi' 109 ] 110 111 # Add sysroot info. 112 cflags.extend(( 113 '--sysroot=' + compiler_info['sysroot'], 114 '-isystem' + 115 str(Path(compiler_info['sysroot']) / 'include' / 'newlib-nano'), 116 # This must be included after Clang's builtin headers. 117 '-isystem-after' + str(Path(compiler_info['sysroot']) / 'include'), 118 '-stdlib++-isystem' + str( 119 Path(compiler_info['sysroot']) / 'include' / 'c++' / 120 compiler_info['version']), 121 '-isystem' + str( 122 Path(compiler_info['sysroot']) / 'include' / 'c++' / 123 compiler_info['version'] / _ARM_COMPILER_PREFIX / 124 compiler_info['multi_dir']), 125 )) 126 127 return cflags 128 129 130def get_crt_objs(compiler_info: Dict[str, str]) -> Tuple[str, ...]: 131 return ( 132 str(Path(compiler_info['gcc_libs_dir']) / 'crtfastmath.o'), 133 str(Path(compiler_info['gcc_libs_dir']) / 'crti.o'), 134 str(Path(compiler_info['gcc_libs_dir']) / 'crtn.o'), 135 str( 136 Path(compiler_info['sysroot']) / 'lib' / 137 compiler_info['multi_dir'] / 'crt0.o'), 138 ) 139 140 141def get_ldflags(compiler_info: Dict[str, str]) -> List[str]: 142 ldflags: List[str] = [ 143 '-lnosys', 144 # Add library search paths. 145 '-L' + compiler_info['gcc_libs_dir'], 146 '-L' + str( 147 Path(compiler_info['sysroot']) / 'lib' / 148 compiler_info['multi_dir']), 149 # Add libraries to link. 150 '-lc_nano', 151 '-lm', 152 '-lgcc', 153 '-lstdc++_nano', 154 ] 155 156 # Add C runtime object files. 157 objs = get_crt_objs(compiler_info) 158 ldflags.extend(objs) 159 160 return ldflags 161 162 163def main( 164 cflags: bool, 165 ldflags: bool, 166 gn_scope: bool, 167 clang_flags: List[str], 168) -> int: 169 """Script entry point.""" 170 compiler_info = get_compiler_info(clang_flags) 171 if ldflags: 172 ldflag_list = get_ldflags(compiler_info) 173 174 if cflags: 175 cflag_list = get_cflags(compiler_info) 176 177 if not gn_scope: 178 flags = [] 179 if cflags: 180 flags.extend(cflag_list) 181 if ldflags: 182 flags.extend(ldflag_list) 183 print(' '.join(flags)) 184 return 0 185 186 if cflags: 187 print('cflags = [') 188 for flag in cflag_list: 189 print(f' "{flag}",') 190 print(']') 191 192 if ldflags: 193 print('ldflags = [') 194 for flag in ldflag_list: 195 print(f' "{flag}",') 196 print(']') 197 return 0 198 199 200if __name__ == '__main__': 201 sys.exit(main(**vars(_parse_args()))) 202