Skip to content

Commit

Permalink
Use config.py, simplify
Browse files Browse the repository at this point in the history
  • Loading branch information
thinkyhead committed Jan 7, 2025
1 parent 99f1138 commit 1ff55f8
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 163 deletions.
18 changes: 12 additions & 6 deletions buildroot/bin/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
'''
config.py - Helper functions for config manipulation
Make sure both copies always match:
- buildroot/bin/config.py
- buildroot/share/PlatformIO/scripts/config.py
'''
import re

Expand All @@ -17,24 +22,25 @@ def set(file_path, define_name, value):
modified = False
for i in range(len(content)):
# Regex to match the desired pattern
match = re.match(r'^(\s*)(/*)(\s*)(#define\s+{})\s+(.*)$'.format(re.escape(define_name)), content[i])
match = re.match(r'^(\s*)(/*)(\s*)(#define\s+{})\s+(.*?)\s*(//.*)?$'.format(re.escape(define_name)), content[i])
if match:
new_line = f"{match[1]}{match[3]}{match[4]} {value} // {match[5]}\n"
content[i] = new_line
modified = True
comm = '' if match[6] is None else ' ' + match[6]
oldval = '' if match[5] is None else match[5]
if match[2] or value != oldval:
content[i] = f"{match[1]}{match[3]}{match[4]} {value} // {match[5]}{comm}\n"

# Write the modified content back to the file only if changes were made
if modified:
with open(file_path, 'w') as f:
f.writelines(content)
return True
return True

return False

def add(file_path, define_name, value=""):
'''
Insert a define on the first blank line in a file.
Returns True if the define was found and replaced, False otherwise.
'''
with open(file_path, 'r') as f:
content = f.readlines()
Expand Down Expand Up @@ -66,7 +72,7 @@ def enable(file_path, define_name, enable=True):
content = f.readlines()

# Prepare the regex
regex = re.compile(r'^(\s*)(/*)(\s*)(#define\s+{}\b.*?)( *//.*)?$'.format(re.escape(define_name)))
regex = re.compile(r'^(\s*)(/*)(\s*)(#define\s+{}\b.*?)(\s*//.*)?$'.format(re.escape(define_name)))

# Find the define in the file and uncomment or comment it
found = False
Expand Down
102 changes: 102 additions & 0 deletions buildroot/share/PlatformIO/scripts/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
'''
config.py - Helper functions for config manipulation
Make sure both copies always match:
- buildroot/bin/config.py
- buildroot/share/PlatformIO/scripts/config.py
'''
import re

FILES = ('Marlin/Configuration.h', 'Marlin/Configuration_adv.h')

def set(file_path, define_name, value):
'''
Replaces a define in a file with a new value.
Returns True if the define was found and replaced, False otherwise.
'''
# Read the contents of the file
with open(file_path, 'r') as f:
content = f.readlines()

modified = False
for i in range(len(content)):
# Regex to match the desired pattern
match = re.match(r'^(\s*)(/*)(\s*)(#define\s+{})\s+(.*?)\s*(//.*)?$'.format(re.escape(define_name)), content[i])
if match:
modified = True
comm = '' if match[6] is None else ' ' + match[6]
oldval = '' if match[5] is None else match[5]
if match[2] or value != oldval:
content[i] = f"{match[1]}{match[3]}{match[4]} {value} // {match[5]}{comm}\n"

# Write the modified content back to the file only if changes were made
if modified:
with open(file_path, 'w') as f:
f.writelines(content)
return True

return False

def add(file_path, define_name, value=""):
'''
Insert a define on the first blank line in a file.
'''
with open(file_path, 'r') as f:
content = f.readlines()

# Prepend a space to the value if it's not empty
if value != "":
value = " " + value

# Find the first blank line to insert the new define
for i in range(len(content)):
if content[i].strip() == '':
# Insert the define at the first blank line
content.insert(i, f"#define {define_name}{value}\n")
break
else:
# If no blank line is found, append to the end
content.append(f"#define {define_name}{value}\n")

with open(file_path, 'w') as f:
f.writelines(content)

def enable(file_path, define_name, enable=True):
'''
Uncomment or comment the named defines in the given file path.
Returns True if the define was found, False otherwise.
'''
# Read the contents of the file
with open(file_path, 'r') as f:
content = f.readlines()

# Prepare the regex
regex = re.compile(r'^(\s*)(/*)(\s*)(#define\s+{}\b.*?)(\s*//.*)?$'.format(re.escape(define_name)))

# Find the define in the file and uncomment or comment it
found = False
modified = False
for i in range(len(content)):
match = regex.match(content[i])
if not match: continue
found = True
if enable:
if match[2]:
modified = True
comment = '' if match[5] is None else ' ' + match[5]
content[i] = f"{match[1]}{match[3]}{match[4]}{comment}\n"
else:
if not match[2]:
modified = True
comment = '' if match[5] is None else match[5]
if comment.startswith(' '): comment = comment[2:]
content[i] = f"{match[1]}//{match[3]}{match[4]}{comment}\n"
break

# Write the modified content back to the file only if changes were made
if modified:
with open(file_path, 'w') as f:
f.writelines(content)

return found
220 changes: 63 additions & 157 deletions buildroot/share/PlatformIO/scripts/mc-apply.py
Original file line number Diff line number Diff line change
@@ -1,177 +1,83 @@
#!/usr/bin/env python
"""
mc-apply.py
Create Marlin firmware configuration files from a JSON file (marlin_config.json).
usage: mc-apply.py [-h] [--opt] [--bare-output] [config_file]
Process Marlin firmware configuration.
positional arguments:
config_file Path to the configuration file.
optional arguments:
-h, --help show this help message and exit
--opt Enable optional output format.
--bare-output Disable output suffix.
"""
#
# mc-apply.py
#
# Apply firmware configuration from a JSON file (marlin_config.json).
#
# usage: mc-apply.py [-h] [--opt] [config_file]
#
# Process Marlin firmware configuration.
#
# positional arguments:
# config_file Path to the configuration file.
#
# optional arguments:
# -h, --help show this help message and exit
# --opt Enable optional output format.
#
import json, sys, shutil
import os, re
import config
import argparse
import logging
from typing import Dict, List

logging.basicConfig(level=logging.INFO)

MARLIN_CONFIG_FILES = ('Configuration.h', 'Configuration_adv.h')

# Load and return the JSON configuration from the specified file path.
# Exit with an error message if the file hasn't got CONFIGURATION_EMBEDDING or CONFIG_EXPORT 1
def load_config(file_path: str) -> Dict:
try:
with open(file_path, 'r') as file:
config = json.load(file)
if config.get("CONFIG_EXPORT") != "1" and config.get("CONFIGURATION_EMBEDDING") is None:
logging.error(f"{config_file_path} isn't a valid CONFIG_EXPORT 1.")
sys.exit(1)
return config
except FileNotFoundError:
logging.error(f'{file_path} not found.')
sys.exit(1)
except json.JSONDecodeError:
logging.error(f'Failed to decode JSON from {file_path}.')
sys.exit(1)

# Writes the given content to the specified file path.
# Exits with an error message if writing to the file fails.
def write_output_file(file_path: str, content: str) -> None:
try:
with open(file_path, 'w') as outfile:
outfile.write(content)
except IOError as e:
logging.error(f'Failed to write to {file_path}. {e}')
sys.exit(1)

# Move a file from the source to the destination path.
# Exit with an error message if the move operation fails.
def move_file(src: str, dst: str) -> None:
try:
shutil.move(src, dst)
except IOError as e:
logging.error(f'Failed to move {src} to {dst}. {e}')
sys.exit(1)

# Move files from the source list to the destination list.
# Exit with an error message if the source and destination lists have different lengths.
def move_list_files(src_list: List[str], dst_list: List[str]) -> None:
if len(src_list) != len(dst_list):
logging.error('Source / destination length mismatch.')
sys.exit(1)
for src, dst in zip(src_list, dst_list):
move_file(src, dst)
def report_version(conf):
if 'VERSION' in conf:
for k, v in sorted(conf['VERSION'].items()):
print(k + ': ' + v)

# Process the configuration dictionary and write the output configuration files.
# Handles both standard and optional output formats.
# Move original files to backup locations if an output suffix is specified.
def process_configuration(conf: Dict, opt_output: bool, output_suffix: str) -> None:
output_file_path = os.path.join('Marlin', MARLIN_CONFIG_FILES[0] + output_suffix)
content = ''
def write_opt_file(conf, outpath='Marlin/apply_config.sh'):
with open(outpath, 'w') as outfile:
for key, val in conf.items():
if key in ('__INITIAL_HASH', 'VERSION'): continue

for key, values in conf.items():
if key in ('__INITIAL_HASH', 'VERSION'):
if key == 'VERSION':
for k, v in sorted(values.items()):
logging.info(f'{k}: {v}')
continue

if opt_output:
if values:
if '"' in values:
values = f"'{values}'"
elif ' ' in values:
values = f'"{values}"'
define = f'opt_set {key} {values}\n'
else:
define = f'opt_enable {key}\n'
else:
define = f'#define {key} {values}\n'
content += define.strip() + "\n"

write_output_file(output_file_path, content)

if output_suffix:
original_file_paths = [os.path.join('Marlin', config) for config in MARLIN_CONFIG_FILES]
backup_file_paths = [path + '.orig' for path in original_file_paths]
move_list_files(original_file_paths, backup_file_paths)

lines = []
separator = "#mc-apply-separator"

for backup_file_path in backup_file_paths:
try:
with open(backup_file_path, 'r') as file:
lines.extend(file.read().split('\n'))
lines.append(separator)
except IOError as e:
logging.error(f'Failed to read from {backup_file_path}. {e}')
sys.exit(1)

content = ''
conf.pop('__INITIAL_HASH', None)
conf.pop('VERSION', None)
for line in lines:
sline = re.sub(r'//\s*(#define)', '$1', line)
if sline.startswith("#define"):
indent = line[:len(line) - len(line.lstrip())]
trailing_comment = ''
if '//' in sline:
cpart = sline.split('//', 1)
sline = cpart[0]
trailing_comment = '//' + cpart[1]
trailing_comment_ws = sline[len(sline.rstrip()):]
trailing_comment = trailing_comment_ws + trailing_comment
kv = sline[8:].strip().split()
mid_whitespace = sline.split(kv[0], 1)[1].rsplit(kv[1], 1)[0] if len(kv) > 1 else ''
if kv[0] in conf:
line = f'{indent}#define {kv[0]}{mid_whitespace}{conf[kv[0]]}{trailing_comment}'
del conf[kv[0]]
content += line + '\n'

for k, v in sorted(conf.items()):
content += f'#define {k} {v}\n'

seen_separator = 0
config_data = ''
config_adv_data = ''
for line in content.split('\n'):
if separator in line:
seen_separator += 1
# Other keys are assumed to be configs
if not type(val) is dict:
continue
if seen_separator == 0 or seen_separator >= 2:
config_data += line + '\n'
else:
config_adv_data += line + '\n'
write_output_file(original_file_paths[0], config_data)
write_output_file(original_file_paths[1], config_adv_data)

logging.info(f'Output configuration written to: {output_file_path}')
# Write config commands to the script file
lines = []
for k, v in sorted(val.items()):
if v != '':
v.replace('"', '\\"').replace("'", "\\'").replace(' ', '\\ ')
lines += [f'opt_set {k} {v}']
else:
lines += [f'opt_enable {k}']

outfile.write('\n'.join(lines))

print('Config script written to: ' + outpath)

def apply_config(conf):
for key in conf:
if key in ('__INITIAL_HASH', 'VERSION'): continue
for k, v in conf[key].items():
if v:
config.set('Marlin/' + key, k, v)
else:
config.enable('Marlin/' + key, k)

# Parse command line arguments to get config file path, output format, and output suffix.
# Load the configuration and process it.
def main() -> None:
def main():
parser = argparse.ArgumentParser(description='Process Marlin firmware configuration.')
parser.add_argument('--opt', action='store_true', help='Enable optional output format.')
parser.add_argument('--bare-output', action='store_true', help='Disable output suffix.')
parser.add_argument('config_file', nargs='?', default='marlin_config.json', help='Path to the configuration file.')

args = parser.parse_args()

opt_output = args.opt
output_suffix = '.sh' if opt_output else '' if args.bare_output else '.gen'
config_file_path = args.config_file

conf = load_config(config_file_path)
process_configuration(conf, opt_output, output_suffix)
try:
infile = open('marlin_config.json', 'r')
except:
print('No marlin_config.json found.')
sys.exit(1)

conf = json.load(infile)

report_version(conf)

if opt_output:
write_opt_file(conf)
else:
apply_config(conf)

if __name__ == '__main__':
main()

0 comments on commit 1ff55f8

Please sign in to comment.