wscript.py - Waf build script for in-class examples

This is invoked by wscript.

Imports

These are listed in the order prescribed by PEP 8.

Standard library

import sys
from pathlib import Path
import os.path
from os import makedirs
import zipfile

Local application imports

from waflib.Errors import WafError
from waflib import Utils
from waflib.TaskGen import after, feature

Local imports

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from book_binder_lib import get_sim_str

Project definition

See https://waf.io/book/#_waf_project_definition.

out = '_build/waf'

Options

Define additional command-line options (see the end of this section.

def options(opt):

Load in additional command-line options for the C compiler and the assembler – see xc16_gcc.py - Configure waf for the xc16 compiler and xc16_as.py - Waf support for the PIC24 assembler.

    opt.load('xc16_gcc xc16_as')

Configure

Configure the build

def configure(conf):

Tell the tools our “OS” is the PIC24 platform.

    conf.env.DEST_OS = 'PIC24'

Select a specific processor to compile for.

    conf.env.MCU = '33EP128GP502'
    conf.load('xc16_gcc xc16_as')

Define the processor family for the simulator. See the supported devices.

    conf.env.SIM_MCU = 'dspic33epsuper'

Locate the simulator.

    conf.find_program(['sim30'], var='SIM30')

Build

Define the source files which are a part of the PIC24 library collection.

PIC24SupportLibSources = [
    'lib/src/pic24_clockfreq.c',
    'lib/src/pic24_configbits.c',
    'lib/src/pic24_serial.c',
    'lib/src/pic24_uart.c',
    'lib/src/pic24_util.c',
    'lib/src/pic24_timer.c',

Replaced by mocks.

    #'lib/src/pic24_i2c.c',
    #'lib/src/pic24_adc.c',
    #'lib/src/pic24_spi.c',

Not needed by the code used.

    #'lib/src/pic24_stdio_uart.c',
    #'lib/src/dataXfer.c',
    #'lib/src/dataXferImpl.c',
    #'lib/src/pic24_ecan.c',
    #'lib/src/pic24_flash.c',
]

This function runs a simulation, verifying that the simulation results are correct.

Inputs: path_to_elf_binary

Outputs: sim_output_file

def sim_run(task):
    sim_ret = 0
    out = ''
    try:

        sim_ret = task.exec_command([task.env.SIM30[0]], timeout=1,
                                     input=get_sim_str(task.env.SIM_MCU, task.inputs[0].abspath(),
                                        task.outputs[0].abspath()), universal_newlines=True)
    except (WafError) as e:
        sim_ret = 1

Report the exception (such as a timeout) in addition to the simulator output.

        out = str(e) + '\n' + '*'*80 + '\n'
 

Check the output.

    out += task.outputs[0].read().rstrip()
    if not sim_ret and out.endswith('Correct.'):
        return 0
    else:

Display what output we saw if there’s a failure.

        print(out)
        return 1

This adds the path to the linker script in the link step. It was inspired by https://github.com/waf-project/waf/blob/master/playground/ldscript/wscript.

@after('apply_link')
@feature('cprogram', 'cshlib')
def process_ldscript(self):
    mcu = getattr(self.env, 'MCU', None)
    if not mcu or self.env.CC_NAME != 'xc16-gcc':
        return

    node = self.path.find_resource('lib/lkr/p{MCU}_bootldr.gld'.format(**self.env))
    if not node:
        raise Utils.WafError('could not find %r' % self.ldscript)
    self.link_task.env.append_value('LINKFLAGS', '-Wl,--script=' + node.abspath())
    self.link_task.dep_nodes.append(node)

Given an assembly or C source file, build it and run a simulation.

def build_src(

ctx: The build context.

  ctx,

src: A string defining the assembly or C source file to build.

  src,

A string defining the C testbench to use. If not specified, it defaults to src + ‘-test.c’.

  c_test_src=None):
 

Derive file names from src.

    base_name = os.path.splitext(src)[0]
    obj_name = base_name + '.o'

Assemble src.

    ctx.objects(
      source=[src],
      features='c asm',
      name=obj_name,
      includes=['lib/include', 'book'],
    )
 

Use that plus the C library and test code to build the program.

    if not c_test_src:
        c_test_src = base_name + '-test.c'
    p = ctx.program(
      source=[c_test_src],
      use=[obj_name, 'support_libraries'],
      target=base_name,
      includes=['lib/include', 'book'],

This must be defined, or the simulator will get stuck waiting for the PIC24’s clock to switch.

      defines=['SIM'],
      features='c cprogram asm',

Consistent output file names: make the names of object files stay constant instead of being a varying number, so Flask can find and link with a known file name. See https://waf.io/book/#_common_declaration_for_c_c_fortran_and_d_applications, under “Additional attributes” and https://waf.io/faq.html, under “Why does foo.cpp yield a file named foo.cpp.number.o?”

      idx=1,
    )
 

Run the simulation.

    ctx(rule=sim_run, source=(ctx.env.cprogram_PATTERN % p.target),
        target=[ctx.path.find_or_declare(p.target + '.sim_out')])
 

Create MPLAB X project files. See https://waf.io/apidocs/TaskGen.html#waflib.TaskGen.process_subst.

First, create the project directory. TODO: duplicated string.

    project_dir = os.path.join('_build/source', base_name + '.X')
    try:

Also create the nbproject diretory, since make_node expects the containing directory to exist.

        os.makedirs(os.path.join(ctx.path.abspath(), project_dir, 'nbproject'))
    except FileExistsError:
        pass

Copy and template replace the project files.

    for _ in ('Makefile', 'nbproject/configurations.xml', 'nbproject/project.xml'):
        ctx(features='subst',
            source='build/waf/mplab_X_project_template/{}.in'.format(_),
            target=ctx.path.make_node(os.path.join(project_dir, _)),
            SRC=os.path.basename(src),
            TEST_SRC=os.path.basename(c_test_src),
            PROJECT_NAME=os.path.basename(base_name)
            )

def build(ctx):

Compile the library source

    ctx.objects(
      source=PIC24SupportLibSources + ['book/test_utils.c'],
      target='support_libraries',
      includes=['lib/include', 'book'],
      defines=['SIM'],
      features='c asm',
      idx=1,
    )

Build all asm source.

    for node in ctx.path.ant_glob(['book/**/*.s', 'book/**/*.c'], excl=['**/*-test.c', 'book/rtos_state_machines/**/*', 'book/test_utils.c']):
        build_src(ctx, node.relpath())

Distribute

Create an archive of student source files produced by Sphinx.

def gdist(ctx):
    book_path = '_build/html/_static/book'
    makedirs(book_path, exist_ok=True)
    zip_name = os.path.join(ctx.path.abspath(), book_path + '/pic24-book.zip')
 

Add student source files to the ZIP file. This was copied and modified from waflib.Scripting.Dist.archive.

    with zipfile.ZipFile(zip_name, 'w',
                         compression=zipfile.ZIP_DEFLATED) as zip_:

Get a list of student source files.

        base_source_path = ctx.path.find_node('_build/source')
        generated_files = base_source_path.ant_glob('**')
 

Add them to the archive.

        for x in generated_files:
            archive_name = x.path_from(base_source_path)
            zip_.write(x.abspath(), archive_name, zipfile.ZIP_DEFLATED)
 

Repeat for PIC24 library files.

        base_project_path = ctx.path.find_node('.')
        generated_files = base_project_path.ant_glob('lib/**/*')
 

Add them to the archive.

        for x in generated_files:
            archive_name = x.path_from(base_project_path)
            zip_.write(x.abspath(), archive_name, zipfile.ZIP_DEFLATED)