REDAC HybridController
Firmware for LUCIDAC/REDAC Teensy
Loading...
Searching...
No Matches
build_distributor.py
Go to the documentation of this file.
1# This is a pio build script (see also scons.org)
2# and can also be used as a standlone python3 script.
3#
4# The purpose of this script is to build distributor-specific
5# data into the ROM. These data are not subject to be changed
6# by users or their settings. The data is stored as a simple
7# key-value "database"/dictionary.
8#
9# Note that Teensy 3 is 32bit ARM and any string literals defined
10# with "const" go automatically into flash memory, there is no more
11# need for PROGMEM, F() and similar.
12#
13# Note that we want to foster reproducable builds (deterministic
14# compilation), thus things as build time, build system paths or
15# the build system hostname is not included.
16#
17# Within the Pio build process, this python script is not invoked
18# regularly as a seperate process but within the pio code.
19# Therefore, magic such as Import("env") can be evaluated within pio.
20
21try:
22 # the following code demonstrates how to access build system variables.
23 Import("env")
24 #print(env.Dump())
25 interesting_fields = ["BOARD", "BUILD_TYPE"]#, "UPLOAD_PROTOCOL"]
26 build_system = { k: env.Dictionary(k) for k in interesting_fields }
27 build_flags = env.Dictionary('BUILD_FLAGS') # is a list
28except (NameError, KeyError):
29 # NameError: pure python does not know 'Import' (is not defined in pure Python).
30 # KeyError: doesn't know some of the interesting_fields keys
31 build_system = {}
32 build_flags = []
33
34# all the following libs are python built-ins:
35import datetime
36import json
37import pathlib
38import re
39import subprocess
40
41lib_dir = pathlib.Path("./").resolve() # use this if called from library.json
42rel_src_dir = lib_dir / pathlib.Path("src/build") # without trailing slash
43
44
45print(f"OEM distribution generating infos at {rel_src_dir}")
46warn = lambda msg: print("build_distributor.py: WARN ", msg)
47
48# Read off the current firmware version from the latest git tag. There are alternatives, see
49# https://setuptools-git-versioning.readthedocs.io/en/stable/differences.html for a list, but
50# in the end they all call external git and parse the output.
51firmware_version = subprocess.getoutput("which git >/dev/null && git describe --tags || echo no-git-avail").strip()
52firmware_version_useful = bool(re.match(r"v\d+\.\d+(-\d+.*)?", firmware_version)) # a proper string is for instance "0.2-80-g302f016"
53not_available = "N/A" # placeholder string used instead
54if not firmware_version_useful: firmware_version = not_available # instead of subprocess garbage
55
56# Use the current commit time as firmware date approximator.
57# Do not use the current time, as we want to have reproducable builds.
58try:
59 unix_timestamp = subprocess.getoutput("which git >/dev/null && git log -1 --format=%ct || echo failure").strip()
60 firmware_version_date = datetime.datetime.fromtimestamp(int(unix_timestamp)).isoformat()
61except ValueError:
62 print("Read: ", unix_timestamp)
63 warn("No git available, have no information about version and date at all.")
64 firmware_version_date = not_available
65
66rename_keys = lambda dct, prefix='',suffix='': {prefix+k+suffix:v for k,v in dct.items()}
67
68# Given that firmware builds are supposed to be published for allowing users
69# to update their LUCIDAC on their own, do not include things like device
70# serial keys here. Instead of the firmware, they shall be stored in "EEPROM",
71# cf. the src/nvmconfig/ subsystem.
72
73distdb = dict(
74 OEM = "anabrid",
75 OEM_MODEL_NAME = "LUCIDAC",
76
77 BUILD_SYSTEM_NAME = "pio",
78 **rename_keys(build_system, prefix="BUILD_SYSTEM_"),
79 BUILD_FLAGS = " ".join(build_flags),
80
81 FIRMWARE_VERSION = firmware_version,
82 FIRMWARE_DATE = firmware_version_date,
83
84 #PROTOCOL_VERSION = "0.0.2", # FIXME
85 #PROTOCOL_DATE = firmware_version_date, # FIXME
86)
87
88# uncomment this line to inspect the full data
89# pprint.pprint(item)
90
91for k,v in distdb.items():
92 assert isinstance(v,str), f"Expected {k} to be string but it is: {v}"
93
94nl, quote = "\n", '"'
95esc = lambda s: s.replace(quote, '\\"')
96
97distdb_as_variables = "\n".join(f"const char {k}[] PROGMEM = {quote}{esc(v)}{quote};" for k,v in distdb.items())
98distdb_variable_lookup = "\n".join(f" if(strcmp_P(key, (PGM_P)F({quote}{k}{quote}) )) return {k};" for k,v in distdb.items())
99distdb_as_defines = "\n".join(f"#define {k} {quote}{esc(v)}{quote}" for k,v in distdb.items())
100distdb_as_json_string_esc = esc(json.dumps(distdb));
101distdb_as_json_defines = "\n".join(f" target[{quote}{k}{quote}] = {k};" for k,v in distdb.items())
102
103code_files = {}
104
105code_files["distributor_generated.h"] = \
106"""
107// This file was written by dist/build_progmem.py and is not supposed to be git-versioned
108
109// This file is not supposed to be included directly. Include "distributor.h" instead.
110
111%(distdb_as_defines)s
112
113"""
114
115code_files["distributor_generated.cpp"] = \
116"""
117#include <build/distributor.h>
118#include "distributor_generated.h"
119
120// Interestingly, FLASHMEM and PROGMEM *do* have an effect in Teensy,
121// which is whether functions are copied into ICTM RAM or not. If not provided,
122// it is up to the compiler (linker) to decide where to put stuff.
123
124FLASHMEM const char* dist::ident() {
125 return OEM_MODEL_NAME " Hybrid Controller (" FIRMWARE_VERSION ")";
126}
127
128FLASHMEM void dist::write_to_json(JsonObject target) {
129%(distdb_as_json_defines)s
130}
131"""
132
133for fname, content in code_files.items():
134 pathlib.Path(rel_src_dir / fname).write_text(content % locals())
135
136# just to make sure the generated code does not touch the git repository.
137(rel_src_dir / ".gitignore").write_text("".join( f"{fname}\n" for fname in code_files.keys()))