#!/bin/bash LIBS_DIR="tools/esp32-arduino-libs" function build_sketch(){ # build_sketch [extra-options] while [ ! -z "$1" ]; do case "$1" in -ai ) shift ide_path=$1 ;; -au ) shift user_path=$1 ;; -t ) shift target=$1 ;; -fqbn ) shift fqbn=$1 ;; -o ) shift options=$1 ;; -s ) shift sketchdir=$1 ;; -i ) shift chunk_index=$1 ;; -l ) shift log_compilation=$1 ;; * ) break ;; esac shift done xtra_opts=$* len=0 if [ -z $sketchdir ]; then echo "ERROR: Sketch directory not provided" echo "$USAGE" exit 1 fi # No FQBN was passed, try to get it from other options if [ -z $fqbn ]; then if [ -z $target ]; then echo "ERROR: Unspecified chip" echo "$USAGE" exit 1 fi # The options are either stored in the test directory, for a per test # customization or passed as parameters. Command line options take # precedence. Note that the following logic also falls to the default # parameters if no arguments were passed and no file was found. if [ -z $options ] && [ -f $sketchdir/ci.json ]; then # The config file could contain multiple FQBNs for one chip. If # that's the case we build one time for every FQBN. len=`jq -r --arg target $target '.fqbn[$target] | length' $sketchdir/ci.json` if [ $len -gt 0 ]; then fqbn=`jq -r --arg target $target '.fqbn[$target] | sort' $sketchdir/ci.json` fi fi if [ ! -z $options ] || [ $len -eq 0 ]; then # Since we are passing options, we will end up with only one FQBN to # build. len=1 # Default FQBN options if none were passed in the command line. esp32_opts="PSRAM=enabled,PartitionScheme=huge_app,FlashMode=dio" esp32s2_opts="PSRAM=enabled,PartitionScheme=huge_app,FlashMode=dio" esp32s3_opts="PSRAM=opi,USBMode=default,PartitionScheme=huge_app,FlashMode=dio" esp32c3_opts="PartitionScheme=huge_app,FlashMode=dio" esp32c6_opts="PartitionScheme=huge_app,FlashMode=dio" esp32h2_opts="PartitionScheme=huge_app,FlashMode=dio" # Select the common part of the FQBN based on the target. The rest will be # appended depending on the passed options. case "$target" in "esp32") fqbn="espressif:esp32:esp32:${options:-$esp32_opts}" ;; "esp32s2") fqbn="espressif:esp32:esp32s2:${options:-$esp32s2_opts}" ;; "esp32c3") fqbn="espressif:esp32:esp32c3:${options:-$esp32c3_opts}" ;; "esp32s3") fqbn="espressif:esp32:esp32s3:${options:-$esp32s3_opts}" ;; "esp32c6") fqbn="espressif:esp32:esp32c6:${options:-$esp32c6_opts}" ;; "esp32h2") fqbn="espressif:esp32:esp32h2:${options:-$esp32h2_opts}" ;; esac # Make it look like a JSON array. fqbn="[\"$fqbn\"]" fi else # An FQBN was passed. Make it look like a JSON array. len=1 fqbn="[\"$fqbn\"]" fi if [ -z "$fqbn" ]; then echo "No FQBN passed or invalid chip: $target" exit 1 fi # The directory that will hold all the artifcats (the build directory) is # provided through: # 1. An env variable called ARDUINO_BUILD_DIR. # 2. Created at the sketch level as "build" in the case of a single # configuration test. # 3. Created at the sketch level as "buildX" where X is the number # of configuration built in case of a multiconfiguration test. sketchname=$(basename $sketchdir) if [ -f $sketchdir/ci.json ]; then # If the target is listed as false, skip the sketch. Otherwise, include it. is_target=$(jq -r --arg target $target '.targets[$target]' $sketchdir/ci.json) if [[ "$is_target" == "false" ]]; then echo "Skipping $sketchname for target $target" exit 0 fi # Check if the sketch requires any configuration options requirements=$(jq -r '.requires[]? // empty' $sketchdir/ci.json) if [[ "$requirements" != "null" ]] || [[ "$requirements" != "" ]]; then for requirement in $requirements; do found_line=$(grep -E "^$requirement" $LIBS_DIR/$target/sdkconfig) if [[ "$found_line" == "" ]]; then echo "Target $target does not meet the requirement $requirement for $sketchname. Skipping." exit 0 fi done fi fi ARDUINO_CACHE_DIR="$HOME/.arduino/cache.tmp" if [ -n "$ARDUINO_BUILD_DIR" ]; then build_dir="$ARDUINO_BUILD_DIR" elif [ $len -eq 1 ]; then # build_dir="$sketchdir/build" build_dir="$HOME/.arduino/tests/$sketchname/build.tmp" fi output_file="$HOME/.arduino/cli_compile_output.txt" sizes_file="$GITHUB_WORKSPACE/cli_compile_$chunk_index.json" mkdir -p "$ARDUINO_CACHE_DIR" for i in `seq 0 $(($len - 1))` do if [ $len -ne 1 ]; then # build_dir="$sketchdir/build$i" build_dir="$HOME/.arduino/tests/$sketchname/build$i.tmp" fi rm -rf $build_dir mkdir -p $build_dir currfqbn=`echo $fqbn | jq -r --argjson i $i '.[$i]'` if [ -f "$ide_path/arduino-cli" ]; then echo "Building $sketchname with arduino-cli and FQBN=$currfqbn" curroptions=`echo "$currfqbn" | cut -d':' -f4` currfqbn=`echo "$currfqbn" | cut -d':' -f1-3` $ide_path/arduino-cli compile \ --fqbn "$currfqbn" \ --board-options "$curroptions" \ --warnings "all" \ --build-property "compiler.warning_flags.all=-Wall -Werror=all -Wextra" \ --build-cache-path "$ARDUINO_CACHE_DIR" \ --build-path "$build_dir" \ $xtra_opts "${sketchdir}" \ > $output_file exit_status=$? if [ $exit_status -ne 0 ]; then echo "ERROR: Compilation failed with error code $exit_status" exit $exit_status fi if [ $log_compilation ]; then #Extract the program storage space and dynamic memory usage in bytes and percentage in separate variables from the output, just the value without the string flash_bytes=$(grep -oE 'Sketch uses ([0-9]+) bytes' $output_file | awk '{print $3}') flash_percentage=$(grep -oE 'Sketch uses ([0-9]+) bytes \(([0-9]+)%\)' $output_file | awk '{print $5}' | tr -d '(%)') ram_bytes=$(grep -oE 'Global variables use ([0-9]+) bytes' $output_file | awk '{print $4}') ram_percentage=$(grep -oE 'Global variables use ([0-9]+) bytes \(([0-9]+)%\)' $output_file | awk '{print $6}' | tr -d '(%)') # Extract the directory path excluding the filename directory_path=$(dirname "$sketch") # Define the constant part constant_part="/home/runner/Arduino/hardware/espressif/esp32/libraries/" # Extract the desired substring using sed lib_sketch_name=$(echo "$directory_path" | sed "s|$constant_part||") #append json file where key is fqbn, sketch name, sizes -> extracted values echo "{\"name\": \"$lib_sketch_name\", \"sizes\": [{ \"flash_bytes\": $flash_bytes, \"flash_percentage\": $flash_percentage, \"ram_bytes\": $ram_bytes, \"ram_percentage\": $ram_percentage }] }," >> "$sizes_file" fi elif [ -f "$ide_path/arduino-builder" ]; then echo "Building $sketchname with arduino-builder and FQBN=$currfqbn" echo "Build path = $build_dir" $ide_path/arduino-builder -compile -logger=human -core-api-version=10810 \ -fqbn=\"$currfqbn\" \ -warnings="all" \ -tools "$ide_path/tools-builder" \ -hardware "$user_path/hardware" \ -libraries "$user_path/libraries" \ -build-cache "$ARDUINO_CACHE_DIR" \ -build-path "$build_dir" \ $xtra_opts "${sketchdir}/${sketchname}.ino" exit_status=$? if [ $exit_status -ne 0 ]; then echo "ERROR: Compilation failed with error code $exit_status" exit $exit_status fi # $ide_path/arduino-builder -compile -logger=human -core-api-version=10810 \ # -fqbn=\"$currfqbn\" \ # -warnings="all" \ # -tools "$ide_path/tools-builder" \ # -tools "$ide_path/tools" \ # -built-in-libraries "$ide_path/libraries" \ # -hardware "$ide_path/hardware" \ # -hardware "$user_path/hardware" \ # -libraries "$user_path/libraries" \ # -build-cache "$ARDUINO_CACHE_DIR" \ # -build-path "$build_dir" \ # $xtra_opts "${sketchdir}/${sketchname}.ino" fi done unset fqbn unset xtra_opts unset options } function count_sketches(){ # count_sketches [target] [file] local path=$1 local target=$2 local file=$3 if [ $# -lt 1 ]; then echo "ERROR: Illegal number of parameters" echo "USAGE: ${0} count [target]" fi rm -rf sketches.txt touch sketches.txt if [ ! -d "$path" ]; then return 0 fi if [ -n "$file" ]; then local sketches=$(cat $file) else local sketches=$(find $path -name *.ino | sort) fi local sketchnum=0 for sketch in $sketches; do local sketchdir=$(dirname $sketch) local sketchdirname=$(basename $sketchdir) local sketchname=$(basename $sketch) if [[ "$sketchdirname.ino" != "$sketchname" ]]; then continue elif [[ -n $target ]] && [[ -f $sketchdir/ci.json ]]; then # If the target is listed as false, skip the sketch. Otherwise, include it. is_target=$(jq -r --arg target $target '.targets[$target]' $sketchdir/ci.json) if [[ "$is_target" == "false" ]]; then continue fi # Check if the sketch requires any configuration options requirements=$(jq -r '.requires[]? // empty' $sketchdir/ci.json) if [[ "$requirements" != "null" ]] || [[ "$requirements" != "" ]]; then for requirement in $requirements; do found_line=$(grep -E "^$requirement" $LIBS_DIR/$target/sdkconfig) if [[ "$found_line" == "" ]]; then continue 2 fi done fi fi echo $sketch >> sketches.txt sketchnum=$(($sketchnum + 1)) done return $sketchnum } function build_sketches(){ # build_sketches [extra-options] local args="" while [ ! -z "$1" ]; do case $1 in -ai ) shift ide_path=$1 ;; -au ) shift user_path=$1 ;; -t ) shift target=$1 args+=" -t $target" ;; -fqbn ) shift fqbn=$1 args+=" -fqbn $fqbn" ;; -p ) shift path=$1 ;; -i ) shift chunk_index=$1 ;; -m ) shift chunk_max=$1 ;; -l ) shift log_compilation=$1 ;; -f ) shift sketches_file=$1 ;; * ) break ;; esac shift done local xtra_opts=$* if [ -z "$chunk_index" ] || [ -z "$chunk_max" ]; then echo "ERROR: Invalid chunk paramters" echo "$USAGE" exit 1 fi if [ "$chunk_max" -le 0 ]; then echo "ERROR: Chunks count must be positive number" return 1 fi if [ "$chunk_index" -gt "$chunk_max" ] && [ "$chunk_max" -ge 2 ]; then chunk_index=$chunk_max fi set +e if [ -n "$sketches_file" ]; then count_sketches "$path" "$target" "$sketches_file" local sketchcount=$? else count_sketches "$path" "$target" local sketchcount=$? fi set -e local sketches=$(cat sketches.txt) rm -rf sketches.txt local chunk_size=$(( $sketchcount / $chunk_max )) local all_chunks=$(( $chunk_max * $chunk_size )) if [ "$all_chunks" -lt "$sketchcount" ]; then chunk_size=$(( $chunk_size + 1 )) fi local start_index=0 local end_index=0 if [ "$chunk_index" -ge "$chunk_max" ]; then start_index=$chunk_index end_index=$sketchcount else start_index=$(( $chunk_index * $chunk_size )) if [ "$sketchcount" -le "$start_index" ]; then echo "No sketches to build for $target in this chunk" return 0 fi end_index=$(( $(( $chunk_index + 1 )) * $chunk_size )) if [ "$end_index" -gt "$sketchcount" ]; then end_index=$sketchcount fi fi local start_num=$(( $start_index + 1 )) echo "Found $sketchcount Sketches for target '$target'"; echo "Chunk Index : $chunk_index" echo "Chunk Count : $chunk_max" echo "Chunk Size : $chunk_size" echo "Start Sketch: $start_num" echo "End Sketch : $end_index" #if fqbn is not passed then set it to default for compilation log if [ -z $fqbn ]; then log_fqbn="espressif:esp32:$target" else log_fqbn=$fqbn fi sizes_file="$GITHUB_WORKSPACE/cli_compile_$chunk_index.json" if [ $log_compilation ]; then #echo board,target and start of sketches to sizes_file json echo "{ \"board\": \"$log_fqbn\", \"target\": \"$target\", \"sketches\": [" >> "$sizes_file" fi local sketchnum=0 args+=" -ai $ide_path -au $user_path -i $chunk_index" if [ $log_compilation ]; then args+=" -l $log_compilation" fi for sketch in $sketches; do local sketchdir=$(dirname $sketch) local sketchdirname=$(basename $sketchdir) sketchnum=$(($sketchnum + 1)) if [ "$sketchnum" -le "$start_index" ] \ || [ "$sketchnum" -gt "$end_index" ]; then continue fi echo "" echo "Building Sketch Index $sketchnum - $sketchdirname" build_sketch $args -s $sketchdir $xtra_opts local result=$? if [ $result -ne 0 ]; then return $result fi done if [ $log_compilation ]; then #remove last comma from json if [ $i -eq $(($len - 1)) ]; then sed -i '$ s/.$//' "$sizes_file" fi #echo end of sketches sizes_file json echo "]" >> "$sizes_file" #echo end of board sizes_file json echo "}," >> "$sizes_file" fi return 0 } USAGE=" USAGE: ${0} [command] [options] Available commands: count: Count sketches. build: Build a sketch. chunk_build: Build a chunk of sketches. " cmd=$1 shift if [ -z $cmd ]; then echo "ERROR: No command supplied" echo "$USAGE" exit 2 fi case "$cmd" in "count") count_sketches $* ;; "build") build_sketch $* ;; "chunk_build") build_sketches $* ;; *) echo "ERROR: Unrecognized command" echo "$USAGE" exit 2 esac