🔨 Build script improvements

This commit is contained in:
Scott Lahteine 2024-08-10 20:15:08 -05:00
parent b94c75bcda
commit 3b33f7ec03
4 changed files with 68 additions and 39 deletions

View file

@ -110,7 +110,7 @@ def disable_all_options():
match = regex.match(line) match = regex.match(line)
if match: if match:
name = match[3].upper() name = match[3].upper()
if name in ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION'): continue if name in ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION', 'CONFIG_EXAMPLES_DIR'): continue
if name.startswith('_'): continue if name.startswith('_'): continue
found = True found = True
# Comment out the define # Comment out the define

View file

@ -80,7 +80,7 @@ def load_boards():
return '' return ''
# #
# Extract the current configuration files in the form of a structured schema. # Extract the specified configuration files in the form of a structured schema.
# Contains the full schema for the configuration files, not just the enabled options, # Contains the full schema for the configuration files, not just the enabled options,
# Contains the current values of the options, not just data structure, so "schema" is a slight misnomer. # Contains the current values of the options, not just data structure, so "schema" is a slight misnomer.
# #
@ -99,9 +99,9 @@ def load_boards():
# - requires = The conditions that must be met for the define to be enabled # - requires = The conditions that must be met for the define to be enabled
# - comment = The comment for the define, if it has one # - comment = The comment for the define, if it has one
# - units = The units for the define, if it has one # - units = The units for the define, if it has one
# - options = The options for the define, if it has one # - options = The options for the define, if it has any
# #
def extract(): def extract_files(filekey):
# Load board names from boards.h # Load board names from boards.h
boards = load_boards() boards = load_boards()
@ -114,10 +114,8 @@ def extract():
GET_SENSORS = 4 # Gathering temperature sensor options GET_SENSORS = 4 # Gathering temperature sensor options
ERROR = 9 # Syntax error ERROR = 9 # Syntax error
# List of files to process, with shorthand
filekey = { 'Configuration.h':'basic', 'Configuration_adv.h':'advanced' }
# A JSON object to store the data # A JSON object to store the data
sch_out = { 'basic':{}, 'advanced':{} } sch_out = { key:{} for key in filekey.values() }
# Regex for #define NAME [VALUE] [COMMENT] with sanitized line # Regex for #define NAME [VALUE] [COMMENT] with sanitized line
defgrep = re.compile(r'^(//)?\s*(#define)\s+([A-Za-z0-9_]+)\s*(.*?)\s*(//.+)?$') defgrep = re.compile(r'^(//)?\s*(#define)\s+([A-Za-z0-9_]+)\s*(.*?)\s*(//.+)?$')
# Pattern to match a float value # Pattern to match a float value
@ -180,6 +178,12 @@ def extract():
cfield = 'notes' if 'comment' in last_added_ref else 'comment' cfield = 'notes' if 'comment' in last_added_ref else 'comment'
last_added_ref[cfield] = cline last_added_ref[cfield] = cline
#
# Add the given comment line to the comment buffer, unless:
# - The line starts with ':' and JSON values to assign to 'opt'.
# - The line starts with '@section' so a new section needs to be returned.
# - The line starts with '======' so just skip it.
#
def use_comment(c, opt, sec, bufref): def use_comment(c, opt, sec, bufref):
if c.startswith(':'): # If the comment starts with : then it has magic JSON if c.startswith(':'): # If the comment starts with : then it has magic JSON
d = c[1:].strip() # Strip the leading : d = c[1:].strip() # Strip the leading :
@ -199,7 +203,7 @@ def extract():
# The comment will be applied to the next #define. # The comment will be applied to the next #define.
if state == Parse.SLASH_COMMENT: if state == Parse.SLASH_COMMENT:
if not defmatch and the_line.startswith('//'): if not defmatch and the_line.startswith('//'):
use_comment(the_line[2:].strip(), options_json, section, comment_buff) options_json, section = use_comment(the_line[2:].strip(), options_json, section, comment_buff)
continue continue
else: else:
state = Parse.NORMAL state = Parse.NORMAL
@ -219,7 +223,7 @@ def extract():
state = Parse.NORMAL state = Parse.NORMAL
# Strip the leading '*' from block comments # Strip the leading '* ' from block comments
cline = re.sub(r'^\* ?', '', cline) cline = re.sub(r'^\* ?', '', cline)
# Collect temperature sensors # Collect temperature sensors
@ -412,6 +416,13 @@ def extract():
return sch_out return sch_out
#
# Extract the current configuration files in the form of a structured schema.
#
def extract():
# List of files to process, with shorthand
return extract_files({ 'Configuration.h':'basic', 'Configuration_adv.h':'advanced' })
def dump_json(schema:dict, jpath:Path): def dump_json(schema:dict, jpath:Path):
with jpath.open('w') as jfile: with jpath.open('w') as jfile:
json.dump(schema, jfile, ensure_ascii=False, indent=2) json.dump(schema, jfile, ensure_ascii=False, indent=2)

View file

@ -35,18 +35,27 @@ def enabled_defines(filepath):
''' '''
outdict = {} outdict = {}
section = "user" section = "user"
spatt = re.compile(r".*@section +([-a-zA-Z0-9_\s]+)$") # must match @section ... spatt = re.compile(r".*@section +([-a-zA-Z0-9_\s]+)$") # @section ...
f = open(filepath, encoding="utf8").read().split("\n") f = open(filepath, encoding="utf8").read().split("\n")
# Get the full contents of the file and remove all block comments. incomment = False
# This will avoid false positives from #defines in comments
f = re.sub(r'/\*.*?\*/', '', '\n'.join(f), flags=re.DOTALL).split("\n")
for line in f: for line in f:
sline = line.strip() sline = line.strip()
m = re.match(spatt, sline) # @section ... m = re.match(spatt, sline) # @section ...
if m: section = m.group(1).strip() ; continue if m: section = m.group(1).strip() ; continue
if incomment:
if '*/' in sline:
incomment = False
continue
else:
mpos, spos = sline.find('/*'), sline.find('//')
if mpos >= 0 and (spos < 0 or spos > mpos):
incomment = True
continue
if sline[:7] == "#define": if sline[:7] == "#define":
# Extract the key here (we don't care about the value) # Extract the key here (we don't care about the value)
kv = sline[8:].strip().split() kv = sline[8:].strip().split()
@ -70,6 +79,11 @@ def compress_file(filepath, storedname, outpath):
with zipfile.ZipFile(outpath, 'w', compression=zipfile.ZIP_BZIP2, compresslevel=9) as zipf: with zipfile.ZipFile(outpath, 'w', compression=zipfile.ZIP_BZIP2, compresslevel=9) as zipf:
zipf.write(filepath, arcname=storedname, compress_type=zipfile.ZIP_BZIP2, compresslevel=9) zipf.write(filepath, arcname=storedname, compress_type=zipfile.ZIP_BZIP2, compresslevel=9)
ignore = ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION', 'CONFIG_EXAMPLES_DIR', 'CONFIG_EXPORT')
#
# Compute a build signature and/or export the configuration
#
def compute_build_signature(env): def compute_build_signature(env):
''' '''
Compute the build signature by extracting all configuration settings and Compute the build signature by extracting all configuration settings and
@ -81,11 +95,17 @@ def compute_build_signature(env):
env.Append(BUILD_SIGNATURE=1) env.Append(BUILD_SIGNATURE=1)
build_path = Path(env['PROJECT_BUILD_DIR'], env['PIOENV']) build_path = Path(env['PROJECT_BUILD_DIR'], env['PIOENV'])
marlin_json = build_path / 'marlin_config.json' json_name = 'marlin_config.json'
marlin_json = build_path / json_name
marlin_zip = build_path / 'mc.zip' marlin_zip = build_path / 'mc.zip'
# ANSI colors
green = "\u001b[32m"
yellow = "\u001b[33m"
red = "\u001b[31m"
# Definitions from these files will be kept # Definitions from these files will be kept
header_paths = [ 'Marlin/Configuration.h', 'Marlin/Configuration_adv.h' ] header_paths = ('Marlin/Configuration.h', 'Marlin/Configuration_adv.h')
# Check if we can skip processing # Check if we can skip processing
hashes = '' hashes = ''
@ -100,7 +120,7 @@ def compute_build_signature(env):
conf = json.load(infile) conf = json.load(infile)
same_hash = conf['__INITIAL_HASH'] == hashes same_hash = conf['__INITIAL_HASH'] == hashes
if same_hash: if same_hash:
compress_file(marlin_json, 'marlin_config.json', marlin_zip) compress_file(marlin_json, json_name, marlin_zip)
except: except:
pass pass
@ -179,25 +199,28 @@ def compute_build_signature(env):
extended_dump = config_dump > 100 extended_dump = config_dump > 100
if extended_dump: config_dump -= 100 if extended_dump: config_dump -= 100
# Get the schema class for exports that require it
if config_dump in (3, 4) or (extended_dump and config_dump in (2, 5)):
try:
conf_schema = schema.extract()
except Exception as exc:
print(red + "Error: " + str(exc))
conf_schema = None
# #
# Produce an INI file if CONFIG_EXPORT == 2 # Produce an INI file if CONFIG_EXPORT == 2
# #
if config_dump == 2: if config_dump == 2:
print("Generating config.ini ...") print(yellow + "Generating config.ini ...")
ini_fmt = '{0:40} = {1}' ini_fmt = '{0:40} = {1}'
ext_fmt = '{0:40} {1}' ext_fmt = '{0:40} {1}'
ignore = ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION', 'CONFIG_EXAMPLES_DIR', 'CONFIG_EXPORT')
if extended_dump: if extended_dump:
# Extended export will dump config options by section # Extended export will dump config options by section
# We'll use Schema class to get the sections # We'll use Schema class to get the sections
try: if not conf_schema: exit(1)
conf_schema = schema.extract()
except Exception as exc:
print("Error: " + str(exc))
exit(1)
# Then group options by schema @section # Then group options by schema @section
sections = {} sections = {}
@ -305,27 +328,22 @@ f'''#
for header in real_config: for header in real_config:
outfile.write(f'\n[{filegrp[header]}]\n') outfile.write(f'\n[{filegrp[header]}]\n')
for name in sorted(real_config[header]): for name in sorted(real_config[header]):
if name not in ignore: if name in ignore: continue
val = real_config[header][name]['value'] val = real_config[header][name]['value']
if val == '': val = 'on' if val == '': val = 'on'
outfile.write(ini_fmt.format(name.lower(), val) + '\n') outfile.write(ini_fmt.format(name.lower(), val) + '\n')
# #
# CONFIG_EXPORT 3 = schema.json, 4 = schema.yml # CONFIG_EXPORT 3 = schema.json, 4 = schema.yml
# #
if config_dump >= 3: if config_dump in (3, 4):
try:
conf_schema = schema.extract()
except Exception as exc:
print("Error: " + str(exc))
conf_schema = None
if conf_schema: if conf_schema:
# #
# 3 = schema.json # 3 = schema.json
# #
if config_dump in (3, 13): if config_dump in (3, 13):
print("Generating schema.json ...") print(yellow + "Generating schema.json ...")
schema.dump_json(conf_schema, build_path / 'schema.json') schema.dump_json(conf_schema, build_path / 'schema.json')
if config_dump == 13: if config_dump == 13:
schema.group_options(conf_schema) schema.group_options(conf_schema)
@ -335,7 +353,7 @@ f'''#
# 4 = schema.yml # 4 = schema.yml
# #
elif config_dump == 4: elif config_dump == 4:
print("Generating schema.yml ...") print(yellow + "Generating schema.yml ...")
try: try:
import yaml import yaml
except ImportError: except ImportError:
@ -355,7 +373,7 @@ f'''#
json_data = {} json_data = {}
if extended_dump: if extended_dump:
print("Extended dump ...") print(yellow + "Extended dump ...")
for header in real_config: for header in real_config:
confs = real_config[header] confs = real_config[header]
json_data[header] = {} json_data[header] = {}
@ -395,7 +413,7 @@ f'''#
# Compress the JSON file as much as we can # Compress the JSON file as much as we can
if not same_hash: if not same_hash:
compress_file(marlin_json, 'marlin_config.json', marlin_zip) compress_file(marlin_json, json_name, marlin_zip)
# Generate a C source file containing the entire ZIP file as an array # Generate a C source file containing the entire ZIP file as an array
with open('Marlin/src/mczip.h','wb') as result_file: with open('Marlin/src/mczip.h','wb') as result_file:

View file

@ -1,6 +1,6 @@
/** /**
* Marlin 3D Printer Firmware * Marlin 3D Printer Firmware
* Copyright (c) 2023 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
* *
* Based on Sprinter and grbl. * Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm