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 os 36import subprocess 37 38from pathlib import Path 39from typing import List, Dict, Tuple 40 41_ARM_COMPILER_PREFIX = 'arm-none-eabi' 42_ARM_COMPILER_NAME = _ARM_COMPILER_PREFIX + '-gcc' 43 44 45def _parse_args() -> argparse.Namespace: 46 """Parses arguments for this script, splitting out the command to run.""" 47 48 parser = argparse.ArgumentParser(description=__doc__) 49 parser.add_argument( 50 '--gn-scope', 51 action='store_true', 52 help=("Formats the output like a GN scope so it can be ingested by " 53 "exec_script()")) 54 parser.add_argument('--cflags', 55 action='store_true', 56 help=('Include necessary C flags in the output')) 57 parser.add_argument('--ldflags', 58 action='store_true', 59 help=('Include necessary linker flags in the output')) 60 parser.add_argument( 61 'clang_flags', 62 nargs=argparse.REMAINDER, 63 help='Flags to pass to clang, which can affect library/include paths', 64 ) 65 parsed_args = parser.parse_args() 66 67 assert parsed_args.clang_flags[0] == '--', 'arguments not correctly split' 68 parsed_args.clang_flags = parsed_args.clang_flags[1:] 69 return parsed_args 70 71 72def _compiler_info_command(print_command: str, cflags: List[str]) -> str: 73 command = [_ARM_COMPILER_NAME] 74 command.extend(cflags) 75 command.append(print_command) 76 result = subprocess.run( 77 command, 78 stdout=subprocess.PIPE, 79 stderr=subprocess.STDOUT, 80 ) 81 result.check_returncode() 82 return result.stdout.decode().rstrip() 83 84 85def get_gcc_lib_dir(cflags: List[str]) -> Path: 86 return Path(_compiler_info_command('-print-libgcc-file-name', 87 cflags)).parent 88 89 90def get_compiler_info(cflags: List[str]) -> Dict[str, str]: 91 compiler_info: Dict[str, str] = {} 92 compiler_info['gcc_libs_dir'] = os.path.relpath( 93 str(get_gcc_lib_dir(cflags)), ".") 94 compiler_info['sysroot'] = os.path.relpath( 95 _compiler_info_command('-print-sysroot', cflags), ".") 96 compiler_info['version'] = _compiler_info_command('-dumpversion', cflags) 97 compiler_info['multi_dir'] = _compiler_info_command( 98 '-print-multi-directory', cflags) 99 return compiler_info 100 101 102def get_cflags(compiler_info: Dict[str, str]): 103 # TODO(amontanez): Make newlib-nano optional. 104 cflags = [ 105 # TODO(amontanez): For some reason, -stdlib++-isystem and 106 # -isystem-after work, but emit unused argument errors. This is the only 107 # way to let the build succeed. 108 '-Qunused-arguments', 109 # Disable all default libraries. 110 "-nodefaultlibs", 111 '--target=arm-none-eabi' 112 ] 113 114 # Add sysroot info. 115 cflags.extend(( 116 '--sysroot=' + compiler_info['sysroot'], 117 '-isystem' + 118 str(Path(compiler_info['sysroot']) / 'include' / 'newlib-nano'), 119 # This must be included after Clang's builtin headers. 120 '-isystem-after' + str(Path(compiler_info['sysroot']) / 'include'), 121 '-stdlib++-isystem' + str( 122 Path(compiler_info['sysroot']) / 'include' / 'c++' / 123 compiler_info['version']), 124 '-isystem' + str( 125 Path(compiler_info['sysroot']) / 'include' / 'c++' / 126 compiler_info['version'] / _ARM_COMPILER_PREFIX / 127 compiler_info['multi_dir']), 128 )) 129 130 return cflags 131 132 133def get_crt_objs(compiler_info: Dict[str, str]) -> Tuple[str, ...]: 134 return ( 135 str(Path(compiler_info['gcc_libs_dir']) / 'crtfastmath.o'), 136 str(Path(compiler_info['gcc_libs_dir']) / 'crti.o'), 137 str(Path(compiler_info['gcc_libs_dir']) / 'crtn.o'), 138 str( 139 Path(compiler_info['sysroot']) / 'lib' / 140 compiler_info['multi_dir'] / 'crt0.o'), 141 ) 142 143 144def get_ldflags(compiler_info: Dict[str, str]) -> List[str]: 145 ldflags: List[str] = [ 146 '-lnosys', 147 # Add library search paths. 148 '-L' + compiler_info['gcc_libs_dir'], 149 '-L' + str( 150 Path(compiler_info['sysroot']) / 'lib' / 151 compiler_info['multi_dir']), 152 # Add libraries to link. 153 '-lc_nano', 154 '-lm', 155 '-lgcc', 156 '-lstdc++_nano', 157 ] 158 159 # Add C runtime object files. 160 objs = get_crt_objs(compiler_info) 161 ldflags.extend(objs) 162 163 return ldflags 164 165 166def main( 167 cflags: bool, 168 ldflags: bool, 169 gn_scope: bool, 170 clang_flags: List[str], 171) -> int: 172 """Script entry point.""" 173 compiler_info = get_compiler_info(clang_flags) 174 if ldflags: 175 ldflag_list = get_ldflags(compiler_info) 176 177 if cflags: 178 cflag_list = get_cflags(compiler_info) 179 180 if not gn_scope: 181 flags = [] 182 if cflags: 183 flags.extend(cflag_list) 184 if ldflags: 185 flags.extend(ldflag_list) 186 print(' '.join(flags)) 187 return 0 188 189 if cflags: 190 print('cflags = [') 191 for flag in cflag_list: 192 print(f' "{flag}",') 193 print(']') 194 195 if ldflags: 196 print('ldflags = [') 197 for flag in ldflag_list: 198 print(f' "{flag}",') 199 print(']') 200 return 0 201 202 203if __name__ == '__main__': 204 sys.exit(main(**vars(_parse_args()))) 205