# Copyright 2024 Google LLC # # 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 # # http://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. import os import re import waflib from waflib import Utils from waflib.Configure import conf def find_clang_path(conf): """ Find the first clang on our path with a version greater than 3.2""" out = conf.cmd_and_log('which -a clang') paths = out.splitlines() for path in paths: # Make sure clang is at least version 3.3 out = conf.cmd_and_log('%s --version' % path) r = re.findall(r'clang version (\d+)\.(\d+)', out) if len(r): version_major = int(r[0][0]) version_minor = int(r[0][1]) if version_major > 3 or (version_major == 3 and version_minor >= 3): return path conf.fatal('No version of clang 3.3+ found on your path!') def find_toolchain_path(conf): possible_paths = ['~/arm-cs-tools/arm-none-eabi', '/usr/local/Cellar/arm-none-eabi-gcc/arm/arm-none-eabi'] for p in possible_paths: if os.path.isdir(p): return p conf.fatal('could not find arm-none-eabi folder') def find_sysroot_path(conf): """ The sysroot is a directory struct that looks like /usr/bin/ that includes custom headers and libraries for a target. We want to use the headers from our mentor toolchain, but they don't have a structure like /usr/bin. Therefore, if this is first time configuring on a system, create the directory structure with the appropriate symlinks so things work out. Note that this is a bit of a hack. Ideally our toolchain setup script will produce the directory structure that we expect, but I'm not going to muck with the toolchain too much until I get everything working end to end as opposed to having to rebuild our toolchain all the time. """ toolchain_path = find_toolchain_path(conf) sysroot_path = os.path.join(toolchain_path, 'sysroot') if not os.path.isdir(sysroot_path): waflib.Logs.pprint('CYAN', 'Sysroot dir not found at %s, creating...', sysroot_path) os.makedirs(os.path.join(sysroot_path, 'usr/local/')) os.symlink(os.path.join(toolchain_path, 'include/'), os.path.join(sysroot_path, 'usr/local/include')) return sysroot_path @conf def using_clang_compiler(ctx): compiler_name = ctx.env.CC if isinstance(ctx.env.CC, list): compiler_name = ctx.env.CC[0] if 'CCC_CC' in os.environ: compiler_name = os.environ['CCC_CC'] return 'clang' in compiler_name def options(opt): opt.add_option('--relax_toolchain_restrictions', action='store_true', help='Allow us to compile with a non-standard toolchain') opt.add_option('--use_clang', action='store_true', help='(EXPERIMENTAL) Uses clang instead of gcc as our compiler') opt.add_option('--use_env_cc', action='store_true', help='Use whatever CC is in the environment as our compiler') opt.add_option('--beta', action='store_true', help='Build in beta mode ' '(--beta and --release are mutually exclusive)') opt.add_option('--release', action='store_true', help='Build in release mode' ' (--beta and --release are mutually exclusive)') opt.add_option('--fat_firmware', action='store_true', help='build in GDB mode WITH logs; requires 1M of onbaord flash') opt.add_option('--gdb', action='store_true', help='build in GDB mode (no optimization, no logs)') opt.add_option('--lto', action='store_true', help='Enable link-time optimization') opt.add_option('--no-lto', action='store_true', help='Disable link-time optimization') opt.add_option('--save_temps', action='store_true', help='Save *.i and *.s files during compilation') opt.add_option('--no_debug', action='store_true', help='Remove -g debug information. See --save_temps') def configure(conf): CROSS_COMPILE_PREFIX = 'arm-none-eabi-' conf.env.AS = CROSS_COMPILE_PREFIX + 'gcc' conf.env.AR = CROSS_COMPILE_PREFIX + 'gcc-ar' if conf.options.use_env_cc: pass # Don't touch conf.env.CC elif conf.options.use_clang: conf.env.CC = find_clang_path(conf) else: conf.env.CC = CROSS_COMPILE_PREFIX + 'gcc' conf.env.LINK_CC = conf.env.CC conf.load('gcc') conf.env.append_value('CFLAGS', [ '-std=c11', ]) c_warnings = [ '-Wall', '-Wextra', '-Werror', '-Wpointer-arith', '-Wno-unused-parameter', '-Wno-missing-field-initializers', '-Wno-error=unused-function', '-Wno-error=unused-variable', '-Wno-error=unused-parameter' ] if conf.using_clang_compiler(): sysroot_path = find_sysroot_path(conf) # Disable clang warnings from now... they don't quite match c_warnings = [] conf.env.append_value('CFLAGS', [ '-target', 'arm-none-eabi' ]) conf.env.append_value('CFLAGS', [ '--sysroot', sysroot_path ]) # Clang doesn't enable short-enums by default since # arm-none-eabi is an unsupported target conf.env.append_value('CFLAGS', '-fshort-enums') arm_toolchain_path = find_toolchain_path(conf) conf.env.append_value('CFLAGS', [ '-B' + arm_toolchain_path ]) conf.env.append_value('LINKFLAGS', [ '-target', 'arm-none-eabi' ]) conf.env.append_value('LINKFLAGS', [ '--sysroot', sysroot_path ]) else: # These warnings only exist in GCC c_warnings.append('-Wno-error=unused-but-set-variable') c_warnings.append('-Wno-packed-bitfield-compat') if not ('4', '8') <= conf.env.CC_VERSION <= ('4', '9', '3'): # Verify the toolchain we're using is allowed. This is to prevent us from accidentally # building and releasing firmwares that are built in ways we haven't tested. if not conf.options.relax_toolchain_restrictions: TOOLCHAIN_ERROR_MSG = \ """=== INVALID TOOLCHAIN === Either upgrade your toolchain using the process listed here: https://pebbletechnology.atlassian.net/wiki/display/DEV/Firmware+Toolchain Or re-configure with the --relax_toolchain_restrictions option. """ conf.fatal('Invalid toolchain detected!\n' + \ repr(conf.env.CC_VERSION) + '\n' + \ TOOLCHAIN_ERROR_MSG) conf.env.CFLAGS.append('-I' + conf.path.abspath() + '/src/fw/util/time') conf.env.append_value('CFLAGS', c_warnings) conf.add_platform_defines(conf.env) conf.env.ASFLAGS = [ '-xassembler-with-cpp', '-c' ] conf.env.AS_TGT_F = '-o' conf.env.append_value('LINKFLAGS', [ '-Wl,--warn-common' ]) args = [ '-fvar-tracking-assignments', # Track variable locations better '-mthumb', '-ffreestanding', '-ffunction-sections', '-fbuiltin', '-fno-builtin-itoa' ] if not conf.options.no_debug: args += [ '-g3', # Extra debugging info, including macro definitions '-gdwarf-4' ] # More detailed debug info if conf.options.save_temps: args += [ '-save-temps=obj' ] if conf.options.lto: args += [ '-flto' ] if not using_clang_compiler(conf): # None of these options are supported by clang args += [ '-flto-partition=balanced', '--param','lto-partitions=128', # Can be trimmed down later '-fuse-linker-plugin', '-fno-if-conversion', '-fno-caller-saves', '-fira-region=mixed', '-finline-functions', '-fconserve-stack', '--param','inline-unit-growth=1', '--param','max-inline-insns-auto=1', '--param','max-cse-path-length=1000', '--param','max-grow-copy-bb-insns=1', '-fno-hoist-adjacent-loads', '-fno-optimize-sibling-calls', '-fno-schedule-insns2' ] cpu_fpu = None if conf.env.MICRO_FAMILY == "STM32F2": args += [ '-mcpu=cortex-m3' ] elif conf.env.MICRO_FAMILY == "STM32F4": args += [ '-mcpu=cortex-m4'] cpu_fpu = "fpv4-sp-d16" elif conf.env.MICRO_FAMILY == "STM32F7": args += [ '-mcpu=cortex-m7'] cpu_fpu = "fpv5-d16" # QEMU does not have FPU if conf.env.QEMU: cpu_fpu = None if cpu_fpu: args += [ "-mfloat-abi=softfp", "-mfpu="+cpu_fpu ] else: # Not using float-abi=softfp means no FPU instructions. # It also defines __SOFTFP__=1 # Yes that define name is super misleading, but what can you do. pass conf.env.append_value('CFLAGS', args) conf.env.append_value('ASFLAGS', args) conf.env.append_value('LINKFLAGS', args) conf.env.SHLIB_MARKER = None conf.env.STLIB_MARKER = None # Set whether or not we show the "Your Pebble just reset..." alert if conf.options.release and conf.options.beta: raise RuntimeError("--beta and --release are mutually exclusive and cannot be used together") if not conf.options.release: conf.env.append_value('DEFINES', [ 'SHOW_PEBBLE_JUST_RESET_ALERT' ]) conf.env.append_value('DEFINES', [ 'SHOW_BAD_BT_STATE_ALERT' ]) if not conf.is_bigboard(): conf.env.append_value('DEFINES', [ 'SHOW_ACTIVITY_DEMO' ]) # Set optimization level if conf.options.beta: optimize_flags = '-Os' print "Beta mode" elif conf.options.release: optimize_flags = '-Os' print "Release mode" elif conf.options.fat_firmware: optimize_flags = '-O0' conf.env.IS_FAT_FIRMWARE = True print 'Building Fat Firmware (no optimizations, logging enabled)' elif conf.options.gdb: optimize_flags = '-Og' print "GDB mode" else: optimize_flags = '-Os' print 'Debug Mode' conf.env.append_value('CFLAGS', optimize_flags) conf.env.append_value('LINKFLAGS', optimize_flags)