Skip to content

Commit 36623a2

Browse files
ellenspthinkyhead
andauthored
🐛 Fix config embed and restore (#27628)
Co-authored-by: Scott Lahteine <[email protected]>
1 parent 83278bd commit 36623a2

File tree

4 files changed

+217
-71
lines changed

4 files changed

+217
-71
lines changed

buildroot/bin/config.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
'''
22
config.py - Helper functions for config manipulation
3+
4+
Make sure both copies always match:
5+
- buildroot/bin/config.py
6+
- buildroot/share/PlatformIO/scripts/config.py
7+
38
'''
49
import re
510

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

2633
# Write the modified content back to the file only if changes were made
2734
if modified:
2835
with open(file_path, 'w') as f:
2936
f.writelines(content)
30-
return True
37+
return True
3138

3239
return False
3340

3441
def add(file_path, define_name, value=""):
3542
'''
3643
Insert a define on the first blank line in a file.
37-
Returns True if the define was found and replaced, False otherwise.
3844
'''
3945
with open(file_path, 'r') as f:
4046
content = f.readlines()
@@ -66,7 +72,7 @@ def enable(file_path, define_name, enable=True):
6672
content = f.readlines()
6773

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

7177
# Find the define in the file and uncomment or comment it
7278
found = False
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
'''
2+
config.py - Helper functions for config manipulation
3+
4+
Make sure both copies always match:
5+
- buildroot/bin/config.py
6+
- buildroot/share/PlatformIO/scripts/config.py
7+
8+
'''
9+
import re
10+
11+
FILES = ('Marlin/Configuration.h', 'Marlin/Configuration_adv.h')
12+
13+
def set(file_path, define_name, value):
14+
'''
15+
Replaces a define in a file with a new value.
16+
Returns True if the define was found and replaced, False otherwise.
17+
'''
18+
# Read the contents of the file
19+
with open(file_path, 'r') as f:
20+
content = f.readlines()
21+
22+
modified = False
23+
for i in range(len(content)):
24+
# Regex to match the desired pattern
25+
match = re.match(r'^(\s*)(/*)(\s*)(#define\s+{})\s+(.*?)\s*(//.*)?$'.format(re.escape(define_name)), content[i])
26+
if match:
27+
modified = True
28+
comm = '' if match[6] is None else ' ' + match[6]
29+
oldval = '' if match[5] is None else match[5]
30+
if match[2] or value != oldval:
31+
content[i] = f"{match[1]}{match[3]}{match[4]} {value} // {match[5]}{comm}\n"
32+
33+
# Write the modified content back to the file only if changes were made
34+
if modified:
35+
with open(file_path, 'w') as f:
36+
f.writelines(content)
37+
return True
38+
39+
return False
40+
41+
def add(file_path, define_name, value=""):
42+
'''
43+
Insert a define on the first blank line in a file.
44+
'''
45+
with open(file_path, 'r') as f:
46+
content = f.readlines()
47+
48+
# Prepend a space to the value if it's not empty
49+
if value != "":
50+
value = " " + value
51+
52+
# Find the first blank line to insert the new define
53+
for i in range(len(content)):
54+
if content[i].strip() == '':
55+
# Insert the define at the first blank line
56+
content.insert(i, f"#define {define_name}{value}\n")
57+
break
58+
else:
59+
# If no blank line is found, append to the end
60+
content.append(f"#define {define_name}{value}\n")
61+
62+
with open(file_path, 'w') as f:
63+
f.writelines(content)
64+
65+
def enable(file_path, define_name, enable=True):
66+
'''
67+
Uncomment or comment the named defines in the given file path.
68+
Returns True if the define was found, False otherwise.
69+
'''
70+
# Read the contents of the file
71+
with open(file_path, 'r') as f:
72+
content = f.readlines()
73+
74+
# Prepare the regex
75+
regex = re.compile(r'^(\s*)(/*)(\s*)(#define\s+{}\b.*?)(\s*//.*)?$'.format(re.escape(define_name)))
76+
77+
# Find the define in the file and uncomment or comment it
78+
found = False
79+
modified = False
80+
for i in range(len(content)):
81+
match = regex.match(content[i])
82+
if not match: continue
83+
found = True
84+
if enable:
85+
if match[2]:
86+
modified = True
87+
comment = '' if match[5] is None else ' ' + match[5]
88+
content[i] = f"{match[1]}{match[3]}{match[4]}{comment}\n"
89+
else:
90+
if not match[2]:
91+
modified = True
92+
comment = '' if match[5] is None else match[5]
93+
if comment.startswith(' '): comment = comment[2:]
94+
content[i] = f"{match[1]}//{match[3]}{match[4]}{comment}\n"
95+
break
96+
97+
# Write the modified content back to the file only if changes were made
98+
if modified:
99+
with open(file_path, 'w') as f:
100+
f.writelines(content)
101+
102+
return found
Lines changed: 93 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,100 @@
11
#!/usr/bin/env python
22
#
3-
# Create a Configuration from marlin_config.json
3+
# mc-apply.py
44
#
5-
import json, sys, shutil
5+
# Apply firmware configuration from a JSON file (marlin_config.json).
6+
#
7+
# usage: mc-apply.py [-h] [--opt] [config_file]
8+
#
9+
# Process Marlin firmware configuration.
10+
#
11+
# positional arguments:
12+
# config_file Path to the configuration file.
13+
#
14+
# optional arguments:
15+
# -h, --help show this help message and exit
16+
# --opt Output as an option setting script.
17+
#
18+
import json, sys, os
19+
import config
20+
import argparse
621

7-
opt_output = '--opt' in sys.argv
8-
output_suffix = '.sh' if opt_output else '' if '--bare-output' in sys.argv else '.gen'
22+
def report_version(conf):
23+
if 'VERSION' in conf:
24+
for k, v in sorted(conf['VERSION'].items()):
25+
print(k + ': ' + v)
926

10-
try:
11-
with open('marlin_config.json', 'r') as infile:
12-
conf = json.load(infile)
13-
for key in conf:
14-
# We don't care about the hash when restoring here
15-
if key == '__INITIAL_HASH':
16-
continue
17-
if key == 'VERSION':
18-
for k, v in sorted(conf[key].items()):
19-
print(k + ': ' + v)
27+
def write_opt_file(conf, outpath='Marlin/apply_config.sh'):
28+
with open(outpath, 'w') as outfile:
29+
for key, val in conf.items():
30+
if key in ('__INITIAL_HASH', 'VERSION'): continue
31+
32+
# Other keys are assumed to be configs
33+
if not type(val) is dict:
2034
continue
21-
# The key is the file name, so let's build it now
22-
outfile = open('Marlin/' + key + output_suffix, 'w')
23-
for k, v in sorted(conf[key].items()):
24-
# Make define line now
25-
if opt_output:
26-
if v != '':
27-
if '"' in v:
28-
v = "'%s'" % v
29-
elif ' ' in v:
30-
v = '"%s"' % v
31-
define = 'opt_set ' + k + ' ' + v + '\n'
32-
else:
33-
define = 'opt_enable ' + k + '\n'
35+
36+
# Write config commands to the script file
37+
lines = []
38+
for k, v in sorted(val.items()):
39+
if v != '':
40+
v.replace('"', '\\"').replace("'", "\\'").replace(' ', '\\ ')
41+
lines += [f'opt_set {k} {v}']
3442
else:
35-
define = '#define ' + k + ' ' + v + '\n'
36-
outfile.write(define)
37-
outfile.close()
38-
39-
# Try to apply changes to the actual configuration file (in order to keep useful comments)
40-
if output_suffix != '':
41-
# Move the existing configuration so it doesn't interfere
42-
shutil.move('Marlin/' + key, 'Marlin/' + key + '.orig')
43-
infile_lines = open('Marlin/' + key + '.orig', 'r').read().split('\n')
44-
outfile = open('Marlin/' + key, 'w')
45-
for line in infile_lines:
46-
sline = line.strip(" \t\n\r")
47-
if sline[:7] == "#define":
48-
# Extract the key here (we don't care about the value)
49-
kv = sline[8:].strip().split(' ')
50-
if kv[0] in conf[key]:
51-
outfile.write('#define ' + kv[0] + ' ' + conf[key][kv[0]] + '\n')
52-
# Remove the key from the dict, so we can still write all missing keys at the end of the file
53-
del conf[key][kv[0]]
54-
else:
55-
outfile.write(line + '\n')
56-
else:
57-
outfile.write(line + '\n')
58-
# Process any remaining defines here
59-
for k, v in sorted(conf[key].items()):
60-
define = '#define ' + k + ' ' + v + '\n'
61-
outfile.write(define)
62-
outfile.close()
63-
64-
print('Output configuration written to: ' + 'Marlin/' + key + output_suffix)
65-
except:
66-
print('No marlin_config.json found.')
43+
lines += [f'opt_enable {k}']
44+
45+
outfile.write('\n'.join(lines))
46+
47+
print('Config script written to: ' + outpath)
48+
49+
def back_up_config(name):
50+
# Back up the existing file before modifying it
51+
conf_path = 'Marlin/' + name
52+
with open(conf_path, 'r') as f:
53+
# Write a filename.bak#.ext retaining the original extension
54+
parts = conf_path.split('.')
55+
nr = ''
56+
while True:
57+
bak_path = '.'.join(parts[:-1]) + f'.bak{nr}.' + parts[-1]
58+
if os.path.exists(bak_path):
59+
nr = 1 if nr == '' else nr + 1
60+
continue
61+
62+
with open(bak_path, 'w') as b:
63+
b.writelines(f.readlines())
64+
break
65+
66+
def apply_config(conf):
67+
for key in conf:
68+
if key in ('__INITIAL_HASH', 'VERSION'): continue
69+
70+
back_up_config(key)
71+
72+
for k, v in conf[key].items():
73+
if v:
74+
config.set('Marlin/' + key, k, v)
75+
else:
76+
config.enable('Marlin/' + key, k)
77+
78+
def main():
79+
parser = argparse.ArgumentParser(description='Process Marlin firmware configuration.')
80+
parser.add_argument('--opt', action='store_true', help='Output as an option setting script.')
81+
parser.add_argument('config_file', nargs='?', default='marlin_config.json', help='Path to the configuration file.')
82+
83+
args = parser.parse_args()
84+
85+
try:
86+
infile = open(args.config_file, 'r')
87+
except:
88+
print(f'No {args.config_file} found.')
89+
sys.exit(1)
90+
91+
conf = json.load(infile)
92+
report_version(conf)
93+
94+
if args.opt:
95+
write_opt_file(conf)
96+
else:
97+
apply_config(conf)
98+
99+
if __name__ == '__main__':
100+
main()

buildroot/share/PlatformIO/scripts/signature.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ def get_file_sha256sum(filepath):
7575
#
7676
import zipfile
7777
def compress_file(filepath, storedname, outpath):
78-
with zipfile.ZipFile(outpath, 'w', compression=zipfile.ZIP_BZIP2, compresslevel=9) as zipf:
79-
zipf.write(filepath, arcname=storedname, compress_type=zipfile.ZIP_BZIP2, compresslevel=9)
78+
with zipfile.ZipFile(outpath, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False, compresslevel=9) as zipf:
79+
zipf.write(filepath, arcname=storedname)
8080

8181
ignore = ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION', 'CONFIG_EXAMPLES_DIR', 'CONFIG_EXPORT')
8282

@@ -161,7 +161,8 @@ def compute_build_signature(env):
161161
#
162162
# Continue to gather data for CONFIGURATION_EMBEDDING or CONFIG_EXPORT
163163
#
164-
if not ('CONFIGURATION_EMBEDDING' in build_defines or 'CONFIG_EXPORT' in build_defines):
164+
is_embed = 'CONFIGURATION_EMBEDDING' in build_defines
165+
if not (is_embed or 'CONFIG_EXPORT' in build_defines):
165166
return
166167

167168
# Filter out useless macros from the output
@@ -450,7 +451,7 @@ def optsort(x, optorder):
450451
# Produce a JSON file for CONFIGURATION_EMBEDDING or CONFIG_EXPORT == 1 or 101
451452
# Skip if an identical JSON file was already present.
452453
#
453-
if not same_hash and (config_dump == 1 or 'CONFIGURATION_EMBEDDING' in build_defines):
454+
if not same_hash and (config_dump == 1 or is_embed):
454455
with marlin_json.open('w') as outfile:
455456

456457
json_data = {}
@@ -460,16 +461,19 @@ def optsort(x, optorder):
460461
confs = real_config[header]
461462
json_data[header] = {}
462463
for name in confs:
464+
if name in ignore: continue
463465
c = confs[name]
464466
s = c['section']
465467
if s not in json_data[header]: json_data[header][s] = {}
466468
json_data[header][s][name] = c['value']
467469
else:
468470
for header in real_config:
471+
json_data[header] = {}
469472
conf = real_config[header]
470473
#print(f"real_config[{header}]", conf)
471474
for name in conf:
472-
json_data[name] = conf[name]['value']
475+
if name in ignore: continue
476+
json_data[header][name] = conf[name]['value']
473477

474478
json_data['__INITIAL_HASH'] = hashes
475479

@@ -489,7 +493,7 @@ def optsort(x, optorder):
489493
#
490494
# The rest only applies to CONFIGURATION_EMBEDDING
491495
#
492-
if not 'CONFIGURATION_EMBEDDING' in build_defines:
496+
if not is_embed:
493497
(build_path / 'mc.zip').unlink(missing_ok=True)
494498
return
495499

0 commit comments

Comments
 (0)