from waflib import Build, Task
from waflib.Tools.ccroot import link_task
from waflib.TaskGen import before_method, feature
import waflib.Logs

import waftools.asm
import waftools.compress
import waftools.gitinfo
import waftools.ldscript
import waftools.objcopy
import waftools.generate_log_strings_json
import waftools.generate_timezone_data
import tools.mpu_calc

import collections
import json
import os

from waflib.Configure import conf


@feature("c")
@before_method('apply_link')
def use_group_link(self):
    """
    Use a link group to resolve dependencies
    """
    if 'cprogram' in self.features and getattr(self, 'link_group', False):
        self.features.insert(0, "group_cprogram")


class group_cprogram(link_task):
    run_str = '${LINK_CC} ${LINKFLAGS} ${CCLNK_SRC_F}${SRC} ${CCLNK_TGT_F}${TGT[0].abspath()} ${RPATH_ST:RPATH} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${FRAMEWORK_ST:FRAMEWORK} ${ARCH_ST:ARCH} -Wl,--start-group ${STLIB_MARKER} ${STLIBPATH_ST:STLIBPATH} ${STLIB_ST:STLIB} ${SHLIB_MARKER} ${LIBPATH_ST:LIBPATH} ${LIB_ST:LIB} -Wl,--end-group'
    ext_out=['.bin']
    vars=['LINKDEPS']
    inst_to='${BINDIR}'


@conf
def get_pbz_node(ctx, fw_type, board_type, version_string):
    return ctx.path.get_bld().make_node('{}_{}_{}.pbz'.format(fw_type, board_type, version_string))


@conf
def get_pbpack_node(ctx):
    return ctx.path.get_bld().make_node('system_resources.pbpack')


@conf
def get_tintin_fw_node(ctx, subdir=None):
    subpath = 'src/fw/tintin_fw.bin'
    if subdir:
        subpath = os.path.join(subdir, subpath)
    return ctx.path.get_bld().make_node(subpath)


@conf
def get_tintin_fw_node_prf(ctx):
    return ctx.get_tintin_fw_node('prf')


@conf
def get_tintin_boot_node(ctx):
    return ctx.path.get_bld().make_node('src/boot/tintin_boot.bin')


def options(opt):
    pass



def configure(conf):
    conf.load('binary_header')
    conf.recurse('vendor')
    conf.recurse('applib')
    conf.recurse('services')

    if conf.is_silk():
        conf.env.append_value('DEFINES', ['MFG_INFO_RECORDS_TEST_RESULTS'])

    if conf.options.sdkshell:
        conf.env.NORMAL_SHELL = 'sdk'
        conf.env.append_value('DEFINES', ['SHELL_SDK'])
    else:
        conf.env.NORMAL_SHELL = 'normal'


def _generate_memory_layout(bld):

    if bld.is_cutts() or bld.is_robert():
        ldscript_template = bld.path.find_node('stm32f7xx_flash_fw.ld.template')
    elif bld.is_silk():
        ldscript_template = bld.path.find_node('stm32f412_flash_fw.ld.template')
    elif bld.is_snowy_compatible():
        ldscript_template = bld.path.find_node('stm32f439_flash_fw.ld.template')
    elif bld.is_tintin():
        ldscript_template = bld.path.find_node('stm32f2xx_flash_fw.ld.template')

    # Determine bootloader size so we can later calculate FLASH_LENGTH_*
    if bld.env.MICRO_FAMILY == 'STM32F2':
        flash_size = '512K'
        bootloader_size = '16K'

    elif bld.env.MICRO_FAMILY == 'STM32F4':
        if bld.env.BOARD in ('snowy_evt', 'snowy_evt2', 'spalding_evt'):
            bootloader_size = '64K'
        else:
            bootloader_size = '16K'
        if bld.is_silk() and bld.variant == 'prf':
            # silk PRF is limited to 512k to save on SPI flash space
            flash_size = '512K'
        else:
            flash_size = '1024K'

    elif bld.env.MICRO_FAMILY == 'STM32F7':
        bootloader_size = '32K'
        flash_size = '2048K'

    if bld.env.QEMU:
        flash_size = '4M'

    if bld.env.FLASH_ITCM:
        flash_origin = '0x00200000'
    else:
        flash_origin = '0x08000000'

    # Determine FLASH_LENGTH_*
    fw_flash_length = '%(flash_size)s - %(bootloader_size)s' % locals()
    fw_flash_origin = '%(flash_origin)s + %(bootloader_size)s' % locals()

    if bld.env.MICRO_FAMILY == 'STM32F2' and bld.variant == '':
        # If we're building a tintin normal firmware make sure to plug in all the symbols that we
        # want to use from the bootloader.

        with open(bld.path.find_node('bootloader_symbols.json').abspath(), 'r') as f:
            bootloader_symbols_json = json.load(f)

        bootloader_symbols = bootloader_symbols_json['bootloader_symbols'].iteritems()
        bootloader_symbol_strings = ("%s = %s;" % (n, v) for n, v in bootloader_symbols)
        bootloader_symbol_definitions = "\n    ".join(bootloader_symbol_strings)
    else:
        bootloader_symbol_definitions = ""

    # Determine ram layout

    # Each tuple defines the amount of RAM we give to apps (stack + text + data
    # + bss + heap) and the amount of RAM reserved for the application runtime
    # (AppState) for each SDK platform, respectively.
    AppRamSize = collections.namedtuple('AppRamSize',
                                        'app_segment runtime_reserved')
    APP_RAM_SIZES = {
        'aplite': AppRamSize(25952, 6820),
        'basalt': AppRamSize(66 * 1024, 30 * 1024),
        'chalk': AppRamSize(66 * 1024, 30 * 1024),
        # FIXME: The runtime_reserved size could be reduced for diorite
        'diorite': AppRamSize(66 * 1024, 30 * 1024),
        'emery': AppRamSize(130 * 1024, 62 * 1024),
    }
    APP_UNSUPPORTED = AppRamSize(0, 0)

    # The process loader enforces eight-byte alignment on all segments, so
    # configuring a segment with a size that is not a multiple of eight will
    # result in segments being smaller than expected. The runtime_reserved
    # size is not checked as its value isn't currently used anywhere.
    for platform, sizes in APP_RAM_SIZES.iteritems():
        if sizes.app_segment % 8 != 0:
            bld.fatal("The app_segment size for APP_RAM_SIZES[%r] is not a "
                      "multiple of eight bytes. You're gonna have a bad "
                      "time." % platform)

    # In the FW, the app execution environment is based on the major FW version with which the SDK
    # is associated. Each model supports a different set of SDK platforms, and determines the SDK
    # platform of an app using these major FW version associations (and also by considering the
    # hardware capabilities watch model itself - i.e. chalk vs basalt). We want to define
    # APP_RAM_*X_SIZE macros for each app execution environment supported by the model so we
    # don't need to hard-code these in the FW itself.
    if bld.is_tintin():
        app_ram_size_2x = APP_RAM_SIZES['aplite']
        app_ram_size_3x = APP_RAM_SIZES['aplite']
        app_ram_size_4x = APP_UNSUPPORTED
        # We have a 128k continuous block of RAM.
        total_ram = (0x20000000, 128 * 1024)
    elif bld.is_snowy():
        app_ram_size_2x = APP_RAM_SIZES['aplite']
        app_ram_size_3x = APP_RAM_SIZES['basalt']
        app_ram_size_4x = APP_RAM_SIZES['basalt']
        # We have a 192k continuous block of RAM, plus a separate 64k of CCM which we don't care
        # about here.
        total_ram = (0x20000000, 192 * 1024)
    elif bld.is_spalding():
        app_ram_size_2x = APP_UNSUPPORTED
        app_ram_size_3x = APP_RAM_SIZES['chalk']
        app_ram_size_4x = APP_RAM_SIZES['chalk']
        # We have a 192k continuous block of RAM, plus a separate 64k of CCM which we don't care
        # about here.
        total_ram = (0x20000000, 192 * 1024)
    elif bld.is_silk():
        app_ram_size_2x = APP_RAM_SIZES['aplite']
        app_ram_size_3x = APP_RAM_SIZES['aplite']
        app_ram_size_4x = APP_RAM_SIZES['diorite']
        # We have a 256k continuous block of RAM.
        total_ram = (0x20000000, 256 * 1024)
    elif bld.is_cutts() or bld.is_robert():
        app_ram_size_2x = APP_RAM_SIZES['aplite']
        app_ram_size_3x = APP_RAM_SIZES['basalt']
        app_ram_size_4x = APP_RAM_SIZES['emery']
        # We block off the 128k of DTCM for variables marked as DTCM_BSS. We use the remaining 384k
        # of SRAM as a continuous block of RAM.
        total_ram = (0x20020000, 384 * 1024)
    else:
        bld.fatal("No set of supported SDK platforms defined for this board")

    # Allocate RAM from the end to the start. Do the app first, then the worker, then give whatever
    # is left to the kernel.
    ram_end = sum(total_ram)  # The end of RAM is the start address plus the size.
    all_app_ram_sizes = [app_ram_size_2x, app_ram_size_3x, app_ram_size_4x]
    app_ram_size = max(sum(x) for x in all_app_ram_sizes)
    system_app_segment_size = max(x.app_segment for x in all_app_ram_sizes)
    app_runtime_size = max(x.runtime_reserved for x in all_app_ram_sizes)
    if app_ram_size <= 0 or app_runtime_size <= 0:
        bld.fatal("App RAM is too small!")
    app_ram = (ram_end - app_ram_size, app_ram_size)
    worker_ram_size = 12 * 1024  # The worker always gets 12k of RAM.
    worker_ram = (ram_end - app_ram_size - worker_ram_size, worker_ram_size)
    kernel_ram_size = total_ram[1] - app_ram_size - worker_ram_size
    kernel_ram = (total_ram[0], kernel_ram_size)

    # As a basic sanity check, make sure we're giving the kernel at least 64k.
    if kernel_ram_size < 64 * 1024:
        bld.fatal("Kernel RAM is too small!")

    ldscript_result = ldscript_template.get_bld().change_ext('.ld', ext_in='.ld.template')

    bld(features='subst',
        source=ldscript_template,
        target=ldscript_result,
        KERNEL_RAM_ADDR="0x{:x}".format(kernel_ram[0]),
        KERNEL_RAM_SIZE=kernel_ram[1],
        APP_RAM_ADDR="0x{:x}".format(app_ram[0]),
        APP_RAM_SIZE=app_ram[1],
        WORKER_RAM_ADDR="0x{:x}".format(worker_ram[0]),
        WORKER_RAM_SIZE=worker_ram[1],
        FLASH_ORIGIN=flash_origin,
        FW_FLASH_ORIGIN=fw_flash_origin,
        FW_FLASH_LENGTH=fw_flash_length,
        FLASH_SIZE=flash_size,
        BOOTLOADER_SYMBOLS=bootloader_symbol_definitions)

    app_mpu_region = tools.mpu_calc.find_subregions_for_region(app_ram[0], app_ram[1])
    worker_mpu_region = tools.mpu_calc.find_subregions_for_region(worker_ram[0], worker_ram[1])

    bld(features='subst',
        source=bld.path.find_node('kernel/mpu_regions.template.h'),
        target=bld.path.get_bld().make_node('kernel/mpu_regions.auto.h'),
        APP_BASE_ADDRESS="0x{:x}".format(app_mpu_region.address),
        APP_SIZE="0x{:x}".format(app_mpu_region.size),
        APP_DISABLED_SUBREGIONS="0b{:08b}".format(app_mpu_region.disabled_subregion),
        WORKER_BASE_ADDRESS="0x{:x}".format(worker_mpu_region.address),
        WORKER_SIZE="0x{:x}".format(worker_mpu_region.size),
        WORKER_DISABLED_SUBREGIONS=" 0b{:08b}".format(worker_mpu_region.disabled_subregion))

    bld(features='subst',
        source=bld.path.find_node('process_management/sdk_memory_limits.template.h'),
        target=bld.path.get_bld().make_node('process_management/sdk_memory_limits.auto.h'),
        APP_RAM_2X_SIZE=str(app_ram_size_2x.app_segment),
        APP_RAM_3X_SIZE=str(app_ram_size_3x.app_segment),
        APP_RAM_4X_SIZE=str(app_ram_size_4x.app_segment),
        APP_RAM_SYSTEM_SIZE=str(system_app_segment_size))

    return ldscript_result


def _link_firmware(bld, sources):
    if bld.is_spalding():
        sources += ['board/displays/display_spalding.c']

    fw_linkflags = ['-Wl,--cref',
                    '-Wl,-Map=tintin_fw.map',
                    '-Wl,--gc-sections',
                    '-Wl,--build-id=sha1',
                    '-Wl,--sort-section=alignment',
                    '-nostdlib']

    fw_linkflags.extend(['-Wl,--wrap=malloc',
                         '-Wl,--undefined=__wrap_malloc',
                         '-Wl,--wrap=realloc',
                         '-Wl,--undefined=__wrap_realloc',
                         '-Wl,--wrap=calloc',
                         '-Wl,--undefined=__wrap_calloc',
                         '-Wl,--wrap=free',
                         '-Wl,--undefined=__wrap_free'])

    uses = ['applib',
            'board',
            'bt_driver',
            'drivers',
            'freertos',
            'fw_services',
            'gcc',
            'proto_schemas',
            'jerry_core',
            'jerry_libm',
            'libbtutil',
            'libos',
            'libutil',
            'nanopb',
            'pblibc',
            'root_includes',
            'startup',
            'tinymt32',
            'upng']

    ldscript = _generate_memory_layout(bld)

    if bld.env.NO_LINK:
        # Only build the object files
        bld.objects(source=sources,
                    use=uses,
                    includes='fonts')
    else:
        # ..and actually build and link the firmware ELF
        elf_node = bld.path.get_bld().make_node('tintin_fw.elf')
        bld.program(source=sources,
                    use=uses,
                    link_group=True,
                    lib=['gcc'],
                    target=elf_node,
                    includes='fonts',
                    ldscript=[ldscript, 'fw_common.ld'],
                    linkflags=fw_linkflags)

        if bld.env.FLASH_ITCM:
            # 0x07E00000 is the difference between the flash memory address and the flash ITCM
            # address. We need to add this because otherwise OpenOCD is unable to write the
            # image into the flash chip.
            extra_args = '--change-addresses 0x07E00000'
        else:
            extra_args = ''

        hex_node = elf_node.change_ext('.hex')
        bld(rule=waftools.objcopy.objcopy_hex, source=elf_node, target=hex_node, extra_args=extra_args)
        bin_node = elf_node.change_ext('.bin')
        bld(rule=waftools.objcopy.objcopy_bin, source=elf_node, target=bin_node)

        # Create the log_strings .elf and check the format specifier rules
        if 'PBL_LOGS_HASHED' in bld.env.DEFINES:
            fw_loghash_node = bld.path.get_bld().make_node('tintin_fw_loghash_dict.json')
            bld(rule=waftools.generate_log_strings_json.wafrule,
                source=elf_node, target=fw_loghash_node)
            bld.LOGHASH_DICTS.append(fw_loghash_node)


def _get_mfg_paths(bld):
    """Return a list of directories we want to build to support manufacturing on a platform"""

    if bld.is_tintin():
        return ('mfg/tintin',)
    elif bld.is_snowy():
        return ('mfg/snowy',)
    elif bld.is_spalding():
        return ('mfg/spalding',)
    elif bld.is_silk():
        return ('mfg/silk',)
    elif bld.is_cutts():
        # This probably should end up as 'mfg/cutts' if this comes back
        return ('mfg/robert',)
    elif bld.is_robert():
        return ('mfg/robert',)
    else:
        bld.fatal('No MFG configuration for board %s' % bld.env.BOARD)

def _get_dbg_paths(bld):
    """Return a list of directories we want to build to support debug logging on a platform"""

    if bld.is_tintin():
        return ('debug/legacy',)
    else:
        return ('debug/default',)

def _gen_fpga_bitstream_header(bld):
    if bld.is_snowy():
        if bld.env.BOARD in ('snowy_bb', 'snowy_evt'):
            source_bitstream = '../../platform/snowy/snowy_framebuffer_evt.fpga'
        else:
            source_bitstream = '../../platform/snowy/Snowy_LP1K_framebuffer.fpga'
    elif bld.is_spalding():
        if bld.env.BOARD in ('spalding_bb2',):
            source_bitstream = '../../platform/snowy/Spalding_LP1K_framebuffer.fpga'
        else:
            source_bitstream = '../../platform/snowy/Spalding_UL1K_framebuffer.fpga'
    elif bld.is_cutts():
        source_bitstream = '../../platform/robert/Cutts_UL1K_framebuffer_144x168_bitmap.fpga'
    elif bld.is_robert():
        source_bitstream = '../../platform/robert/Robert_UL1K_framebuffer_bitmap.fpga'
    else:
        bld.fatal('No FPGA available for {}'.format(bld.env.BOARD))

    bld(features='binary_header',
        source=source_bitstream,
        target='drivers/display/ice40lp/fpga_bitstream.auto.h',
        array_name='s_fpga_bitstream',
        compressed=True)

def _get_comm_sources(bld, is_recovery):
    excl = []
    if bld.env.QEMU:
        excl.append('comm/internals/profiles/ispp.c')

    if is_recovery:
        excl.append('comm/ble/kernel_le_client/ancs/*.c')
        excl.append('comm/ble/kernel_le_client/ams/*.c')
    else:
        excl.append('comm/prf_stubs/*')

    return bld.path.ant_glob('comm/**/*.c', excl=excl)


def _build_recovery(bld):
    source_dirs = ['apps/core_apps/**',
                   'apps/prf_apps/**',
                   'process_management',
                   'process_state/**',
                   'console',
                   'debug',
                   'flash_region',
                   'graphics',
                   'kernel/**',
                   'mfg',
                   'mfg/mfg_apps',
                   'mfg/mfg_mode',
                   'resource',
                   'syscall',
                   'system',
                   'shell',
                   'shell/prf/**',
                   'util/**']

    source_dirs.extend(_get_mfg_paths(bld))
    source_dirs.extend(_get_dbg_paths(bld))

    excludes = ['process_management/app_custom_icon.c',
                'process_management/app_menu_data_source.c',
                'resource/resource_storage_file.c']

    if 'MFG_INFO_RECORDS_TEST_RESULTS' not in bld.env.DEFINES:
        excludes.extend('mfg/results_ui.c')

    if bld.is_silk():
        bld.env.append_value('DEFINES', ['QSPI_DMA_DISABLE=1'])

    sources = sum([bld.path.ant_glob('%s/*.c' % d, excl=excludes) for d in source_dirs], [])
    sources.extend(_get_comm_sources(bld, True))
    sources.extend(bld.path.ant_glob('*.c'))
    sources.extend(bld.path.ant_glob('*.[sS]'))

    sources.append(bld.path.make_node('popups/bluetooth_pairing_ui.c'))

    sources.append(bld.path.get_bld().make_node('builtin_resources.auto.c'))

    if bld.is_snowy_compatible() or bld.is_cutts() or bld.is_robert():
        _gen_fpga_bitstream_header(bld)

    if bld.is_snowy():
        bld(features='binary_header',
            source='../../platform/snowy/snowy_boot.fpga',
            target='mfg/snowy/snowy_boot.fpga.auto.h',
            array_name='s_boot_fpga')
    elif bld.is_spalding():
        bld(features='binary_header',
            source='../../platform/snowy/spalding_boot.fpga',
            target='mfg/spalding/spalding_boot.fpga.auto.h',
            array_name='s_boot_fpga')

    if 'BOOTLOADER_TEST_STAGE1=1' in bld.env.DEFINES:
        bld(features='binary_header',
            source=bld.path.make_node('../../bootloader_test_stage2.bin'),
            target='bootloader_test_bin.auto.h',
            array_name='s_bootloader_test_stage2')


    if bld.env.DISABLE_PROMPT:
        sources = [ x for x in sources if not x.abspath().endswith('console/prompt.c') ]
        sources = [ x for x in sources if not x.abspath().endswith('console/prompt_commands.c') ]

    _link_firmware(bld, sources)


def _get_launcher_globs_to_exclude(bld):
    system_launchers_root_path = 'apps/system_apps/launcher/'
    legacy_launcher_glob = system_launchers_root_path + 'legacy/**'
    default_launcher_glob = system_launchers_root_path + 'default/**'
    launcher_globs_to_exclude = [legacy_launcher_glob, default_launcher_glob]

    if bld.is_tintin():
        launcher_glob_to_use = legacy_launcher_glob
    else:
        launcher_glob_to_use = default_launcher_glob

    launcher_globs_to_exclude.remove(launcher_glob_to_use)
    return launcher_globs_to_exclude


def _build_normal(bld):
    # Generate timezone data
    olson_txt = bld.srcnode.make_node('resources/normal/base/tzdata/timezones_olson.txt')
    tzdata_bin = bld.bldnode.make_node('resources/normal/base/tzdata/tzdata.bin.reso')
    bld(rule=waftools.generate_timezone_data.wafrule,
        source=olson_txt,
        target=tzdata_bin)

    bld.DYNAMIC_RESOURCES.append(tzdata_bin)

    source_dirs = ['process_management',
                   'process_state/**',
                   'bt_test',
                   'console',
                   'debug',
                   'flash_region',
                   'graphics',
                   'kernel/**',
                   'launcher/**',
                   'mfg',
                   'popups/**',
                   'resource',
                   'syscall',
                   'system',
                   'shell',
                   'shell/%s/**' % bld.env.NORMAL_SHELL,
                   'util/**']

    source_files = []
    excludes = []

    source_dirs.append('apps/core_apps/**')
    if bld.env.NORMAL_SHELL == 'sdk':
        source_dirs.append('apps/sdk/**')
        source_dirs.append('apps/system_apps/timeline')
        if bld.capability('HAS_SDK_SHELL4'):
            source_dirs.append('apps/system_apps/launcher/default/**')
        else:
            excludes.append('process_management/app_custom_icon.c')
            excludes.append('process_management/app_menu_data_source.c')
    else:
        source_dirs.extend(('apps/%s/**' % d for d in ('system_apps', 'watch')))
        if bld.is_spalding():
            excludes.append('apps/watch/tictoc/default')
        else:
            excludes.append('apps/watch/tictoc/spalding')

        if bld.is_cutts():
            excludes.append('apps/watch/kickstart')

    source_dirs.extend(_get_mfg_paths(bld))
    source_dirs.extend(_get_dbg_paths(bld))

    if bld.env.BUILD_TEST_APPS:
        source_dirs.append('apps/demo_apps/**')
        if bld.is_tintin() or bld.is_silk():
            excludes.append('apps/demo_apps/vibe_score_demo.c')
    elif bld.env.PERFORMANCE_TESTS:
        source_dirs.append('apps/demo_apps/gfx_tests')

    excludes.extend(_get_launcher_globs_to_exclude(bld))

    if not bld.capability('HAS_BUILTIN_HRM'):
        excludes.append('popups/ble_hrm/**')

    if bld.is_tintin():
        excludes.append('apps/system_apps/health/**')
        excludes.append('apps/system_apps/settings/settings_vibe_patterns.c')
        excludes.append('apps/system_apps/weather/**')
        excludes.append('apps/system_apps/workout/**')

    sources = sum([bld.path.ant_glob('%s/*.c' % d, excl=excludes) for d in source_dirs], [])
    sources.extend(bld.path.make_node(x) for x in source_files)
    sources.extend(_get_comm_sources(bld, False))
    sources.extend(bld.path.ant_glob('*.c'))
    sources.extend(bld.path.ant_glob('*.[sS]'))

    if bld.env.NORMAL_SHELL == 'sdk':
        sources.append('apps/system_apps/app_fetch_ui.c')
        if bld.capability('HAS_SDK_SHELL4'):
            sources.append('apps/system_apps/watchfaces.c')

    gettexts = []
    gettexts.extend(sources)
    gettexts.extend(bld.path.ant_glob('**/*.h'))
    gettexts.extend(bld.path.ant_glob('**/*.def'))

    bld.gettext(source=gettexts, target='fw.pot')
    bld.msgcat(
            source='fw.pot services/services.pot applib/applib.pot',
            target='tintin.pot')

    if bld.is_snowy_compatible() or bld.is_cutts() or bld.is_robert():
        _gen_fpga_bitstream_header(bld)

    if bld.is_snowy():
        bld(features='binary_header',
            source='../../platform/snowy/snowy_boot.fpga',
            target='mfg/snowy/snowy_boot.fpga.auto.h',
            array_name='s_boot_fpga')
    elif bld.is_spalding():
        bld(features='binary_header',
            source='../../platform/snowy/spalding_boot.fpga',
            target='mfg/spalding/spalding_boot.fpga.auto.h',
            array_name='s_boot_fpga')

    sources.append(bld.path.get_bld().make_node('pebble.auto.c'))
    sources.append(bld.path.get_bld().make_node('resource/pfs_resource_table.auto.c'))
    sources.append(bld.path.get_bld().make_node('resource/timeline_resource_table.auto.c'))
    sources.append(bld.path.get_bld().make_node('builtin_resources.auto.c'))

    if bld.env.DISABLE_PROMPT:
        sources = [ x for x in sources if not x.abspath().endswith('console/prompt.c') ]
        sources = [ x for x in sources if not x.abspath().endswith('console/prompt_commands.c') ]

    _link_firmware(bld, sources)

def build(bld):
    # FIXME create applib_includes or something like that
    bld(export_includes=['.',
                         'applib/vendor/uPNG',
                         'applib/vendor/tinflate'],
        use=['stm32_stdlib',
             'libbtutil_includes',
             'libos_includes',
             'libutil_includes',
             'root_includes',
             'idl_includes',
             'nanopb_includes'],
        name='fw_includes')

    if bld.variant in ('applib', 'test_rocky_emx'):
        bld.set_env(bld.all_envs['local'])
        bld.env.DEFINES.extend(['UNITTEST', 'SCREEN_COLOR_DEPTH_BITS=8',
                                'DISP_COLS=144', 'DISP_ROWS=168',
                                'DISPLAY_FRAMEBUFFER_BYTES=%d' % (144 * 168),
                                'PBL_COLOR', 'PBL_RECT'])
        bld.recurse('applib')
        return

    # Truncate the commit to fit in our versions struct. This may cause an ambiguous commit
    # hash, but it's better than killing the build because the commit doesn't fit.
    git_rev = waftools.gitinfo.get_git_revision(bld)
    git_rev['COMMIT'] = git_rev['COMMIT'][:7]
    git_rev['PATCH_VERBOSE_STRING']
    if len(git_rev['TAG']) > 31:
        waflib.Logs.warn('Git tag {} is too long, truncating'.format(git_rev['TAG']))
        git_rev['TAG'] = git_rev['TAG'][:31]

    bld(features='subst',
        source='git_version.auto.h.in',
        target='git_version.auto.h',
        **git_rev)

    bld.recurse('startup')
    bld.recurse('drivers')
    bld.recurse('board')
    bld.recurse('shell')
    bld.recurse('vendor')
    bld.recurse('services')
    bld.recurse('applib')

    # We can't use DMA until we root cause PBL-37278
    if bld.is_silk():
        bld.env.append_value('DEFINES', ['QSPI_DMA_DISABLE=1'])

    if bld.variant == 'prf':
        _build_recovery(bld)
    else:
        _build_normal(bld)

# vim:filetype=python
