Skip to content

Commit 5ed6746

Browse files
committed
Add project dependencies output
1 parent 16647cf commit 5ed6746

File tree

1 file changed

+138
-30
lines changed

1 file changed

+138
-30
lines changed

build_platform.py

Lines changed: 138 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@
2929
sys.argv.pop(sys.argv.index("--build_timeout") + 1)
3030
sys.argv.remove("--build_timeout")
3131

32+
# optional wippersnapper argument to generate a dependencies header file
33+
PRINT_DEPENDENCIES_AS_HEADER = False
34+
INCLUDE_PRINT_DEPENDENCIES_HEADER = False
35+
PRINT_DEPENDENCIES_AS_HEADER_FILENAME = None
36+
if "--include_print_dependencies_header" in sys.argv:
37+
# check argument not null and folder path exists
38+
PRINT_DEPENDENCIES_AS_HEADER_FILENAME = sys.argv[sys.argv.index("--include_print_dependencies_header") + 1] if len(sys.argv) > sys.argv.index("--include_print_dependencies_header") + 1 else None
39+
if PRINT_DEPENDENCIES_AS_HEADER_FILENAME is None or not os.path.exists(PRINT_DEPENDENCIES_AS_HEADER_FILENAME):
40+
raise AttributeError("Header file path not found or not provided to --include_print_dependencies_header argument")
41+
INCLUDE_PRINT_DEPENDENCIES_HEADER = True
42+
sys.argv.pop(sys.argv.index("--include_print_dependencies_header") + 1)
43+
sys.argv.remove("--include_print_dependencies_header")
44+
3245
# add user bin to path!
3346
BUILD_DIR = ''
3447
# add user bin to path!
@@ -56,9 +69,6 @@
5669
print("Found MetroX Examples Repo")
5770
IS_LEARNING_SYS = True
5871

59-
#os.system('pwd')
60-
#os.system('ls -lA')
61-
6272
CROSS = u'\N{cross mark}'
6373
CHECK = u'\N{check mark}'
6474

@@ -104,7 +114,7 @@ def manually_install_esp32_bsp(repo_info):
104114
# Assemble git url
105115
repo_url = "git clone -b {0} https://github.com/{1}/arduino-esp32.git esp32".format(repo_info.split("/")[1], repo_info.split("/")[0])
106116
# Locally clone repo (https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html#linux)
107-
os.system("mkdir -p /home/runner/Arduino/hardware/espressif")
117+
subprocess.run("mkdir -p /home/runner/Arduino/hardware/espressif", shell=True, check=True)
108118
print("Cloning %s"%repo_url)
109119
cmd = "cd /home/runner/Arduino/hardware/espressif && " + repo_url
110120
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
@@ -135,10 +145,11 @@ def manually_install_esp32_bsp(repo_info):
135145

136146

137147
def install_platform(fqbn, full_platform_name=None):
148+
print("Checking for drazzy.json (before moving)")
138149
if os.path.exists("/home/runner/.arduino15/package_drazzy.json"):
139150
print("Moving drazzy.json")
140151
shutil.move("/home/runner/.arduino15/package_drazzy.json", "/home/runner/.arduino15/package_drazzy.com_index.json")
141-
print("Installing", fqbn, end=" ")
152+
print("Installing", fqbn, " (", full_platform_name, ")", end=" ")
142153
if fqbn == "adafruit:avr": # we have a platform dep
143154
install_platform("arduino:avr", full_platform_name)
144155
if full_platform_name[2] is not None:
@@ -147,7 +158,7 @@ def install_platform(fqbn, full_platform_name=None):
147158
return # bail out
148159
for retry in range(0, 3):
149160
print("arduino-cli core install "+fqbn+" --additional-urls "+BSP_URLS)
150-
if os.system("arduino-cli core install "+fqbn+" --additional-urls "+BSP_URLS+" > /dev/null") == 0:
161+
if subprocess.run("arduino-cli core install "+fqbn+" --additional-urls "+BSP_URLS+" > /dev/null", shell=True, check=False).returncode == 0:
151162
break
152163
print("...retrying...", end=" ")
153164
time.sleep(10) # wait 10 seconds then try again?
@@ -157,14 +168,16 @@ def install_platform(fqbn, full_platform_name=None):
157168
exit(-1)
158169
ColorPrint.print_pass(CHECK)
159170
# print installed core version
160-
print(os.popen('arduino-cli core list | grep {}'.format(fqbn)).read(), end='')
171+
result = subprocess.Popen('arduino-cli core list | grep {}'.format(fqbn), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
172+
print(result.stdout.read().decode(), end='')
173+
161174

162175

163176
def run_or_die(cmd, error):
164177
print(cmd)
165178
attempt = 0
166179
while attempt < 3:
167-
if os.system(cmd) == 0:
180+
if subprocess.run(cmd, shell=True, check=False).returncode == 0:
168181
return
169182
attempt += 1
170183
print('attempt {} failed, {} retry left'.format(attempt, 3-attempt))
@@ -175,7 +188,7 @@ def run_or_die(cmd, error):
175188

176189
def is_library_installed(lib_name):
177190
try:
178-
installed_libs = subprocess.check_output(["arduino-cli", "lib", "list"]).decode("utf-8")
191+
installed_libs = subprocess.check_output("arduino-cli lib list", shell=True).decode("utf-8")
179192
return not all(not item for item in [re.match('^'+lib_name+'\\s*\\d+\\.', line) for line in installed_libs.split('\n')])
180193
except subprocess.CalledProcessError as e:
181194
print("Error checking installed libraries:", e)
@@ -290,6 +303,7 @@ def generate_uf2(platform, fqbn, example_path):
290303
family_id = ALL_PLATFORMS[platform][1]
291304
cmd = ['python3', 'uf2conv.py', hex_input_file, '-c', '-f', family_id, '-o', output_file]
292305
else:
306+
# ColorPrint.print_info(subprocess.check_output(["tree"]).decode("utf-8"))
293307
cli_build_path = "build/*.*." + fqbn.split(':')[2] + "/*.ino.bin"
294308
input_file = glob1(os.path.join(example_path, cli_build_path))
295309
output_file = os.path.splitext(input_file)[0] + ".uf2"
@@ -329,27 +343,101 @@ def group_output(title):
329343
sys.stdout.flush()
330344

331345

346+
def extract_dependencies(output):
347+
print("Extracting libraries from output:", output)
348+
print(f"{"############## Done #############":-^80}")
349+
350+
IS_LIBS_FOUND = False
351+
IS_BSP_FOUND = False
352+
libraries = []
353+
platforms = []
354+
COLS = []
355+
for i,line in enumerate(output.split('\n')):
356+
if line.strip() == '':
357+
continue
358+
if not IS_LIBS_FOUND:
359+
if re.match(r'Used library', line):
360+
IS_LIBS_FOUND = True
361+
print("Found libraries token using regex")
362+
print("find Version:", line.find('Version'))
363+
print("find Path:", line.find('Path'))
364+
COLS = [0,line.find('Version'), line.find('Path')]
365+
continue
366+
else:
367+
if line.find("Used library") != -1:
368+
print("Found libraries token using find")
369+
IS_LIBS_FOUND = True
370+
print("non regex find Version:", line.find('Version'))
371+
print("find Path:", line.find('Path'))
372+
COLS = [0,line.find('Version'), line.find('Path')]
373+
else:
374+
if not IS_BSP_FOUND:
375+
if re.match(r'Used platform', line):
376+
print("Found platform token using regex")
377+
IS_BSP_FOUND = True
378+
COLS = [0,line.find('Version'), line.find('Path')]
379+
continue
380+
elif line.find("Used platform") != -1:
381+
print("Found platform token using find")
382+
IS_BSP_FOUND = True
383+
COLS = [0,line.find('Version'), line.find('Path')]
384+
continue
385+
else:
386+
libraries.append([line[:COLS[1]].strip(),line[COLS[1]:COLS[2]].strip()])
387+
else:
388+
platforms.append([line[:COLS[1]].strip(),line[COLS[1]:COLS[2]].strip()])
389+
390+
dependencies = {
391+
'libraries': libraries,
392+
'platforms': platforms
393+
}
394+
print("Extracted list of dependencies:", dependencies)
395+
return dependencies
396+
397+
def write_dependencies_to_header(dependencies, output_file):
398+
# header file
399+
with open(output_file, 'w') as f:
400+
f.write('#ifndef PROJECT_DEPENDENCIES_H\n')
401+
f.write('#define PROJECT_DEPENDENCIES_H\n\n')
402+
f.write('#define PRINT_DEPENDENCIES 1\n')
403+
f.write('extern const char* project_dependencies;\n\n')
404+
f.write('#endif // PROJECT_DEPENDENCIES_H\n')
405+
# cpp file
406+
with open(re.sub(r'\.h$','.cpp', output_file), 'w') as f:
407+
f.write('#include "print_dependencies.h"\n\n')
408+
f.write('const char* project_dependencies = R"(\n')
409+
410+
f.write('Libraries and Versions:\n')
411+
for lib in dependencies['libraries']:
412+
f.write(f'Library: {lib[0].strip()}, Version: {lib[1].strip()}\n')
413+
414+
f.write('\nPlatforms and Versions:\n')
415+
for plat in dependencies['platforms']:
416+
f.write(f'Platform: {plat[0].strip()}, Version: {plat[1].strip()}\n')
417+
418+
f.write(')";\n\n')
419+
332420
def test_examples_in_folder(platform, folderpath):
333-
global success
421+
global success, BUILD_TIMEOUT, popen_timeout, BUILD_WALL, BUILD_WARN, PRINT_DEPENDENCIES_AS_HEADER, INCLUDE_PRINT_DEPENDENCIES_HEADER, IS_LEARNING_SYS, BUILD_DIR
334422
fqbn = ALL_PLATFORMS[platform][0]
335423
for example in sorted(os.listdir(folderpath)):
336-
examplepath = folderpath+"/"+example
424+
examplepath = folderpath + "/" + example
337425
if os.path.isdir(examplepath):
338426
test_examples_in_folder(platform, examplepath)
339427
continue
340428
if not examplepath.endswith(".ino"):
341429
continue
342430

343-
print('\t'+example, end=' ')
431+
print('\t' + example, end=' ')
344432

345433
# check if we should SKIP
346-
skipfilename = folderpath+"/."+platform+".test.skip"
347-
onlyfilename = folderpath+"/."+platform+".test.only"
434+
skipfilename = folderpath + "/." + platform + ".test.skip"
435+
onlyfilename = folderpath + "/." + platform + ".test.only"
348436
# check if we should GENERATE UF2
349-
gen_file_name = folderpath+"/."+platform+".generate"
437+
gen_file_name = folderpath + "/." + platform + ".generate"
350438

351439
# .skip txt include all skipped platforms, one per line
352-
skip_txt = folderpath+"/.skip.txt"
440+
skip_txt = folderpath + "/.skip.txt"
353441

354442
is_skip = False
355443
if os.path.exists(skipfilename):
@@ -365,10 +453,10 @@ def test_examples_in_folder(platform, folderpath):
365453
ColorPrint.print_warn("skipping")
366454
continue
367455

368-
if glob.glob(folderpath+"/.*.test.only"):
369-
platformname = glob.glob(folderpath+"/.*.test.only")[0].split('.')[1]
370-
if platformname != "none" and not platformname in ALL_PLATFORMS:
371-
# uh oh, this isnt a valid testonly!
456+
if glob.glob(folderpath + "/.*.test.only"):
457+
platformname = glob.glob(folderpath + "/.*.test.only")[0].split('.')[1]
458+
if platformname != "none" and platformname not in ALL_PLATFORMS:
459+
# uh oh, this isn't a valid testonly!
372460
ColorPrint.print_fail(CROSS)
373461
ColorPrint.print_fail("This example does not have a valid .platform.test.only file")
374462
success = 1
@@ -386,8 +474,16 @@ def test_examples_in_folder(platform, folderpath):
386474
cmd = ['arduino-cli', 'compile', '--warnings', 'all', '--fqbn', fqbn, folderpath]
387475
else:
388476
cmd = ['arduino-cli', 'compile', '--warnings', 'none', '--export-binaries', '--fqbn', fqbn, folderpath]
389-
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
390-
stderr=subprocess.PIPE)
477+
478+
if PRINT_DEPENDENCIES_AS_HEADER:
479+
cmd.append('--only-compilation-database')
480+
cmd.append('--no-color')
481+
elif INCLUDE_PRINT_DEPENDENCIES_HEADER:
482+
cmd.append('--build-property')
483+
cmd.append('"build.extra_flags=\'-DPRINT_DEPENDENCIES -I\"' + os.path.join(BUILD_DIR, "print_dependencies.cpp") + '\"\'"')
484+
cmd.append('--verbose')
485+
486+
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
391487
try:
392488
if BUILD_TIMEOUT:
393489
out, err = proc.communicate(timeout=popen_timeout)
@@ -405,7 +501,11 @@ def test_examples_in_folder(platform, folderpath):
405501
# also print out warning message
406502
with group_output(f"{example} {fqbn} build output"):
407503
ColorPrint.print_fail(err.decode("utf-8"))
408-
if os.path.exists(gen_file_name):
504+
if PRINT_DEPENDENCIES_AS_HEADER:
505+
# Extract dependencies and write to header for the first successful example
506+
dependencies = extract_dependencies(out.decode("utf-8") + err.decode("utf-8"))
507+
write_dependencies_to_header(dependencies, PRINT_DEPENDENCIES_AS_HEADER_FILENAME)
508+
elif os.path.exists(gen_file_name):
409509
if ALL_PLATFORMS[platform][1] is None:
410510
ColorPrint.print_info("Platform does not support UF2 files, skipping...")
411511
else:
@@ -415,10 +515,10 @@ def test_examples_in_folder(platform, folderpath):
415515
success = 1 # failure
416516
if IS_LEARNING_SYS:
417517
fqbnpath, uf2file = filename.split("/")[-2:]
418-
os.makedirs(BUILD_DIR+"/build", exist_ok=True)
419-
os.makedirs(BUILD_DIR+"/build/"+fqbnpath, exist_ok=True)
420-
shutil.copy(filename, BUILD_DIR+"/build/"+fqbnpath+"-"+uf2file)
421-
os.system("ls -lR "+BUILD_DIR+"/build")
518+
os.makedirs(BUILD_DIR + "/build", exist_ok=True)
519+
os.makedirs(BUILD_DIR + "/build/" + fqbnpath, exist_ok=True)
520+
shutil.copy(filename, BUILD_DIR + "/build/" + fqbnpath + "-" + uf2file)
521+
subprocess.run("ls -lR " + BUILD_DIR + "/build", shell=True, check=True)
422522
else:
423523
ColorPrint.print_fail(CROSS)
424524
with group_output(f"{example} {fqbn} built output"):
@@ -428,6 +528,7 @@ def test_examples_in_folder(platform, folderpath):
428528

429529

430530
def main():
531+
global INCLUDE_PRINT_DEPENDENCIES_HEADER, PRINT_DEPENDENCIES_AS_HEADER
431532
# Test platforms
432533
platforms = []
433534

@@ -449,15 +550,22 @@ def main():
449550
for platform in platforms:
450551
fqbn = ALL_PLATFORMS[platform][0]
451552
print('#'*80)
452-
ColorPrint.print_info("SWITCHING TO "+fqbn)
553+
ColorPrint.print_info("SWITCHING TO " + fqbn)
453554
install_platform(":".join(fqbn.split(':', 2)[0:2]), ALL_PLATFORMS[platform]) # take only first two elements
454555
print('#'*80)
455556
if not IS_LEARNING_SYS:
456-
test_examples_in_folder(platform, BUILD_DIR+"/examples")
557+
if INCLUDE_PRINT_DEPENDENCIES_HEADER:
558+
PRINT_DEPENDENCIES_AS_HEADER = True
559+
INCLUDE_PRINT_DEPENDENCIES_HEADER = False
560+
test_examples_in_folder(platform, BUILD_DIR + "/examples")
561+
PRINT_DEPENDENCIES_AS_HEADER = False
562+
INCLUDE_PRINT_DEPENDENCIES_HEADER = True
563+
test_examples_in_folder(platform, BUILD_DIR + "/examples")
564+
else:
565+
test_examples_in_folder(platform, BUILD_DIR + "/examples")
457566
else:
458567
test_examples_in_folder(platform, BUILD_DIR)
459568

460-
461569
if __name__ == "__main__":
462570
main()
463571
exit(success)

0 commit comments

Comments
 (0)