2021-10-07 00:43:44 -03:00
#!/usr/bin/env python3
'''
Wrapper around elf_diff ( https : / / github . com / noseglasses / elf_diff )
to create a html report comparing an ArduPilot build across two
branches
pip3 install - - user elf_diff weasyprint
AP_FLAKE8_CLEAN
2021-11-04 17:33:27 -03:00
How to use ?
Starting in the ardupilot directory .
~ / ardupilot $ python Tools / scripts / size_compare_branches . py - - branch = [ PR_BRANCH_NAME ] - - vehicle = copter
Output is placed into . . / ELF_DIFF_ [ VEHICLE_NAME ]
2021-10-07 00:43:44 -03:00
'''
2023-01-22 19:42:47 -04:00
import copy
2023-06-07 20:35:46 -03:00
import fnmatch
2021-10-07 00:43:44 -03:00
import optparse
import os
2023-05-30 01:48:05 -03:00
import pathlib
2024-11-07 22:34:05 -04:00
import queue
2021-10-07 00:43:44 -03:00
import shutil
import string
import subprocess
2022-11-01 20:36:20 -03:00
import tempfile
2023-05-07 07:48:45 -03:00
import threading
2021-10-07 00:43:44 -03:00
import time
2022-11-01 20:36:20 -03:00
import board_list
2021-10-07 00:43:44 -03:00
2022-11-01 20:36:20 -03:00
class SizeCompareBranchesResult ( object ) :
''' object to return results from a comparison '''
2023-01-25 18:06:13 -04:00
def __init__ ( self , board , vehicle , bytes_delta , identical ) :
2022-11-01 20:36:20 -03:00
self . board = board
self . vehicle = vehicle
self . bytes_delta = bytes_delta
2023-01-25 18:06:13 -04:00
self . identical = identical
2022-11-01 20:36:20 -03:00
2021-10-07 00:43:44 -03:00
class SizeCompareBranches ( object ) :
''' script to build and compare branches using elf_diff '''
2022-08-11 22:03:30 -03:00
def __init__ ( self ,
branch = None ,
master_branch = " master " ,
2022-11-01 20:36:20 -03:00
board = [ " MatekF405-Wing " ] ,
vehicle = [ " plane " ] ,
2022-08-11 22:03:30 -03:00
bin_dir = None ,
2022-11-01 20:36:20 -03:00
run_elf_diff = True ,
all_vehicles = False ,
2023-06-07 20:35:46 -03:00
exclude_board_glob = [ ] ,
2022-11-01 20:36:20 -03:00
all_boards = False ,
use_merge_base = True ,
2023-02-15 07:20:52 -04:00
waf_consistent_builds = True ,
2023-03-12 21:01:54 -03:00
show_empty = True ,
2023-05-10 22:38:56 -03:00
show_unchanged = True ,
2023-02-06 23:43:18 -04:00
extra_hwdef = [ ] ,
extra_hwdef_branch = [ ] ,
2023-05-07 07:48:45 -03:00
extra_hwdef_master = [ ] ,
parallel_copies = None ,
jobs = None ) :
2021-10-07 00:43:44 -03:00
if branch is None :
2023-04-15 00:15:02 -03:00
branch = self . find_current_git_branch_or_sha1 ( )
2022-11-01 20:36:20 -03:00
2021-10-07 00:43:44 -03:00
self . master_branch = master_branch
self . branch = branch
self . board = board
2021-11-04 00:15:24 -03:00
self . vehicle = vehicle
2021-10-07 00:43:44 -03:00
self . bin_dir = bin_dir
2022-11-01 20:36:20 -03:00
self . run_elf_diff = run_elf_diff
2022-08-11 22:03:30 -03:00
self . extra_hwdef = extra_hwdef
2023-02-06 23:43:18 -04:00
self . extra_hwdef_branch = extra_hwdef_branch
self . extra_hwdef_master = extra_hwdef_master
2022-11-01 20:36:20 -03:00
self . all_vehicles = all_vehicles
self . all_boards = all_boards
self . use_merge_base = use_merge_base
2023-02-15 07:20:52 -04:00
self . waf_consistent_builds = waf_consistent_builds
2023-03-12 21:01:54 -03:00
self . show_empty = show_empty
2023-05-10 22:38:56 -03:00
self . show_unchanged = show_unchanged
2023-05-07 07:48:45 -03:00
self . parallel_copies = parallel_copies
self . jobs = jobs
2021-10-07 00:43:44 -03:00
if self . bin_dir is None :
self . bin_dir = self . find_bin_dir ( )
2022-11-01 20:36:20 -03:00
self . boards_by_name = { }
for board in board_list . BoardList ( ) . boards :
self . boards_by_name [ board . name ] = board
# map from vehicle names to binary names
self . vehicle_map = {
" rover " : " ardurover " ,
" copter " : " arducopter " ,
" plane " : " arduplane " ,
" sub " : " ardusub " ,
" heli " : " arducopter-heli " ,
" blimp " : " blimp " ,
" antennatracker " : " antennatracker " ,
" AP_Periph " : " AP_Periph " ,
2023-01-22 19:42:47 -04:00
" bootloader " : " AP_Bootloader " ,
2022-11-05 20:49:03 -03:00
" iofirmware " : " iofirmware_highpolh " , # FIXME: lowpolh?
2022-11-01 20:36:20 -03:00
}
if all_boards :
self . board = sorted ( list ( self . boards_by_name . keys ( ) ) , key = lambda x : x . lower ( ) )
else :
# validate boards
all_boards = set ( self . boards_by_name . keys ( ) )
for b in self . board :
if b not in all_boards :
raise ValueError ( " Bad board %s " % str ( b ) )
if all_vehicles :
self . vehicle = sorted ( list ( self . vehicle_map . keys ( ) ) , key = lambda x : x . lower ( ) )
else :
for v in self . vehicle :
if v not in self . vehicle_map . keys ( ) :
raise ValueError ( " Bad vehicle ( %s ); choose from %s " % ( v , " , " . join ( self . vehicle_map . keys ( ) ) ) )
2023-06-07 20:35:46 -03:00
# remove boards based on --exclude-board-glob
new_self_board = [ ]
2023-06-08 07:39:08 -03:00
for board_name in self . board :
exclude = False
for exclude_glob in exclude_board_glob :
2023-06-07 20:35:46 -03:00
if fnmatch . fnmatch ( board_name , exclude_glob ) :
2023-06-08 07:39:08 -03:00
exclude = True
break
if not exclude :
2023-06-07 20:35:46 -03:00
new_self_board . append ( board_name )
self . board = new_self_board
2023-01-24 16:56:46 -04:00
# some boards we don't have a -bl.dat for, so skip them.
# TODO: find a way to get this information from board_list:
2023-02-15 10:04:49 -04:00
self . bootloader_blacklist = set ( [
' CubeOrange-SimOnHardWare ' ,
2023-03-28 07:26:55 -03:00
' CubeOrangePlus-SimOnHardWare ' ,
2023-02-15 10:04:49 -04:00
' fmuv2 ' ,
' fmuv3-bdshot ' ,
' iomcu ' ,
2023-09-02 21:57:43 -03:00
' iomcu-dshot ' ,
2023-09-04 02:54:58 -03:00
' iomcu-f103 ' ,
2023-09-02 21:57:43 -03:00
' iomcu-f103-dshot ' ,
2024-05-27 17:04:25 -03:00
' iomcu-f103-8MHz-dshot ' ,
2023-02-15 10:04:49 -04:00
' iomcu_f103_8MHz ' ,
' luminousbee4 ' ,
' skyviper-v2450 ' ,
' skyviper-f412-rev1 ' ,
' skyviper-journey ' ,
' Pixhawk1-1M-bdshot ' ,
2023-06-08 04:19:00 -03:00
' Pixhawk1-bdshot ' ,
2023-02-15 10:04:49 -04:00
' SITL_arm_linux_gnueabihf ' ,
2023-08-01 00:09:07 -03:00
' RADIX2HD ' ,
2023-12-12 22:44:03 -04:00
' canzero ' ,
2024-09-04 00:07:14 -03:00
' CUAV-Pixhack-v3 ' , # uses USE_BOOTLOADER_FROM_BOARD
2023-01-24 16:56:46 -04:00
] )
2023-02-15 10:04:49 -04:00
# blacklist all linux boards for bootloader build:
self . bootloader_blacklist . update ( self . linux_board_names ( ) )
# ... and esp32 boards:
self . bootloader_blacklist . update ( self . esp32_board_names ( ) )
def linux_board_names ( self ) :
''' return a list of all Linux board names; FIXME: get this dynamically '''
# grep 'class.*[(]linux' Tools/ardupilotwaf/boards.py | perl -pe "s/class (.*)\(linux\).*/ '\\1',/"
return [
' navigator ' ,
' erleboard ' ,
' navio ' ,
' navio2 ' ,
' edge ' ,
' zynq ' ,
' ocpoc_zynq ' ,
' bbbmini ' ,
' blue ' ,
' pocket ' ,
' pxf ' ,
' bebop ' ,
' vnav ' ,
' disco ' ,
' erlebrain2 ' ,
' bhat ' ,
' dark ' ,
' pxfmini ' ,
' aero ' ,
' rst_zynq ' ,
' obal ' ,
' SITL_x86_64_linux_gnu ' ,
2023-12-12 22:44:03 -04:00
' canzero ' ,
2023-02-15 10:04:49 -04:00
]
def esp32_board_names ( self ) :
return [
' esp32buzz ' ,
' esp32empty ' ,
2023-05-01 20:27:32 -03:00
' esp32tomte76 ' ,
2023-05-08 11:38:08 -03:00
' esp32nick ' ,
2023-05-15 13:50:33 -03:00
' esp32s3devkit ' ,
2023-12-29 02:22:05 -04:00
' esp32s3empty ' ,
2023-02-15 10:04:49 -04:00
' esp32icarous ' ,
' esp32diy ' ,
]
2021-10-07 00:43:44 -03:00
def find_bin_dir ( self ) :
''' attempt to find where the arm-none-eabi tools are '''
binary = shutil . which ( " arm-none-eabi-g++ " )
if binary is None :
raise Exception ( " No arm-none-eabi-g++? " )
return os . path . dirname ( binary )
# vast amounts of stuff copied into here from build_binaries.py
2023-05-07 07:48:45 -03:00
def run_program ( self , prefix , cmd_list , show_output = True , env = None , show_output_on_error = True , show_command = None , cwd = " . " ) :
if show_command is None :
show_command = True
if show_command :
cmd = " " . join ( cmd_list )
if cwd is None :
cwd = " . "
self . progress ( f " Running ( { cmd } ) in ( { cwd } ) " )
2022-11-02 00:09:30 -03:00
p = subprocess . Popen (
cmd_list ,
stdin = None ,
stdout = subprocess . PIPE ,
close_fds = True ,
stderr = subprocess . STDOUT ,
2023-05-07 07:48:45 -03:00
cwd = cwd ,
2022-11-02 00:09:30 -03:00
env = env )
2021-10-07 00:43:44 -03:00
output = " "
while True :
x = p . stdout . readline ( )
if len ( x ) == 0 :
returncode = os . waitpid ( p . pid , 0 )
if returncode :
break
# select not available on Windows... probably...
time . sleep ( 0.1 )
continue
2024-11-07 22:34:05 -04:00
x = bytearray ( x )
x = filter ( lambda x : chr ( x ) in string . printable , x )
x = " " . join ( [ chr ( c ) for c in x ] )
2021-10-07 00:43:44 -03:00
output + = x
x = x . rstrip ( )
2023-05-07 07:48:45 -03:00
some_output = " %s : %s " % ( prefix , x )
2021-10-07 00:43:44 -03:00
if show_output :
2023-05-07 07:48:45 -03:00
print ( some_output )
else :
output + = some_output
2021-10-07 00:43:44 -03:00
( _ , status ) = returncode
2023-02-08 23:45:12 -04:00
if status != 0 :
2023-05-07 07:48:45 -03:00
if not show_output and show_output_on_error :
# we were told not to show output, but we just
# failed... so show output...
print ( output )
2021-10-07 00:43:44 -03:00
self . progress ( " Process failed ( %s ) " %
str ( returncode ) )
2024-01-11 19:22:47 -04:00
try :
path = pathlib . Path ( self . tmpdir , f " process-failure- { int ( time . time ( ) ) } " )
path . write_text ( output )
self . progress ( " Wrote process failure file ( %s ) " % path )
except Exception :
self . progress ( " Writing process failure file failed " )
2021-10-07 00:43:44 -03:00
raise subprocess . CalledProcessError (
returncode , cmd_list )
return output
2023-04-15 00:15:02 -03:00
def find_current_git_branch_or_sha1 ( self ) :
try :
output = self . run_git ( [ " symbolic-ref " , " --short " , " HEAD " ] )
output = output . strip ( )
return output
except subprocess . CalledProcessError :
pass
# probably in a detached-head state. Get a sha1 instead:
output = self . run_git ( [ " rev-parse " , " --short " , " HEAD " ] )
2022-11-01 20:36:20 -03:00
output = output . strip ( )
return output
def find_git_branch_merge_base ( self , branch , master_branch ) :
output = self . run_git ( [ " merge-base " , branch , master_branch ] )
output = output . strip ( )
return output
2023-05-07 07:48:45 -03:00
def run_git ( self , args , show_output = True , source_dir = None ) :
2021-10-07 00:43:44 -03:00
''' run git with args git_args; returns git ' s output '''
cmd_list = [ " git " ]
cmd_list . extend ( args )
2023-05-07 07:48:45 -03:00
return self . run_program ( " SCB-GIT " , cmd_list , show_output = show_output , cwd = source_dir )
2021-10-07 00:43:44 -03:00
2023-05-07 07:48:45 -03:00
def run_waf ( self , args , compiler = None , show_output = True , source_dir = None ) :
2022-11-04 04:23:23 -03:00
# try to modify the environment so we can consistent builds:
consistent_build_envs = {
" CHIBIOS_GIT_VERSION " : " 12345678 " ,
" GIT_VERSION " : " abcdef " ,
" GIT_VERSION_INT " : " 15 " ,
}
for ( n , v ) in consistent_build_envs . items ( ) :
os . environ [ n ] = v
2021-10-07 00:43:44 -03:00
if os . path . exists ( " waf " ) :
waf = " ./waf "
else :
waf = os . path . join ( " . " , " modules " , " waf " , " waf-light " )
cmd_list = [ waf ]
cmd_list . extend ( args )
env = None
if compiler is not None :
# default to $HOME/arm-gcc, but allow for any path with AP_GCC_HOME environment variable
gcc_home = os . environ . get ( " AP_GCC_HOME " , os . path . join ( os . environ [ " HOME " ] , " arm-gcc " ) )
gcc_path = os . path . join ( gcc_home , compiler , " bin " )
if os . path . exists ( gcc_path ) :
# setup PATH to point at the right compiler, and setup to use ccache
env = os . environ . copy ( )
env [ " PATH " ] = gcc_path + " : " + env [ " PATH " ]
env [ " CC " ] = " ccache arm-none-eabi-gcc "
env [ " CXX " ] = " ccache arm-none-eabi-g++ "
else :
raise Exception ( " BB-WAF: Missing compiler %s " % gcc_path )
2023-05-07 07:48:45 -03:00
self . run_program ( " SCB-WAF " , cmd_list , env = env , show_output = show_output , cwd = source_dir )
2021-10-07 00:43:44 -03:00
def progress ( self , string ) :
''' pretty-print progress '''
print ( " SCB: %s " % string )
2023-05-07 07:48:45 -03:00
def build_branch_into_dir ( self , board , branch , vehicle , outdir , source_dir = None , extra_hwdef = None , jobs = None ) :
self . run_git ( [ " checkout " , branch ] , show_output = False , source_dir = source_dir )
self . run_git ( [ " submodule " , " update " , " --recursive " ] , show_output = False , source_dir = source_dir )
build_dir = " build "
if source_dir is not None :
build_dir = os . path . join ( source_dir , " build " )
shutil . rmtree ( build_dir , ignore_errors = True )
2022-08-11 22:03:30 -03:00
waf_configure_args = [ " configure " , " --board " , board ]
2023-02-15 07:20:52 -04:00
if self . waf_consistent_builds :
waf_configure_args . append ( " --consistent-builds " )
2023-02-06 23:43:18 -04:00
if extra_hwdef is not None :
waf_configure_args . extend ( [ " --extra-hwdef " , extra_hwdef ] )
2023-05-30 01:48:05 -03:00
if self . run_elf_diff :
waf_configure_args . extend ( [ " --debug-symbols " ] )
2023-05-07 07:48:45 -03:00
if jobs is None :
jobs = self . jobs
if jobs is not None :
waf_configure_args . extend ( [ " -j " , str ( jobs ) ] )
self . run_waf ( waf_configure_args , show_output = False , source_dir = source_dir )
2022-11-01 20:36:20 -03:00
# we can't run `./waf copter blimp plane` without error, so do
# them one-at-a-time:
for v in vehicle :
2023-01-22 19:42:47 -04:00
if v == ' bootloader ' :
# need special configuration directive
continue
2023-05-07 07:48:45 -03:00
self . run_waf ( [ v ] , show_output = False , source_dir = source_dir )
2023-01-22 19:42:47 -04:00
for v in vehicle :
if v != ' bootloader ' :
continue
2023-01-24 16:56:46 -04:00
if board in self . bootloader_blacklist :
continue
2023-01-22 19:42:47 -04:00
# need special configuration directive
bootloader_waf_configure_args = copy . copy ( waf_configure_args )
bootloader_waf_configure_args . append ( ' --bootloader ' )
2023-04-04 08:19:27 -03:00
# hopefully temporary hack so you can build bootloader
# after building other vehicles without a clean:
dsdl_generated_path = os . path . join ( ' build ' , board , " modules " , " DroneCAN " , " libcanard " , " dsdlc_generated " )
self . progress ( " HACK: Removing ( %s ) " % dsdl_generated_path )
2023-05-07 07:48:45 -03:00
if source_dir is not None :
dsdl_generated_path = os . path . join ( source_dir , dsdl_generated_path )
2023-04-04 08:19:27 -03:00
shutil . rmtree ( dsdl_generated_path , ignore_errors = True )
2023-05-07 07:48:45 -03:00
self . run_waf ( bootloader_waf_configure_args , show_output = False , source_dir = source_dir )
self . run_waf ( [ v ] , show_output = False , source_dir = source_dir )
self . run_program ( " rsync " , [ " rsync " , " -ap " , " build/ " , outdir ] , cwd = source_dir )
2023-05-30 01:48:05 -03:00
if source_dir is not None :
pathlib . Path ( outdir , " scb_sourcepath.txt " ) . write_text ( source_dir )
2023-05-07 07:48:45 -03:00
def vehicles_to_build_for_board_info ( self , board_info ) :
vehicles_to_build = [ ]
for vehicle in self . vehicle :
if vehicle == ' AP_Periph ' :
if not board_info . is_ap_periph :
continue
else :
if board_info . is_ap_periph :
continue
# the bootloader target isn't an autobuild target, so
# it gets special treatment here:
if vehicle != ' bootloader ' and vehicle . lower ( ) not in [ x . lower ( ) for x in board_info . autobuild_targets ] :
continue
vehicles_to_build . append ( vehicle )
return vehicles_to_build
def parallel_thread_main ( self , thread_number ) :
# initialisation; make a copy of the source directory
my_source_dir = os . path . join ( self . tmpdir , f " thread- { thread_number } -source " )
self . run_program ( " rsync " , [
" rsync " ,
" --exclude=build/ " ,
" -ap " ,
" ./ " ,
my_source_dir
] )
while True :
try :
task = self . parallel_tasks . pop ( 0 )
except IndexError :
break
jobs = None
if self . jobs is not None :
2024-01-11 19:12:16 -04:00
jobs = int ( self . jobs / self . n_threads )
2023-05-07 07:48:45 -03:00
if jobs < = 0 :
jobs = 1
2023-08-10 21:03:58 -03:00
try :
self . run_build_task ( task , source_dir = my_source_dir , jobs = jobs )
except Exception as ex :
self . thread_exit_result_queue . put ( f " { task } " )
raise ex
def check_result_queue ( self ) :
while True :
try :
result = self . thread_exit_result_queue . get_nowait ( )
2024-11-07 22:34:05 -04:00
except queue . Empty :
2023-08-10 21:03:58 -03:00
break
if result is None :
continue
self . failure_exceptions . append ( result )
2023-05-07 07:48:45 -03:00
def run_build_tasks_in_parallel ( self , tasks ) :
2024-01-11 19:12:16 -04:00
self . n_threads = self . parallel_copies
2023-05-07 07:48:45 -03:00
# shared list for the threads:
self . parallel_tasks = copy . copy ( tasks ) # make this an argument instead?!
threads = [ ]
2024-11-07 22:34:05 -04:00
self . thread_exit_result_queue = queue . Queue ( )
2023-05-07 07:48:45 -03:00
tstart = time . time ( )
2023-08-10 21:03:58 -03:00
self . failure_exceptions = [ ]
2024-01-11 19:12:16 -04:00
thread_number = 0
while len ( self . parallel_tasks ) or len ( threads ) :
2024-01-31 20:18:31 -04:00
if len ( self . parallel_tasks ) < self . n_threads :
self . n_threads = len ( self . parallel_tasks )
2024-01-11 19:12:16 -04:00
while len ( threads ) < self . n_threads :
self . progress ( f " Starting thread { thread_number } " )
t = threading . Thread (
target = self . parallel_thread_main ,
name = f ' task-builder- { thread_number } ' ,
args = [ thread_number ] ,
)
t . start ( )
threads . append ( t )
thread_number + = 1
2023-08-10 21:03:58 -03:00
self . check_result_queue ( )
2023-05-07 07:48:45 -03:00
new_threads = [ ]
for thread in threads :
thread . join ( 0 )
if thread . is_alive ( ) :
new_threads . append ( thread )
threads = new_threads
2023-08-10 21:03:58 -03:00
self . progress (
f " remaining-tasks= { len ( self . parallel_tasks ) } " +
2024-01-11 19:12:16 -04:00
f " failed-threads= { len ( self . failure_exceptions ) } elapsed= { int ( time . time ( ) - tstart ) } s " ) # noqa
2023-05-07 07:48:45 -03:00
# write out a progress CSV:
task_results = [ ]
for task in tasks :
task_results . append ( self . gather_results_for_task ( task ) )
# progress CSV:
2024-03-22 02:59:39 -03:00
csv_for_results = self . csv_for_results ( self . compare_task_results ( task_results , no_elf_diff = True ) )
path = pathlib . Path ( " /tmp/some.csv " )
path . write_text ( csv_for_results )
2023-05-07 07:48:45 -03:00
time . sleep ( 1 )
self . progress ( " All threads returned " )
2022-11-01 20:36:20 -03:00
2023-08-10 21:03:58 -03:00
self . check_result_queue ( )
if len ( self . failure_exceptions ) :
self . progress ( " Some threads failed: " )
for ex in self . failure_exceptions :
print ( " Thread failure: %s " % str ( ex ) )
2022-11-01 20:36:20 -03:00
def run_all ( self ) :
''' run tests for boards and vehicles passed in constructor '''
2023-05-07 07:48:45 -03:00
tmpdir = tempfile . mkdtemp ( )
self . tmpdir = tmpdir
self . master_commit = self . master_branch
if self . use_merge_base :
self . master_commit = self . find_git_branch_merge_base ( self . branch , self . master_branch )
self . progress ( " Using merge base ( %s ) " % self . master_commit )
# create an array of tasks to run:
tasks = [ ]
2022-11-01 20:36:20 -03:00
for board in self . board :
2023-05-07 07:48:45 -03:00
board_info = self . boards_by_name [ board ]
vehicles_to_build = self . vehicles_to_build_for_board_info ( board_info )
outdir_1 = os . path . join ( tmpdir , " out-master- %s " % ( board , ) )
tasks . append ( ( board , self . master_commit , outdir_1 , vehicles_to_build , self . extra_hwdef_master ) )
outdir_2 = os . path . join ( tmpdir , " out-branch- %s " % ( board , ) )
tasks . append ( ( board , self . branch , outdir_2 , vehicles_to_build , self . extra_hwdef_branch ) )
2023-05-30 01:48:05 -03:00
self . tasks = tasks
2023-05-07 07:48:45 -03:00
if self . parallel_copies is not None :
self . run_build_tasks_in_parallel ( tasks )
task_results = [ ]
for task in tasks :
task_results . append ( self . gather_results_for_task ( task ) )
else :
# traditional build everything in sequence:
task_results = [ ]
for task in tasks :
self . run_build_task ( task )
task_results . append ( self . gather_results_for_task ( task ) )
# progress CSV:
with open ( " /tmp/some.csv " , " w " ) as f :
f . write ( self . csv_for_results ( self . compare_task_results ( task_results , no_elf_diff = True ) ) )
return self . compare_task_results ( task_results )
def elf_diff_results ( self , result_master , result_branch ) :
master_branch = result_master [ " branch " ]
2023-05-30 01:33:17 -03:00
branch = result_branch [ " branch " ]
2023-05-07 07:48:45 -03:00
for vehicle in result_master [ " vehicle " ] . keys ( ) :
elf_filename = result_master [ " vehicle " ] [ vehicle ] [ " elf_filename " ]
master_elf_dir = result_master [ " vehicle " ] [ vehicle ] [ " elf_dir " ]
new_elf_dir = result_branch [ " vehicle " ] [ vehicle ] [ " elf_dir " ]
board = result_master [ " board " ]
self . progress ( " Starting compare (~10 minutes!) " )
elf_diff_commandline = [
" time " ,
" python3 " ,
" -m " , " elf_diff " ,
" --bin_dir " , self . bin_dir ,
' --bin_prefix=arm-none-eabi- ' ,
" --old_alias " , " %s %s " % ( master_branch , elf_filename ) ,
" --new_alias " , " %s %s " % ( branch , elf_filename ) ,
" --html_dir " , " ../ELF_DIFF_ %s _ %s " % ( board , vehicle ) ,
2023-05-30 01:48:05 -03:00
]
try :
master_source_prefix = result_master [ " vehicle " ] [ vehicle ] [ " source_path " ]
branch_source_prefix = result_branch [ " vehicle " ] [ vehicle ] [ " source_path " ]
elf_diff_commandline . extend ( [
" --old_source_prefix " , master_source_prefix ,
" --new_source_prefix " , branch_source_prefix ,
] )
except KeyError :
pass
elf_diff_commandline . extend ( [
2023-05-07 07:48:45 -03:00
os . path . join ( master_elf_dir , elf_filename ) ,
os . path . join ( new_elf_dir , elf_filename )
2023-05-30 01:48:05 -03:00
] )
2023-05-07 07:48:45 -03:00
self . run_program ( " SCB " , elf_diff_commandline )
def compare_task_results ( self , task_results , no_elf_diff = False ) :
# pair off results, master and branch:
pairs = { }
for res in task_results :
board = res [ " board " ]
if board not in pairs :
pairs [ board ] = { }
if res [ " branch " ] == self . master_commit :
pairs [ board ] [ " master " ] = res
elif res [ " branch " ] == self . branch :
pairs [ board ] [ " branch " ] = res
else :
raise ValueError ( res [ " branch " ] )
results = { }
for pair in pairs . values ( ) :
if " master " not in pair or " branch " not in pair :
# probably incomplete:
continue
master = pair [ " master " ]
board = master [ " board " ]
try :
results [ board ] = self . compare_results ( master , pair [ " branch " ] )
2023-05-12 23:03:02 -03:00
if self . run_elf_diff and not no_elf_diff :
2023-05-07 07:48:45 -03:00
self . elf_diff_results ( master , pair [ " branch " ] )
except FileNotFoundError :
pass
2022-11-01 20:36:20 -03:00
return results
def emit_csv_for_results ( self , results ) :
''' emit dictionary of dictionaries as a CSV '''
print ( self . csv_for_results ( results ) )
def csv_for_results ( self , results ) :
''' return a string with csv for results '''
boards = sorted ( results . keys ( ) )
all_vehicles = set ( )
for board in boards :
all_vehicles . update ( list ( results [ board ] . keys ( ) ) )
sorted_all_vehicles = sorted ( list ( all_vehicles ) )
ret = " "
ret + = " , " . join ( [ " Board " ] + sorted_all_vehicles ) + " \n "
for board in boards :
line = [ board ]
board_results = results [ board ]
for vehicle in sorted_all_vehicles :
bytes_delta = " "
if vehicle in board_results :
result = board_results [ vehicle ]
2023-01-25 18:06:13 -04:00
if result . identical :
bytes_delta = " * "
else :
bytes_delta = result . bytes_delta
2022-11-01 20:36:20 -03:00
line . append ( str ( bytes_delta ) )
2023-03-12 21:01:54 -03:00
# do not add to ret value if we're not showing empty results:
if not self . show_empty :
if len ( list ( filter ( lambda x : x != " " , line [ 1 : ] ) ) ) == 0 :
continue
2023-05-10 22:38:56 -03:00
# do not add to ret value if all output binaries are identical:
if not self . show_unchanged :
starcount = len ( list ( filter ( lambda x : x == " * " , line [ 1 : ] ) ) )
if len ( line [ 1 : ] ) == starcount :
continue
2022-11-01 20:36:20 -03:00
ret + = " , " . join ( line ) + " \n "
return ret
2021-10-07 00:43:44 -03:00
def run ( self ) :
2022-11-01 20:36:20 -03:00
results = self . run_all ( )
self . emit_csv_for_results ( results )
2021-10-07 00:43:44 -03:00
2023-01-25 18:06:13 -04:00
def files_are_identical ( self , file1 , file2 ) :
''' returns true if the files have the same content '''
return open ( file1 , " rb " ) . read ( ) == open ( file2 , " rb " ) . read ( )
2023-02-06 23:43:18 -04:00
def extra_hwdef_file ( self , more ) :
# create a combined list of hwdefs:
extra_hwdefs = [ ]
extra_hwdefs . extend ( self . extra_hwdef )
extra_hwdefs . extend ( more )
extra_hwdefs = list ( filter ( lambda x : x is not None , extra_hwdefs ) )
if len ( extra_hwdefs ) == 0 :
return None
# slurp all content into a variable:
content = bytearray ( )
for extra_hwdef in extra_hwdefs :
with open ( extra_hwdef , " r+b " ) as f :
content + = f . read ( )
# spew content to single file:
f = tempfile . NamedTemporaryFile ( delete = False )
f . write ( content )
f . close ( )
return f . name
2023-05-07 07:48:45 -03:00
def run_build_task ( self , task , source_dir = None , jobs = None ) :
( board , commitish , outdir , vehicles_to_build , extra_hwdef_file ) = task
2023-02-06 23:43:18 -04:00
2023-05-07 07:48:45 -03:00
self . progress ( f " Building { task } " )
shutil . rmtree ( outdir , ignore_errors = True )
2023-02-06 23:43:18 -04:00
self . build_branch_into_dir (
board ,
2023-05-07 07:48:45 -03:00
commitish ,
2023-02-06 23:43:18 -04:00
vehicles_to_build ,
2023-05-07 07:48:45 -03:00
outdir ,
source_dir = source_dir ,
extra_hwdef = self . extra_hwdef_file ( extra_hwdef_file ) ,
jobs = jobs ,
2023-02-06 23:43:18 -04:00
)
2022-11-01 20:36:20 -03:00
2023-05-07 07:48:45 -03:00
def gather_results_for_task ( self , task ) :
( board , commitish , outdir , vehicles_to_build , extra_hwdef_file ) = task
result = {
" board " : board ,
" branch " : commitish ,
" vehicle " : { } ,
}
2022-11-01 20:36:20 -03:00
2023-05-30 01:48:05 -03:00
have_source_trees = self . parallel_copies is not None and len ( self . tasks ) < = self . parallel_copies
2022-11-01 20:36:20 -03:00
for vehicle in vehicles_to_build :
2023-01-24 16:56:46 -04:00
if vehicle == ' bootloader ' and board in self . bootloader_blacklist :
continue
2023-05-07 07:48:45 -03:00
result [ " vehicle " ] [ vehicle ] = { }
v = result [ " vehicle " ] [ vehicle ]
v [ " bin_filename " ] = self . vehicle_map [ vehicle ] + ' .bin '
elf_dirname = " bin "
if vehicle == ' bootloader ' :
# elfs for bootloaders are in the bootloader directory...
elf_dirname = " bootloader "
2023-05-30 01:48:05 -03:00
elf_basedir = outdir
if have_source_trees :
try :
v [ " source_path " ] = pathlib . Path ( outdir , " scb_sourcepath.txt " ) . read_text ( )
elf_basedir = os . path . join ( v [ " source_path " ] , ' build ' )
self . progress ( " Have source trees " )
except FileNotFoundError :
pass
v [ " bin_dir " ] = os . path . join ( elf_basedir , board , " bin " )
elf_dir = os . path . join ( elf_basedir , board , elf_dirname )
2023-05-07 07:48:45 -03:00
v [ " elf_dir " ] = elf_dir
v [ " elf_filename " ] = self . vehicle_map [ vehicle ]
return result
def compare_results ( self , result_master , result_branch ) :
ret = { }
for vehicle in result_master [ " vehicle " ] . keys ( ) :
# check for the difference in size (and identicality)
# of the two binaries:
master_bin_dir = result_master [ " vehicle " ] [ vehicle ] [ " bin_dir " ]
new_bin_dir = result_branch [ " vehicle " ] [ vehicle ] [ " bin_dir " ]
2023-02-15 20:50:25 -04:00
2022-11-01 20:36:20 -03:00
try :
2023-05-07 07:48:45 -03:00
bin_filename = result_master [ " vehicle " ] [ vehicle ] [ " bin_filename " ]
2023-01-25 18:06:13 -04:00
master_path = os . path . join ( master_bin_dir , bin_filename )
new_path = os . path . join ( new_bin_dir , bin_filename )
master_size = os . path . getsize ( master_path )
new_size = os . path . getsize ( new_path )
2022-11-01 20:36:20 -03:00
except FileNotFoundError :
2023-05-07 07:48:45 -03:00
elf_filename = result_master [ " vehicle " ] [ vehicle ] [ " elf_filename " ]
2023-01-25 18:06:13 -04:00
master_path = os . path . join ( master_bin_dir , elf_filename )
new_path = os . path . join ( new_bin_dir , elf_filename )
master_size = os . path . getsize ( master_path )
new_size = os . path . getsize ( new_path )
identical = self . files_are_identical ( master_path , new_path )
2021-10-07 00:43:44 -03:00
2023-05-07 07:48:45 -03:00
board = result_master [ " board " ]
2023-01-25 18:06:13 -04:00
ret [ vehicle ] = SizeCompareBranchesResult ( board , vehicle , new_size - master_size , identical )
2021-10-07 00:43:44 -03:00
2022-11-01 20:36:20 -03:00
return ret
2021-10-07 00:43:44 -03:00
if __name__ == ' __main__ ' :
parser = optparse . OptionParser ( " size_compare_branches.py " )
2022-11-01 20:36:20 -03:00
parser . add_option ( " " ,
2023-03-08 21:27:29 -04:00
" --elf-diff " ,
2022-11-01 20:36:20 -03:00
action = " store_true " ,
default = False ,
2023-03-08 21:27:29 -04:00
help = " run elf_diff on output files " )
2021-10-07 00:43:44 -03:00
parser . add_option ( " " ,
" --master-branch " ,
type = " string " ,
default = " master " ,
help = " master branch to use " )
2022-12-22 18:12:59 -04:00
parser . add_option ( " " ,
" --no-merge-base " ,
action = " store_true " ,
default = False ,
help = " do not use the merge-base for testing, do a direct comparison between branches " )
2023-02-15 07:20:52 -04:00
parser . add_option ( " " ,
" --no-waf-consistent-builds " ,
action = " store_true " ,
default = False ,
help = " do not use the --consistent-builds waf command-line option (for older branches) " )
2021-10-07 00:43:44 -03:00
parser . add_option ( " " ,
" --branch " ,
type = " string " ,
default = None ,
help = " branch to compare " )
2021-11-04 00:15:24 -03:00
parser . add_option ( " " ,
" --vehicle " ,
2022-11-01 20:36:20 -03:00
action = ' append ' ,
default = [ ] ,
2021-11-04 00:15:24 -03:00
help = " vehicle to build for " )
2023-03-12 21:01:54 -03:00
parser . add_option ( " " ,
" --show-empty " ,
action = ' store_true ' ,
default = False ,
help = " Show result lines even if no builds were done for the board " )
2023-05-10 22:38:56 -03:00
parser . add_option ( " " ,
" --hide-unchanged " ,
action = ' store_true ' ,
default = False ,
help = " Hide binary-size-change results for any board where output binary is unchanged " )
2021-10-07 00:43:44 -03:00
parser . add_option ( " " ,
" --board " ,
2022-11-01 20:36:20 -03:00
action = ' append ' ,
default = [ ] ,
2021-10-07 00:43:44 -03:00
help = " board to build for " )
2022-08-11 22:03:30 -03:00
parser . add_option ( " " ,
" --extra-hwdef " ,
2023-02-06 23:43:18 -04:00
default = [ ] ,
action = " append " ,
2022-08-11 22:03:30 -03:00
help = " configure with this extra hwdef file " )
2023-02-06 23:43:18 -04:00
parser . add_option ( " " ,
" --extra-hwdef-branch " ,
default = [ ] ,
action = " append " ,
help = " configure with this extra hwdef file only on new branch " )
parser . add_option ( " " ,
" --extra-hwdef-master " ,
default = [ ] ,
action = " append " ,
help = " configure with this extra hwdef file only on merge/master branch " )
2022-11-01 20:36:20 -03:00
parser . add_option ( " " ,
" --all-boards " ,
action = ' store_true ' ,
default = False ,
help = " Build all boards " )
2023-06-07 20:35:46 -03:00
parser . add_option ( " " ,
" --exclude-board-glob " ,
default = [ ] ,
action = " append " ,
help = " exclude any board which matches this pattern " )
2022-11-01 20:36:20 -03:00
parser . add_option ( " " ,
" --all-vehicles " ,
action = ' store_true ' ,
default = False ,
help = " Build all vehicles " )
2023-05-07 07:48:45 -03:00
parser . add_option ( " " ,
" --parallel-copies " ,
type = int ,
default = None ,
help = " Copy source dir this many times, build from those copies in parallel " )
parser . add_option ( " -j " ,
" --jobs " ,
type = int ,
default = None ,
help = " Passed to waf configure -j; number of build jobs. If running with --parallel-copies, this is divided by the number of remaining threads before being passed. " ) # noqa
2021-10-07 00:43:44 -03:00
cmd_opts , cmd_args = parser . parse_args ( )
2022-11-01 20:36:20 -03:00
vehicle = [ ]
for v in cmd_opts . vehicle :
vehicle . extend ( v . split ( ' , ' ) )
if len ( vehicle ) == 0 :
vehicle . append ( " plane " )
board = [ ]
for b in cmd_opts . board :
board . extend ( b . split ( ' , ' ) )
if len ( board ) == 0 :
board . append ( " MatekF405-Wing " )
2021-10-07 00:43:44 -03:00
x = SizeCompareBranches (
branch = cmd_opts . branch ,
master_branch = cmd_opts . master_branch ,
2022-11-01 20:36:20 -03:00
board = board ,
vehicle = vehicle ,
2022-08-11 22:03:30 -03:00
extra_hwdef = cmd_opts . extra_hwdef ,
2023-02-06 23:43:18 -04:00
extra_hwdef_branch = cmd_opts . extra_hwdef_branch ,
extra_hwdef_master = cmd_opts . extra_hwdef_master ,
2023-03-08 21:27:29 -04:00
run_elf_diff = ( cmd_opts . elf_diff ) ,
2022-11-01 20:36:20 -03:00
all_vehicles = cmd_opts . all_vehicles ,
all_boards = cmd_opts . all_boards ,
2023-06-07 20:35:46 -03:00
exclude_board_glob = cmd_opts . exclude_board_glob ,
2022-12-22 18:12:59 -04:00
use_merge_base = not cmd_opts . no_merge_base ,
2023-02-15 07:20:52 -04:00
waf_consistent_builds = not cmd_opts . no_waf_consistent_builds ,
2023-03-12 21:01:54 -03:00
show_empty = cmd_opts . show_empty ,
2023-05-10 22:38:56 -03:00
show_unchanged = not cmd_opts . hide_unchanged ,
2023-05-07 07:48:45 -03:00
parallel_copies = cmd_opts . parallel_copies ,
jobs = cmd_opts . jobs ,
2021-10-07 00:43:44 -03:00
)
x . run ( )