| #!/usr/bin/env python3 |
| # Copyright 2021 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| """Generates flags needed for an ARM build using clang. |
| |
| Using clang on Cortex-M cores isn't intuitive as the end-to-end experience isn't |
| quite completely in LLVM. LLVM doesn't yet provide compatible C runtime |
| libraries or C/C++ standard libraries. To work around this, this script pulls |
| the missing bits from an arm-none-eabi-gcc compiler on the system path. This |
| lets clang do the heavy lifting while only relying on some headers provided by |
| newlib/arm-none-eabi-gcc in addition to a small assortment of needed libraries. |
| |
| To use this script, specify what flags you want from the script, and run with |
| the required architecture flags like you would with gcc: |
| |
| python -m pw_toolchain.clang_arm_toolchain --cflags -- -mthumb -mcpu=cortex-m3 |
| |
| The script will then print out the additional flags you need to pass to clang to |
| get a working build. |
| """ |
| |
| import argparse |
| import sys |
| import os |
| import subprocess |
| |
| from pathlib import Path |
| from typing import List, Dict, Tuple |
| |
| _ARM_COMPILER_PREFIX = 'arm-none-eabi' |
| _ARM_COMPILER_NAME = _ARM_COMPILER_PREFIX + '-gcc' |
| |
| |
| def _parse_args() -> argparse.Namespace: |
| """Parses arguments for this script, splitting out the command to run.""" |
| |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument( |
| '--gn-scope', |
| action='store_true', |
| help=("Formats the output like a GN scope so it can be ingested by " |
| "exec_script()")) |
| parser.add_argument('--cflags', |
| action='store_true', |
| help=('Include necessary C flags in the output')) |
| parser.add_argument('--ldflags', |
| action='store_true', |
| help=('Include necessary linker flags in the output')) |
| parser.add_argument( |
| 'clang_flags', |
| nargs=argparse.REMAINDER, |
| help='Flags to pass to clang, which can affect library/include paths', |
| ) |
| parsed_args = parser.parse_args() |
| |
| assert parsed_args.clang_flags[0] == '--', 'arguments not correctly split' |
| parsed_args.clang_flags = parsed_args.clang_flags[1:] |
| return parsed_args |
| |
| |
| def _compiler_info_command(print_command: str, cflags: List[str]) -> str: |
| command = [_ARM_COMPILER_NAME] |
| command.extend(cflags) |
| command.append(print_command) |
| result = subprocess.run( |
| command, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, |
| ) |
| result.check_returncode() |
| return result.stdout.decode().rstrip() |
| |
| |
| def get_gcc_lib_dir(cflags: List[str]) -> Path: |
| return Path(_compiler_info_command('-print-libgcc-file-name', |
| cflags)).parent |
| |
| |
| def get_compiler_info(cflags: List[str]) -> Dict[str, str]: |
| compiler_info: Dict[str, str] = {} |
| compiler_info['gcc_libs_dir'] = os.path.relpath( |
| str(get_gcc_lib_dir(cflags)), ".") |
| compiler_info['sysroot'] = os.path.relpath( |
| _compiler_info_command('-print-sysroot', cflags), ".") |
| compiler_info['version'] = _compiler_info_command('-dumpversion', cflags) |
| compiler_info['multi_dir'] = _compiler_info_command( |
| '-print-multi-directory', cflags) |
| return compiler_info |
| |
| |
| def get_cflags(compiler_info: Dict[str, str]): |
| # TODO(amontanez): Make newlib-nano optional. |
| cflags = [ |
| # TODO(amontanez): For some reason, -stdlib++-isystem and |
| # -isystem-after work, but emit unused argument errors. This is the only |
| # way to let the build succeed. |
| '-Qunused-arguments', |
| # Disable all default libraries. |
| "-nodefaultlibs", |
| '--target=arm-none-eabi' |
| ] |
| |
| # Add sysroot info. |
| cflags.extend(( |
| '--sysroot=' + compiler_info['sysroot'], |
| '-isystem' + |
| str(Path(compiler_info['sysroot']) / 'include' / 'newlib-nano'), |
| # This must be included after Clang's builtin headers. |
| '-isystem-after' + str(Path(compiler_info['sysroot']) / 'include'), |
| '-stdlib++-isystem' + str( |
| Path(compiler_info['sysroot']) / 'include' / 'c++' / |
| compiler_info['version']), |
| '-isystem' + str( |
| Path(compiler_info['sysroot']) / 'include' / 'c++' / |
| compiler_info['version'] / _ARM_COMPILER_PREFIX / |
| compiler_info['multi_dir']), |
| )) |
| |
| return cflags |
| |
| |
| def get_crt_objs(compiler_info: Dict[str, str]) -> Tuple[str, ...]: |
| return ( |
| str(Path(compiler_info['gcc_libs_dir']) / 'crtfastmath.o'), |
| str(Path(compiler_info['gcc_libs_dir']) / 'crti.o'), |
| str(Path(compiler_info['gcc_libs_dir']) / 'crtn.o'), |
| str( |
| Path(compiler_info['sysroot']) / 'lib' / |
| compiler_info['multi_dir'] / 'crt0.o'), |
| ) |
| |
| |
| def get_ldflags(compiler_info: Dict[str, str]) -> List[str]: |
| ldflags: List[str] = [ |
| '-lnosys', |
| # Add library search paths. |
| '-L' + compiler_info['gcc_libs_dir'], |
| '-L' + str( |
| Path(compiler_info['sysroot']) / 'lib' / |
| compiler_info['multi_dir']), |
| # Add libraries to link. |
| '-lc_nano', |
| '-lm', |
| '-lgcc', |
| '-lstdc++_nano', |
| ] |
| |
| # Add C runtime object files. |
| objs = get_crt_objs(compiler_info) |
| ldflags.extend(objs) |
| |
| return ldflags |
| |
| |
| def main( |
| cflags: bool, |
| ldflags: bool, |
| gn_scope: bool, |
| clang_flags: List[str], |
| ) -> int: |
| """Script entry point.""" |
| compiler_info = get_compiler_info(clang_flags) |
| if ldflags: |
| ldflag_list = get_ldflags(compiler_info) |
| |
| if cflags: |
| cflag_list = get_cflags(compiler_info) |
| |
| if not gn_scope: |
| flags = [] |
| if cflags: |
| flags.extend(cflag_list) |
| if ldflags: |
| flags.extend(ldflag_list) |
| print(' '.join(flags)) |
| return 0 |
| |
| if cflags: |
| print('cflags = [') |
| for flag in cflag_list: |
| print(f' "{flag}",') |
| print(']') |
| |
| if ldflags: |
| print('ldflags = [') |
| for flag in ldflag_list: |
| print(f' "{flag}",') |
| print(']') |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(**vars(_parse_args()))) |