Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,26 @@
# Compiler settings for running the tests on a simulated atmega1284.
def set_avr_platform(env):
native = env.Clone()
native.Append(LIBS = ["simavr", "libelf"], CFLAGS = "-Wall -Werror -g")
runner = native.Program("build/run_test", "site_scons/platforms/avr/run_test.c")
env.Replace(EMBEDDED = "AVR")
env.Replace(CC = "avr-gcc",
CXX = "avr-g++")
env.Replace(TEST_RUNNER = "build/run_test")
env.Append(CFLAGS = "-mmcu=atmega1284 -Dmain=app_main -Os -g -Wall ")
env.Append(CXXFLAGS = "-mmcu=atmega1284 -Dmain=app_main -Os -Wno-type-limits")
env.Append(CPPDEFINES = {'PB_CONVERT_DOUBLE_FLOAT': 1, 'UNITTESTS_SHORT_MSGS': 1,
'__ASSERT_USE_STDERR': 1, 'MAX_ALLOC_BYTES': 32768,
'FUZZTEST_BUFSIZE': 2048})
env.Append(LINKFLAGS = "-mmcu=atmega1284")
env.Append(LINKFLAGS = "-Wl,-Map,build/avr.map")
# Build library for communicating with test runner
avr_io = env.Object("build/avr_io.o", "site_scons/platforms/avr/avr_io.c")
env.Append(LIBS = avr_io)
# This fake define just ensures that the test runner gets build also
env.Depends(avr_io, runner)

View file

@ -0,0 +1,91 @@
/* This wrapper file initializes stdio to UART connection and
* receives the list of command line arguments.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#undef main
extern int app_main(int argc, const char **argv);
struct {
uint8_t argc;
char args[3][16];
} g_args;
int uart_putchar(char c, FILE *stream)
{
loop_until_bit_is_set(UCSR0A, UDRE0);
UDR0 = c;
return 0;
}
int uart_getchar(FILE *stream)
{
while (bit_is_clear(UCSR0A, RXC0) && bit_is_clear(UCSR0A, FE0));
if (UCSR0A & _BV(FE0)) return _FDEV_EOF; /* Break = EOF */
return UDR0;
}
FILE uart_str = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
static char g_malloc_heap[8192];
extern uint32_t __bss_end;
void abort(void)
{
if (__bss_end != 0xDEADBEEF)
{
fprintf(stderr, "possible stack overflow\n");
}
fprintf(stderr, "abort() called\n");
DDRB = 3;
PORTB = 1;
cli();
while (1) sleep_mode();
}
int main(void)
{
const char *argv[4] = {"main", g_args.args[0], g_args.args[1], g_args.args[2]};
int status;
UBRR0 = (8000000 / (16UL * 9600)) - 1; /* 9600 bps with default 8 MHz clock */
UCSR0B = _BV(TXEN0) | _BV(RXEN0);
__malloc_heap_start = g_malloc_heap;
__malloc_heap_end = g_malloc_heap + sizeof(g_malloc_heap);
__bss_end = 0xDEADBEEF;
stdout = stdin = stderr = &uart_str;
fread((char*)&g_args, 1, sizeof(g_args), stdin);
status = app_main(g_args.argc + 1, argv);
if (__bss_end != 0xDEADBEEF)
{
status = 255;
fprintf(stderr, "possible stack overflow\n");
}
DDRB = 3;
if (status)
{
fprintf(stderr, "Error exit: %d\n", status);
PORTB = 1; // PB0 indicates error
}
else
{
PORTB = 2; // PB1 indicates success
}
cli();
sleep_mode();
return status;
}

View file

@ -0,0 +1,196 @@
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <libgen.h>
#include <simavr/sim_avr.h>
#include <simavr/sim_gdb.h>
#include <simavr/avr_ioport.h>
#include <simavr/sim_elf.h>
#include <simavr/avr_uart.h>
static avr_t *g_avr;
static avr_irq_t *g_uart_irq;
static struct {
uint8_t argc;
char args[3][16];
} g_args;
static int g_args_idx;
static bool g_uart_xon;
static bool g_status_ok;
static void uart_tx_hook(struct avr_irq_t * irq, uint32_t value, void * param)
{
fputc(value, stdout);
fflush(stdout);
}
static bool stdin_can_read()
{
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
return (FD_ISSET(0, &fds));
}
static void avr_logger(avr_t * avr, const int level, const char * format, va_list ap)
{
if ((!avr && level <= LOG_WARNING) || (avr && avr->log >= level)) {
vfprintf(stderr , format, ap);
}
}
static void uart_xon_hook(struct avr_irq_t * irq, uint32_t value, void * param)
{
g_uart_xon = true;
int v;
if (feof(stdin))
{
avr_raise_irq(&g_uart_irq[1], UART_INPUT_FE);
return;
}
while (g_uart_xon)
{
if (g_args_idx < sizeof(g_args))
{
v = ((char*)&g_args)[g_args_idx++];
}
else if (stdin_can_read())
{
v = fgetc(stdin);
}
else
{
break;
}
if (v != EOF)
{
avr_raise_irq(&g_uart_irq[1], v);
}
else
{
avr_raise_irq(&g_uart_irq[1], UART_INPUT_FE);
break;
}
}
}
static void uart_xoff_hook(struct avr_irq_t * irq, uint32_t value, void * param)
{
g_uart_xon = false;
}
void init_uart()
{
const char *irq_names[2] = {"8<uart_in", "8>uart_out"};
g_uart_irq = avr_alloc_irq(&g_avr->irq_pool, 0, 2, irq_names);
avr_irq_register_notify(&g_uart_irq[0], &uart_tx_hook, NULL);
uint32_t flags = 0;
avr_ioctl(g_avr, AVR_IOCTL_UART_GET_FLAGS('0'), &flags);
flags &= ~AVR_UART_FLAG_STDIO;
flags &= ~AVR_UART_FLAG_POLL_SLEEP;
avr_ioctl(g_avr, AVR_IOCTL_UART_SET_FLAGS('0'), &flags);
avr_irq_t *src = avr_io_getirq(g_avr, AVR_IOCTL_UART_GETIRQ('0'), UART_IRQ_OUTPUT);
avr_irq_t *dst = avr_io_getirq(g_avr, AVR_IOCTL_UART_GETIRQ('0'), UART_IRQ_INPUT);
avr_connect_irq(src, &g_uart_irq[0]);
avr_connect_irq(&g_uart_irq[1], dst);
avr_irq_t *xon = avr_io_getirq(g_avr, AVR_IOCTL_UART_GETIRQ('0'), UART_IRQ_OUT_XON);
avr_irq_t *xoff = avr_io_getirq(g_avr, AVR_IOCTL_UART_GETIRQ('0'), UART_IRQ_OUT_XOFF);
avr_irq_register_notify(xon, uart_xon_hook, NULL);
avr_irq_register_notify(xoff, uart_xoff_hook, NULL);
}
static void status_ok_hook(struct avr_irq_t * irq, uint32_t value, void * param)
{
g_status_ok = value;
}
int main(int argc, char *argv[])
{
avr_global_logger_set(&avr_logger);
g_avr = avr_make_mcu_by_name("atmega1284");
if (!g_avr)
{
fprintf(stderr, "avr_make_mcu_by_name failed\n");
return 1;
}
if (argc < 2)
{
fprintf(stderr, "Usage: %s [-g] binary [args ...]\n", argv[0]);
return 2;
}
const char *filename = argv[1];
bool enable_gdb = false;
int argc_offset = 2;
if (strcmp(filename, "-g") == 0)
{
enable_gdb = true;
argc_offset = 3;
filename = argv[2];
}
elf_firmware_t firmware = {};
elf_read_firmware(filename, &firmware);
avr_init(g_avr);
avr_load_firmware(g_avr, &firmware);
g_avr->frequency = 8000000;
if (enable_gdb)
{
g_avr->state = cpu_Stopped;
g_avr->gdb_port = 1234;
avr_gdb_init(g_avr);
}
init_uart();
avr_irq_register_notify(avr_io_getirq(g_avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 1), status_ok_hook, NULL);
// Pass the rest of arguments to application inside simulator
g_args.argc = argc - argc_offset;
if (g_args.argc > 3) g_args.argc = 3;
for (int i = 0; i < g_args.argc; i++)
{
strncpy(g_args.args[i], argv[i + argc_offset], 15);
}
while (1)
{
int state = avr_run(g_avr);
if (state == cpu_Done)
break;
if (state == cpu_Crashed)
{
fprintf(stderr, "CPU Crashed\n");
return 3;
}
}
if (g_status_ok)
{
return 0;
}
else
{
fprintf(stderr, "Received error status from simulation\n");
return 5;
}
}

View file

@ -0,0 +1,11 @@
# Compiler settings for running the tests on mips-linux-gnu (big endian)
# using qemu. Requires following packages to be installed:
# gcc-mips-linux-gnu g++-mips-linux-gnu qemu-user
def set_mips_platform(env):
env.Replace(EMBEDDED = "MIPS")
env.Replace(CC = "mips-linux-gnu-gcc",
CXX = "mips-linux-gnu-g++")
env.Replace(TEST_RUNNER = "/usr/bin/qemu-mips")
env.Append(LINKFLAGS = "-static")

View file

@ -0,0 +1,11 @@
# Compiler settings for running the tests on mipsel-linux-gnu using
# qemu. Requires following packages to be installed:
# gcc-mipsel-linux-gnu g++-mipsel-linux-gnu qemu-user
def set_mipsel_platform(env):
env.Replace(EMBEDDED = "MIPSEL")
env.Replace(CC = "mipsel-linux-gnu-gcc",
CXX = "mipsel-linux-gnu-g++")
env.Replace(TEST_RUNNER = "/usr/bin/qemu-mipsel")
env.Append(LINKFLAGS = "-static")

View file

@ -0,0 +1,11 @@
# Compiler settings for running the tests on riscv64-linux-gnu
# using qemu. Requires following packages to be installed:
# gcc-riscv64-linux-gnu g++-riscv64-linux-gnu qemu-user
def set_riscv64_platform(env):
env.Replace(EMBEDDED = "RISCV64")
env.Replace(CC = "riscv64-linux-gnu-gcc",
CXX = "riscv64-linux-gnu-g++")
env.Replace(TEST_RUNNER = "/usr/bin/qemu-riscv64")
env.Append(LINKFLAGS = "-static")

View file

@ -0,0 +1,28 @@
#!/bin/bash
BINARY=$1
BASENAME=$(basename $1)
shift
ARGS=$*
test X$OPENOCD_BOARD == X && export OPENOCD_BOARD=board/stm32f7discovery.cfg
timeout 1200s openocd -f $OPENOCD_BOARD \
-c "reset_config srst_only srst_nogate connect_assert_srst" \
-c "init" -c "arm semihosting enable" \
-c "arm semihosting_cmdline $BASENAME $ARGS" \
-c "reset halt" \
-c "load_image $BINARY 0" \
-c "reset halt" -c "resume 0x20000040" 2>openocd.log
RESULT=$?
if [ "$RESULT" -ne "0" ]
then
cat openocd.log >&2
echo >&2
fi
exit $RESULT

View file

@ -0,0 +1,18 @@
# Compiler settings for running the tests on a STM32 discovery board
# Tested on the STM32F7 Discovery, but should work on pretty much
# any STM32 with >= 128kB of RAM. To avoid wearing out the flash,
# code is run from RAM also.
def set_stm32_platform(env):
env.Replace(EMBEDDED = "STM32")
env.Replace(CC = "arm-none-eabi-gcc",
CXX = "arm-none-eabi-g++")
env.Replace(TEST_RUNNER = "site_scons/platforms/stm32/run_test.sh")
env.Append(CPPDEFINES = {'FUZZTEST_BUFSIZE': 4096})
env.Append(CFLAGS = "-mcpu=cortex-m3 -mthumb -Os")
env.Append(CXXFLAGS = "-mcpu=cortex-m3 -mthumb -Os")
env.Append(LINKFLAGS = "-mcpu=cortex-m3 -mthumb")
env.Append(LINKFLAGS = "site_scons/platforms/stm32/vectors.c")
env.Append(LINKFLAGS = "--specs=rdimon.specs")
env.Append(LINKFLAGS = "-Tsite_scons/platforms/stm32/stm32_ram.ld")

View file

@ -0,0 +1,194 @@
/* Linker script to configure memory regions.
* Need modifying for a specific board.
* FLASH.ORIGIN: starting address of flash
* FLASH.LENGTH: length of flash
* RAM.ORIGIN: starting address of RAM bank 0
* RAM.LENGTH: length of RAM bank 0
*/
MEMORY
{
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128k
}
/* Linker script to place sections and symbol values. Should be used together
* with other linker script that defines memory regions FLASH and RAM.
* It references following symbols, which must be defined in code:
* Reset_Handler : Entry of reset handler
*
* It defines following symbols, which code can use without definition:
* __exidx_start
* __exidx_end
* __copy_table_start__
* __copy_table_end__
* __zero_table_start__
* __zero_table_end__
* __etext
* __data_start__
* __preinit_array_start
* __preinit_array_end
* __init_array_start
* __init_array_end
* __fini_array_start
* __fini_array_end
* __data_end__
* __bss_start__
* __bss_end__
* __end__
* end
* __HeapLimit
* __StackLimit
* __StackTop
* __stack
*/
ENTRY(_start)
SECTIONS
{
.text :
{
KEEP(*(.isr_vector))
KEEP(*(.ramboot))
*(.text*)
KEEP(*(.init))
KEEP(*(.fini))
/* .ctors */
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
*(SORT(.ctors.*))
*(.ctors)
/* .dtors */
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
*(SORT(.dtors.*))
*(.dtors)
*(.rodata*)
KEEP(*(.eh_frame*))
} > RAM
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > RAM
__exidx_start = .;
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > RAM
__exidx_end = .;
/* To copy multiple ROM to RAM sections,
* uncomment .copy.table section and,
* define __STARTUP_COPY_MULTIPLE in startup_ARMCMx.S */
/*
.copy.table :
{
. = ALIGN(4);
__copy_table_start__ = .;
LONG (__etext)
LONG (__data_start__)
LONG (__data_end__ - __data_start__)
LONG (__etext2)
LONG (__data2_start__)
LONG (__data2_end__ - __data2_start__)
__copy_table_end__ = .;
} > FLASH
*/
/* To clear multiple BSS sections,
* uncomment .zero.table section and,
* define __STARTUP_CLEAR_BSS_MULTIPLE in startup_ARMCMx.S */
/*
.zero.table :
{
. = ALIGN(4);
__zero_table_start__ = .;
LONG (__bss_start__)
LONG (__bss_end__ - __bss_start__)
LONG (__bss2_start__)
LONG (__bss2_end__ - __bss2_start__)
__zero_table_end__ = .;
} > FLASH
*/
/* Location counter can end up 2byte aligned with narrow Thumb code but
__etext is assumed by startup code to be the LMA of a section in RAM
which must be 4byte aligned */
__etext = ALIGN (4);
.data :
{
__data_start__ = .;
*(vtable)
*(.data*)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP(*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
/* init data */
PROVIDE_HIDDEN (__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP(*(SORT(.fini_array.*)))
KEEP(*(.fini_array))
PROVIDE_HIDDEN (__fini_array_end = .);
KEEP(*(.jcr*))
. = ALIGN(4);
/* All data end */
__data_end__ = .;
} > RAM
.bss :
{
. = ALIGN(4);
__bss_start__ = .;
*(.bss*)
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
} > RAM
.heap (COPY):
{
__end__ = .;
PROVIDE(end = .);
*(.heap*)
__HeapLimit = .;
} > RAM
/* .stack_dummy section doesn't contains any symbols. It is only
* used for linker to calculate size of stack sections, and assign
* values to stack symbols later */
.stack_dummy (COPY):
{
*(.stack*)
} > RAM
/* Set stack top to end of RAM, and stack limit move down by
* size of stack_dummy section */
__StackTop = ORIGIN(RAM) + LENGTH(RAM);
__StackLimit = __StackTop - SIZEOF(.stack_dummy);
PROVIDE(__stack = __StackTop);
/* Check if data + heap + stack exceeds RAM limit */
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")
}

View file

@ -0,0 +1,47 @@
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern void _start();
extern void* __StackTop;
static void HardFaultHandler()
{
uint32_t args[3];
args[0] = 2;
args[1] = (uint32_t)"HARDFAULT";
args[2] = 9;
asm("mov r0, #5\n"
"mov r1, %0\n"
"bkpt 0x00ab" : : "r"(args) : "r0", "r1", "memory");
asm("mov r12, %0\n" "mov r0, #24\n" "bkpt 0x00ab" : : "r"(0xDEADBEEF) : "r0");
while(1);
}
void* const g_vector_table[16] __attribute__((section(".isr_vector"))) = {
(void*)&__StackTop,
(void*)&_start,
(void*)&HardFaultHandler,
(void*)&HardFaultHandler,
(void*)&HardFaultHandler,
(void*)&HardFaultHandler,
(void*)&HardFaultHandler,
};
void ramboot() __attribute__((noreturn, naked, section(".ramboot")));
void ramboot()
{
*(const void**)0xE000ED08 = g_vector_table; // SCB->VTOR
__asm__(
"msr msp, %0\n\t"
"bx %1" : : "r" (g_vector_table[0]),
"r" (g_vector_table[1]) : "memory");
}
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,130 @@
import subprocess
import sys
import re
from platforms.stm32.stm32 import set_stm32_platform
from platforms.avr.avr import set_avr_platform
from platforms.mips.mips import set_mips_platform
from platforms.mipsel.mipsel import set_mipsel_platform
from platforms.riscv64.riscv64 import set_riscv64_platform
platforms = {
'STM32': set_stm32_platform,
'AVR': set_avr_platform,
'MIPS': set_mips_platform,
'MIPSEL': set_mipsel_platform,
'RISCV64': set_riscv64_platform,
}
try:
# Make terminal colors work on windows
import colorama
colorama.init()
except ImportError:
pass
# UTF-8 support on Python 2
if sys.version_info.major == 2:
import codecs
open = codecs.open
def add_nanopb_builders(env):
'''Add the necessary builder commands for nanopb tests.'''
# Build command that runs a test program and saves the output
def run_test(target, source, env):
if len(source) > 1:
infile = open(str(source[1]), 'rb')
else:
infile = None
if "COMMAND" in env:
args = [env["COMMAND"]]
else:
args = [str(source[0])]
if 'ARGS' in env:
args.extend(env['ARGS'])
if "TEST_RUNNER" in env:
args = [env["TEST_RUNNER"]] + args
print('Command line: ' + str(args))
pipe = subprocess.Popen(args,
stdin = infile,
stdout = open(str(target[0]), 'w'),
stderr = sys.stderr)
result = pipe.wait()
if result == 0:
print('\033[32m[ OK ]\033[0m Ran ' + args[0])
else:
print('\033[31m[FAIL]\033[0m Program ' + args[0] + ' returned ' + str(result))
return result
run_test_builder = Builder(action = run_test,
suffix = '.output')
env.Append(BUILDERS = {'RunTest': run_test_builder})
# Build command that decodes a message using protoc
def decode_actions(source, target, env, for_signature):
esc = env['ESCAPE']
dirs = ' '.join(['-I' + esc(env.GetBuildPath(d)) for d in env['PROTOCPATH']])
return '$PROTOC $PROTOCFLAGS %s --decode=%s %s <%s >%s' % (
dirs, env['MESSAGE'], esc(str(source[1])), esc(str(source[0])), esc(str(target[0])))
decode_builder = Builder(generator = decode_actions,
suffix = '.decoded')
env.Append(BUILDERS = {'Decode': decode_builder})
# Build command that encodes a message using protoc
def encode_actions(source, target, env, for_signature):
esc = env['ESCAPE']
dirs = ' '.join(['-I' + esc(env.GetBuildPath(d)) for d in env['PROTOCPATH']])
return '$PROTOC $PROTOCFLAGS %s --encode=%s %s <%s >%s' % (
dirs, env['MESSAGE'], esc(str(source[1])), esc(str(source[0])), esc(str(target[0])))
encode_builder = Builder(generator = encode_actions,
suffix = '.encoded')
env.Append(BUILDERS = {'Encode': encode_builder})
# Build command that asserts that two files be equal
def compare_files(target, source, env):
data1 = open(str(source[0]), 'rb').read()
data2 = open(str(source[1]), 'rb').read()
if data1 == data2:
print('\033[32m[ OK ]\033[0m Files equal: ' + str(source[0]) + ' and ' + str(source[1]))
return 0
else:
print('\033[31m[FAIL]\033[0m Files differ: ' + str(source[0]) + ' and ' + str(source[1]))
return 1
compare_builder = Builder(action = compare_files,
suffix = '.equal')
env.Append(BUILDERS = {'Compare': compare_builder})
# Build command that checks that each pattern in source2 is found in source1.
def match_files(target, source, env):
data = open(str(source[0]), 'r', encoding = 'utf-8').read()
patterns = open(str(source[1]), 'r', encoding = 'utf-8')
for pattern in patterns:
if pattern.strip():
invert = False
if pattern.startswith('! '):
invert = True
pattern = pattern[2:]
status = re.search(pattern.strip(), data, re.MULTILINE)
if not status and not invert:
print('\033[31m[FAIL]\033[0m Pattern not found in ' + str(source[0]) + ': ' + pattern)
return 1
elif status and invert:
print('\033[31m[FAIL]\033[0m Pattern should not exist, but does in ' + str(source[0]) + ': ' + pattern)
return 1
else:
print('\033[32m[ OK ]\033[0m All patterns found in ' + str(source[0]))
return 0
match_builder = Builder(action = match_files, suffix = '.matched')
env.Append(BUILDERS = {'Match': match_builder})

View file

@ -0,0 +1,204 @@
'''
Scons Builder for nanopb .proto definitions.
This tool will locate the nanopb generator and use it to generate .pb.c and
.pb.h files from the .proto files.
Basic example
-------------
# Build myproto.pb.c and myproto.pb.h from myproto.proto
myproto = env.NanopbProto("myproto")
# Link nanopb core to the program
env.Append(CPPPATH = "$NANOB")
myprog = env.Program(["myprog.c", myproto, "$NANOPB/pb_encode.c", "$NANOPB/pb_decode.c"])
Configuration options
---------------------
Normally, this script is used in the test environment of nanopb and it locates
the nanopb generator by a relative path. If this script is used in another
application, the path to nanopb root directory has to be defined:
env.SetDefault(NANOPB = "path/to/nanopb")
Additionally, the path to protoc and the options to give to protoc can be
defined manually:
env.SetDefault(PROTOC = "path/to/protoc")
env.SetDefault(PROTOCFLAGS = "--plugin=protoc-gen-nanopb=path/to/protoc-gen-nanopb")
'''
import SCons.Action
import SCons.Builder
import SCons.Util
from SCons.Script import Dir, File
import os.path
import platform
try:
warningbase = SCons.Warnings.SConsWarning
except AttributeError:
warningbase = SCons.Warnings.Warning
class NanopbWarning(warningbase):
pass
SCons.Warnings.enableWarningClass(NanopbWarning)
def _detect_nanopb(env):
'''Find the path to nanopb root directory.'''
if 'NANOPB' in env:
# Use nanopb dir given by user
return env['NANOPB']
p = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
if os.path.isdir(p) and os.path.isfile(os.path.join(p, 'pb.h')):
# Assume we are running under tests/site_scons/site_tools
return p
raise SCons.Errors.StopError(NanopbWarning,
"Could not find the nanopb root directory")
def _detect_python(env):
'''Find Python executable to use.'''
if 'PYTHON' in env:
return env['PYTHON']
p = env.WhereIs('python3')
if p:
return env['ESCAPE'](p)
p = env.WhereIs('py.exe')
if p:
return env['ESCAPE'](p) + " -3"
return env['ESCAPE'](sys.executable)
def _detect_nanopb_generator(env):
'''Return command for running nanopb_generator.py'''
generator_cmd = os.path.join(env['NANOPB'], 'generator-bin', 'nanopb_generator' + env['PROGSUFFIX'])
if os.path.exists(generator_cmd):
# Binary package
return env['ESCAPE'](generator_cmd)
else:
# Source package
return env['PYTHON'] + " " + env['ESCAPE'](os.path.join(env['NANOPB'], 'generator', 'nanopb_generator.py'))
def _detect_protoc(env):
'''Find the path to the protoc compiler.'''
if 'PROTOC' in env:
# Use protoc defined by user
return env['PROTOC']
n = _detect_nanopb(env)
p1 = os.path.join(n, 'generator-bin', 'protoc' + env['PROGSUFFIX'])
if os.path.exists(p1):
# Use protoc bundled with binary package
return env['ESCAPE'](p1)
p = os.path.join(n, 'generator', 'protoc')
if os.path.exists(p):
# Use the grcpio-tools protoc wrapper
if env['PLATFORM'] == 'win32':
return env['ESCAPE'](p + '.bat')
else:
return env['ESCAPE'](p)
p = env.WhereIs('protoc')
if p:
# Use protoc from path
return env['ESCAPE'](p)
raise SCons.Errors.StopError(NanopbWarning,
"Could not find the protoc compiler")
def _detect_protocflags(env):
'''Find the options to use for protoc.'''
if 'PROTOCFLAGS' in env:
return env['PROTOCFLAGS']
p = _detect_protoc(env)
n = _detect_nanopb(env)
p1 = os.path.join(n, 'generator', 'protoc' + env['PROGSUFFIX'])
p2 = os.path.join(n, 'generator-bin', 'protoc' + env['PROGSUFFIX'])
if p in [env['ESCAPE'](p1), env['ESCAPE'](p2)]:
# Using the bundled protoc, no options needed
return ''
e = env['ESCAPE']
if env['PLATFORM'] == 'win32':
return e('--plugin=protoc-gen-nanopb=' + os.path.join(n, 'generator', 'protoc-gen-nanopb.bat'))
else:
return e('--plugin=protoc-gen-nanopb=' + os.path.join(n, 'generator', 'protoc-gen-nanopb'))
def _nanopb_proto_actions(source, target, env, for_signature):
esc = env['ESCAPE']
# Make protoc build inside the SConscript directory
prefix = os.path.dirname(str(source[-1]))
srcfile = esc(os.path.relpath(str(source[0]), prefix))
include_dirs = '-I.'
for d in env['PROTOCPATH']:
d = env.GetBuildPath(d)
if not os.path.isabs(d): d = os.path.relpath(d, prefix)
include_dirs += ' -I' + esc(d)
# when generating .pb.cpp sources, instead of pb.h generate .pb.hpp headers
source_extension = os.path.splitext(str(target[0]))[1]
header_extension = '.h' + source_extension[2:]
nanopb_flags = env['NANOPBFLAGS']
if nanopb_flags:
nanopb_flags = '--source-extension=%s,--header-extension=%s,%s:.' % (source_extension, header_extension, nanopb_flags)
else:
nanopb_flags = '--source-extension=%s,--header-extension=%s:.' % (source_extension, header_extension)
return SCons.Action.CommandAction('$PROTOC $PROTOCFLAGS %s --nanopb_out=%s %s' % (include_dirs, nanopb_flags, srcfile),
chdir = prefix)
def _nanopb_proto_emitter(target, source, env):
basename = os.path.splitext(str(source[0]))[0]
source_extension = os.path.splitext(str(target[0]))[1]
header_extension = '.h' + source_extension[2:]
target.append(basename + '.pb' + header_extension)
# This is a bit of a hack. protoc include paths work the sanest
# when the working directory is the same as the source root directory.
# To get that directory in _nanopb_proto_actions, we add SConscript to
# the list of source files.
source.append(File("SConscript"))
if os.path.exists(basename + '.options'):
source.append(basename + '.options')
return target, source
_nanopb_proto_builder = SCons.Builder.Builder(
generator = _nanopb_proto_actions,
suffix = '.pb.c',
src_suffix = '.proto',
emitter = _nanopb_proto_emitter)
_nanopb_proto_cpp_builder = SCons.Builder.Builder(
generator = _nanopb_proto_actions,
suffix = '.pb.cpp',
src_suffix = '.proto',
emitter = _nanopb_proto_emitter)
def generate(env):
'''Add Builder for nanopb protos.'''
env['NANOPB'] = _detect_nanopb(env)
env['PROTOC'] = _detect_protoc(env)
env['PROTOCFLAGS'] = _detect_protocflags(env)
env['PYTHON'] = _detect_python(env)
env['NANOPB_GENERATOR'] = _detect_nanopb_generator(env)
env.SetDefault(NANOPBFLAGS = '')
env.SetDefault(PROTOCPATH = [".", os.path.join(env['NANOPB'], 'generator', 'proto')])
env.SetDefault(NANOPB_PROTO_CMD = '$PROTOC $PROTOCFLAGS --nanopb_out=$NANOPBFLAGS:. $SOURCES')
env['BUILDERS']['NanopbProto'] = _nanopb_proto_builder
env['BUILDERS']['NanopbProtoCpp'] = _nanopb_proto_cpp_builder
def exists(env):
return _detect_protoc(env) and _detect_protoc_opts(env)