Files
edgetx/tools/build-multilang.sh
2026-08-03 16:37:20 +08:00

1335 lines
48 KiB
Bash
Executable File

#!/bin/bash
# EdgeTX Multi-Language Multi-Variant Build Script
# Written with assistance from GitHub Copilot AI
#
# This script automatically builds all languages for all firmware variants
# with intelligent filtering for character-based languages on B&W screens
# Manual override for color screen targets (set this to override auto-detection)
COLOR_SCREEN_TARGETS_OVERRIDE=""
# Function to build targets by screen type and optional processor filter
build_targets_by_screen_type() {
local screen_type="$1" # "color" or "bw"
local processor_filter="$2" # "F2", "F4", "H7", "exclude-F2", etc. (optional)
local all_targets=$(get_all_targets)
local filtered_targets=""
local failed_targets=0
local overall_start_time=$(date +%s)
# Filter by screen type first
for target in $all_targets; do
local is_color_target=false
if is_color_screen_target "$target"; then
is_color_target=true
fi
if [[ "$screen_type" == "color" && "$is_color_target" == "true" ]]; then
filtered_targets="$filtered_targets $target"
elif [[ "$screen_type" == "bw" && "$is_color_target" == "false" ]]; then
filtered_targets="$filtered_targets $target"
fi
done
# Apply processor filter if specified
if [[ -n "$processor_filter" ]]; then
local processor_filtered_targets=""
if [[ "$processor_filter" == exclude-* ]]; then
# Exclude specific processor
local exclude_proc="${processor_filter#exclude-}"
for target in $filtered_targets; do
local target_processor=$(get_stm32_processor_type "$target")
if [[ "$target_processor" != "$exclude_proc" && "$target_processor" != "UNKNOWN" ]]; then
processor_filtered_targets="$processor_filtered_targets $target"
fi
done
else
# Include only specific processor
for target in $filtered_targets; do
local target_processor=$(get_stm32_processor_type "$target")
if [[ "$target_processor" == "$processor_filter" ]]; then
processor_filtered_targets="$processor_filtered_targets $target"
fi
done
fi
filtered_targets="$processor_filtered_targets"
fi
# Build the filtered targets
if [[ -z "$filtered_targets" ]]; then
echo "No targets match the specified criteria."
return 0
fi
echo "Building $screen_type screen targets$(if [[ -n "$processor_filter" ]]; then echo " with processor filter: $processor_filter"; fi)..."
echo "Selected targets: $filtered_targets"
echo
for target in $filtered_targets; do
echo "========================================"
echo "Processing target: $target ($(get_stm32_processor_type "$target") processor)"
echo "========================================"
if ! build_target_all_languages "$target"; then
((failed_targets++))
echo "Failed to build all languages for target $target"
fi
echo
done
# Calculate overall elapsed time
local overall_end_time=$(date +%s)
local overall_elapsed=$((overall_end_time - overall_start_time))
# Format overall elapsed time
local overall_elapsed_formatted
if [[ $overall_elapsed -ge 3600 ]]; then
overall_elapsed_formatted=$(printf "%dh %dm %ds" $((overall_elapsed / 3600)) $(((overall_elapsed % 3600) / 60)) $((overall_elapsed % 60)))
elif [[ $overall_elapsed -ge 60 ]]; then
overall_elapsed_formatted=$(printf "%dm %ds" $((overall_elapsed / 60)) $((overall_elapsed % 60)))
else
overall_elapsed_formatted="${overall_elapsed}s"
fi
if [[ $failed_targets -gt 0 && "${CONTINUE_ON_FAILURE:-}" != "1" ]]; then
echo "========================================="
echo "BUILD SUMMARY"
echo "========================================="
printf "\033[31m✗\033[0m Warning: %d targets had build failures\n" "$failed_targets"
echo "Total build time: ${overall_elapsed_formatted}"
return 1
else
echo "========================================="
echo "BUILD SUMMARY"
echo "========================================="
if [[ $failed_targets -gt 0 && "${CONTINUE_ON_FAILURE:-}" == "1" ]]; then
printf "\033[32m✓\033[0m All targets attempted successfully!\n"
echo " Note: Some language builds failed but were ignored due to --continue-on-failure"
else
printf "\033[32m✓\033[0m All targets built successfully!\n"
fi
echo "Total build time: ${overall_elapsed_formatted}"
return 0
fi
}
# with intelligent filtering for character-based languages on B&W screens
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Source the build-common.sh for target definitions
if [[ -f "$SCRIPT_DIR/tools/build-common.sh" ]]; then
. "$SCRIPT_DIR/tools/build-common.sh"
elif [[ -f "$SCRIPT_DIR/build-common.sh" ]]; then
. "$SCRIPT_DIR/build-common.sh"
else
echo "Error: Could not find build-common.sh"
exit 1
fi
# Auto-discover available languages from radio/src/CMakeLists.txt
get_radio_languages() {
local cmake_file=""
if [[ -f "$SCRIPT_DIR/../radio/src/CMakeLists.txt" ]]; then
cmake_file="$SCRIPT_DIR/../radio/src/CMakeLists.txt"
else
echo "Error: Could not find radio/src/CMakeLists.txt" >&2
return 1
fi
# Extract languages from: set(RADIO_LANGUAGES CN CZ DA DE EN ES FI FR HE HU IT JP KO NL PL PT RU SE SK TW UA)
# Strip carriage returns to handle Windows line endings
grep "^set(RADIO_LANGUAGES" "$cmake_file" | sed 's/set(RADIO_LANGUAGES \(.*\))/\1/' | tr -d '\r'
}
# Define character-based/logographic languages (configurable with auto-detection fallback)
# These should not be built for B&W screens due to font/display limitations
LOGOGRAPHIC_LANGUAGES_OVERRIDE="" # Set this to override auto-detection
get_logographic_languages() {
# Use override if set
if [[ -n "$LOGOGRAPHIC_LANGUAGES_OVERRIDE" ]]; then
echo "$LOGOGRAPHIC_LANGUAGES_OVERRIDE"
return 0
fi
# Auto-detect logographic languages based on known character sets
# These are languages that typically use complex character sets that may not display well on B&W screens
local known_logographic="CN JP KO TW"
# Could be enhanced to check translation files for unicode/multibyte characters
# For now, use the known list as fallback
echo "$known_logographic"
}
# Extract all firmware variants from build-common.sh
get_all_targets() {
# Extract target names from the get_target_build_options function
local build_common_path=""
if [[ -f "$SCRIPT_DIR/tools/build-common.sh" ]]; then
build_common_path="$SCRIPT_DIR/tools/build-common.sh"
elif [[ -f "$SCRIPT_DIR/build-common.sh" ]]; then
build_common_path="$SCRIPT_DIR/build-common.sh"
else
echo "Error: Could not find build-common.sh" >&2
return 1
fi
# Extract target names from case statement in get_target_build_options function
awk '
/^get_target_build_options\(\)/ { in_function=1; next }
in_function && /^}$/ { exit }
in_function && /^[[:space:]]*[a-z0-9_-]+\)/ {
gsub(/^[[:space:]]*/, "")
gsub(/\).*$/, "")
if ($0 != "*" && $0 != "esac") print $0
}
' "$build_common_path" | sort -u
}
# Auto-discover color screen targets by checking target CMakeLists.txt files - fully dynamic implementation
get_color_screen_targets() {
local color_targets=""
local all_targets
all_targets=$(get_all_targets)
for target in $all_targets; do
# Get the PCB type for this target from build-common.sh
local pcb_line
pcb_line=$(awk -v target="$target" '
/^get_target_build_options\(\)/ { in_function=1; next }
in_function && /^}$/ { exit }
in_function && $0 ~ "^[[:space:]]*" target "\\)" {
found_target=1; next
}
found_target && /BUILD_OPTIONS.*-DPCB=/ {
print $0; exit
}
found_target && /;;/ { exit }
' "$SCRIPT_DIR/build-common.sh" 2>/dev/null || true)
if [[ -n "$pcb_line" ]]; then
# Extract PCB type (e.g., -DPCB=X10 -> X10)
local pcb_type
pcb_type=$(echo "$pcb_line" | grep -o '\-DPCB=[A-Z0-9+]*' | cut -d= -f2)
if [[ -n "$pcb_type" ]]; then
# Dynamically determine target directory by parsing radio/src/CMakeLists.txt
local radio_cmake_file="../radio/src/CMakeLists.txt"
local target_dir=""
if [[ -f "$SCRIPT_DIR/$radio_cmake_file" ]]; then
# Parse the if/elseif chain to find which target directory corresponds to our PCB type
target_dir=$(awk -v pcb="$pcb_type" '
/^if\(PCB STREQUAL|^elseif\(PCB STREQUAL/ {
condition_line = $0
# Extract all PCB types from the condition
gsub(/^[^(]*\(/, "", condition_line) # Remove everything before first (
gsub(/\).*$/, "", condition_line) # Remove everything after last )
gsub(/PCB STREQUAL /, "", condition_line) # Remove "PCB STREQUAL "
gsub(/ OR /, " ", condition_line) # Replace " OR " with space
# Check if our PCB type is in this condition
if ((" " condition_line " ") ~ (" " pcb " ")) {
found_pcb = 1
}
}
found_pcb && /include\(targets\/.*\/CMakeLists\.txt\)/ {
# Extract target directory from include(targets/horus/CMakeLists.txt)
match($0, /targets\/([^\/]+)\//, arr)
if (arr[1]) {
print arr[1]
exit
}
}
/^else\(\)|^endif\(\)/ { found_pcb = 0 }
' "$SCRIPT_DIR/$radio_cmake_file")
fi
if [[ -n "$target_dir" ]]; then
# Check if this target directory has color screen support
local target_cmake_file="../radio/src/targets/$target_dir/CMakeLists.txt"
if [[ -f "$SCRIPT_DIR/$target_cmake_file" ]]; then
# Look for color screen indicators: GUI_DIR with colorlcd, COLORLCD definitions, etc.
if grep -q "GUI_DIR.*colorlcd\|COLORLCD\|set(GUI_DIR.*colorlcd" "$SCRIPT_DIR/$target_cmake_file"; then
color_targets="$color_targets $target"
fi
fi
fi
fi
fi
done
# Return unique sorted targets
echo "$color_targets" | tr ' ' '\n' | sort -u | tr '\n' ' '
}
# Auto-discover STM32 processor types for targets - fully dynamic implementation
get_stm32_processor_type() {
local target=$1
# Hard-coded case for x9dp (will be phased out)
if [[ "$target" == "x9dp" ]]; then
echo "F2"
return 0
fi
# Get the PCB configuration for this target from build-common.sh
local pcb_line
pcb_line=$(awk -v target="$target" '
/^get_target_build_options\(\)/ { in_function=1; next }
in_function && /^}$/ { exit }
in_function && $0 ~ "^[[:space:]]*" target "\\)" {
found_target=1; next
}
found_target && /BUILD_OPTIONS.*-DPCB=/ {
print $0
exit
}
found_target && /;;/ { exit }
' "$SCRIPT_DIR/build-common.sh" 2>/dev/null || true)
if [[ -z "$pcb_line" ]]; then
echo "UNKNOWN"
return 1
fi
# Extract PCB type and PCBREV from BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=T8"
local pcb_type
local pcb_rev
pcb_type=$(echo "$pcb_line" | grep -o '\-DPCB=[A-Z0-9+]*' | cut -d= -f2)
pcb_rev=$(echo "$pcb_line" | grep -o '\-DPCBREV=[A-Z0-9+]*' | cut -d= -f2 || true)
if [[ -z "$pcb_type" ]]; then
echo "UNKNOWN"
return 1
fi
# Dynamically determine target directory by parsing radio/src/CMakeLists.txt
local radio_cmake_file="../radio/src/CMakeLists.txt"
local target_dir=""
if [[ -f "$SCRIPT_DIR/$radio_cmake_file" ]]; then
# Parse the if/elseif chain to find which target directory corresponds to our PCB type
target_dir=$(awk -v pcb="$pcb_type" '
/^if\(PCB STREQUAL|^elseif\(PCB STREQUAL/ {
condition_line = $0
# Extract all PCB types from the condition
gsub(/^[^(]*\(/, "", condition_line) # Remove everything before first (
gsub(/\).*$/, "", condition_line) # Remove everything after last )
gsub(/PCB STREQUAL /, "", condition_line) # Remove "PCB STREQUAL "
gsub(/ OR /, " ", condition_line) # Replace " OR " with space
# Check if our PCB type is in this condition
if ((" " condition_line " ") ~ (" " pcb " ")) {
found_pcb = 1
}
}
found_pcb && /include\(targets\/.*\/CMakeLists\.txt\)/ {
# Extract target directory from include(targets/horus/CMakeLists.txt)
match($0, /targets\/([^\/]+)\//, arr)
if (arr[1]) {
print arr[1]
exit
}
}
/^else\(\)|^endif\(\)/ { found_pcb = 0 }
' "$SCRIPT_DIR/$radio_cmake_file")
fi
if [[ -z "$target_dir" ]]; then
echo "UNKNOWN"
return 1
fi
# Simulate the CMake logic to determine CPU_TYPE_FULL based on PCB and PCBREV
local target_cmake_file="../radio/src/targets/$target_dir/CMakeLists.txt"
if [[ -f "$SCRIPT_DIR/$target_cmake_file" ]]; then
# Parse the target CMakeLists.txt to determine CPU_TYPE_FULL
# We need to simulate the if/elseif logic for PCB and PCBREV
local cpu_type_full
# First, try to find CPU_TYPE_FULL within the specific PCBREV block if PCBREV is specified
if [[ -n "$pcb_rev" ]]; then
cpu_type_full=$(awk -v pcb="$pcb_type" -v pcbrev="$pcb_rev" '
BEGIN { in_pcb_block = 0; in_pcbrev_block = 0; cpu_found = 0 }
# Look for PCB conditions
/^if\(PCB STREQUAL|^elseif\(PCB STREQUAL/ {
condition_line = $0
gsub(/^[^(]*\(/, "", condition_line)
gsub(/\).*$/, "", condition_line)
gsub(/PCB STREQUAL /, "", condition_line)
gsub(/ OR /, " ", condition_line)
# Check if our PCB type is in this condition (use index for exact matching)
if (index(" " condition_line " ", " " pcb " ") > 0) {
in_pcb_block = 1
} else {
in_pcb_block = 0
}
in_pcbrev_block = 0
}
# Look for PCBREV conditions within PCB block
in_pcb_block && /\$\{PCBREV\} STREQUAL/ {
condition_line = $0
gsub(/.*\$\{PCBREV\} STREQUAL /, "", condition_line)
gsub(/\).*$/, "", condition_line)
gsub(/ .*$/, "", condition_line)
if (condition_line == pcbrev) {
in_pcbrev_block = 1
} else {
in_pcbrev_block = 0
}
}
# Accept CPU_TYPE_FULL within PCBREV block
in_pcbrev_block && /set\(CPU_TYPE_FULL STM32/ {
match($0, /STM32[FH][0-9RSx]+[A-Z]*/, arr)
if (arr[0] && !cpu_found) {
print arr[0]
cpu_found = 1
exit
}
}
# Reset PCBREV block when we hit another condition or endif
/^[[:space:]]*if\(|^[[:space:]]*elseif\(|^[[:space:]]*else\(|^[[:space:]]*endif\(/ && !/PCBREV/ {
if (in_pcb_block && in_pcbrev_block) {
in_pcbrev_block = 0
}
}
# Reset everything when we exit the PCB block
/^else\(\)|^endif\(\)/ && !(/PCBREV/) {
in_pcb_block = 0
in_pcbrev_block = 0
}
' "$SCRIPT_DIR/$target_cmake_file")
fi
# If no CPU_TYPE_FULL found in PCBREV block, or no PCBREV specified,
# look for CPU_TYPE_FULL within the specific PCB block
if [[ -z "$cpu_type_full" ]]; then
# Use sed to extract the specific PCB block, then look for CPU_TYPE_FULL
# For targets without PCBREV, we want the "else()" case, not the PCBREV-specific ones
local pcb_block_start_pattern="^elseif(PCB STREQUAL $pcb_type)\|^if(PCB STREQUAL.*$pcb_type"
local pcb_block_end_pattern="^elseif(PCB STREQUAL\|^else()\|^endif()"
if [[ -z "$pcb_rev" ]]; then
# No PCBREV specified, first try to find CPU_TYPE_FULL in else() block for complex targets
cpu_type_full=$(sed -n "/$pcb_block_start_pattern/,/$pcb_block_end_pattern/p" "$SCRIPT_DIR/$target_cmake_file" | \
awk '
BEGIN { in_else_block = 0 }
/else\(\)/ { in_else_block = 1; next }
/endif\(\)/ { in_else_block = 0; next }
/\$\{PCBREV\} STREQUAL/ { in_else_block = 0; next }
in_else_block && /set\(CPU_TYPE_FULL STM32/ {
match($0, /STM32[FH][0-9RSx]+[A-Z]*/, arr)
if (arr[0]) { print arr[0]; exit }
}
# If no else() block, take any CPU_TYPE_FULL not in a PCBREV block
!in_else_block && /set\(CPU_TYPE_FULL STM32/ && (prev_line !~ /\$\{PCBREV\} STREQUAL/) {
match($0, /STM32[FH][0-9RSx]+[A-Z]*/, arr)
if (arr[0] && !found_any) { found_any=1; print arr[0]; exit }
}
{ prev_line = $0 }
')
# If still not found (simple targets), use global CPU_TYPE_FULL
if [[ -z "$cpu_type_full" ]]; then
cpu_type_full=$(grep "set(CPU_TYPE_FULL STM32" "$SCRIPT_DIR/$target_cmake_file" | head -1 | grep -o "STM32[FH][0-9RSx]*[A-Z]*")
fi
else
# PCBREV specified but no CPU_TYPE_FULL found in PCBREV block, use global CPU_TYPE_FULL
cpu_type_full=$(grep "set(CPU_TYPE_FULL STM32" "$SCRIPT_DIR/$target_cmake_file" | head -1 | grep -o "STM32[FH][0-9RSx]*[A-Z]*")
fi
fi
if [[ -n "$cpu_type_full" ]]; then
# Convert CPU_TYPE_FULL to CPU_TYPE (F2, F4, H7, etc.)
local cpu_type
case "$cpu_type_full" in
STM32F2*) cpu_type="F2" ;;
STM32F4*) cpu_type="F4" ;;
STM32H7*) cpu_type="H7" ;;
*) cpu_type="UNKNOWN" ;;
esac
echo "$cpu_type"
return 0
fi
fi
echo "UNKNOWN"
return 1
}
# Function to get targets by STM32 processor type
get_targets_by_processor() {
local processor_filter="$1"
local all_targets=$(get_all_targets)
local filtered_targets=""
for target in $all_targets; do
local target_processor=$(get_stm32_processor_type "$target")
if [[ "$target_processor" == "$processor_filter" ]]; then
filtered_targets="$filtered_targets $target"
fi
done
echo "$filtered_targets"
}
# Function to get targets by processor exclusion
get_targets_excluding_processor() {
local exclude_processor="$1"
local all_targets=$(get_all_targets)
local filtered_targets=""
for target in $all_targets; do
local target_processor=$(get_stm32_processor_type "$target")
if [[ "$target_processor" != "$exclude_processor" && "$target_processor" != "UNKNOWN" ]]; then
filtered_targets="$filtered_targets $target"
fi
done
echo "$filtered_targets"
}
# Function to check if a target uses color screen
is_color_screen_target() {
local target=$1
local color_targets
# Use override if set, otherwise auto-detect
if [[ -n "$COLOR_SCREEN_TARGETS_OVERRIDE" ]]; then
color_targets="$COLOR_SCREEN_TARGETS_OVERRIDE"
else
color_targets=$(get_color_screen_targets)
fi
# Check if target is in the color targets list
for color_target in $color_targets; do
if [[ "$target" == "$color_target" ]]; then
return 0
fi
done
return 1
}
# Function to check if a language is logographic
is_logographic_language() {
local lang=$1
local logographic_langs
logographic_langs=$(get_logographic_languages)
for logo_lang in $logographic_langs; do
if [[ "$lang" == "$logo_lang" ]]; then
return 0
fi
done
return 1
}
# Function to get valid languages for a target
get_valid_languages_for_target() {
local target=$1
local valid_languages=""
local radio_languages
radio_languages=$(get_radio_languages)
# Check if force all languages flag is set
if [[ "${FORCE_ALL_LANGUAGES:-}" == "1" ]]; then
# Force all languages mode: build all languages regardless of screen type
valid_languages="$radio_languages"
else
# Normal mode: filter based on screen type
for lang in $radio_languages; do
if is_color_screen_target "$target"; then
# Color screen: all languages allowed
valid_languages="$valid_languages $lang"
else
# B&W screen: exclude logographic languages
if ! is_logographic_language "$lang"; then
valid_languages="$valid_languages $lang"
fi
fi
done
fi
echo "$valid_languages"
}
# Function to build a specific target with a specific language
build_target_language() {
local target=$1
local language=$2
# Show building message without newline
printf "Building %s with language %s... " "$target" "$language"
# Dry run mode - just simulate the build
if [[ "${DRY_RUN:-}" == "1" ]]; then
printf "\033[32m✓\033[0m [DRY-RUN]\n"
printf " → Would build: TRANSLATIONS=%s FLAVOR=%s\n" "$language" "$target"
return 0
fi
# Use build-gh.sh with the target and set the TRANSLATIONS environment variable
# build-gh.sh expects FLAVOR environment variable with the target name
local build_script=""
if [[ -f "$SCRIPT_DIR/tools/build-gh.sh" ]]; then
build_script="$SCRIPT_DIR/tools/build-gh.sh"
elif [[ -f "$SCRIPT_DIR/build-gh.sh" ]]; then
build_script="$SCRIPT_DIR/build-gh.sh"
else
echo "Error: Could not find build-gh.sh"
return 1
fi
export FLAVOR="$target"
export SRCDIR="/workspaces/edgetx"
export EXTRA_OPTIONS="-DTRANSLATIONS=$language "
# Create build directory if it doesn't exist and change to it
local build_dir="$SCRIPT_DIR/../build"
mkdir -p "$build_dir"
local original_pwd="$PWD"
cd "$build_dir"
# Create a unique log file for this build
local log_file="/tmp/edgetx-build-${target}-${language}-$$.log"
# Run build script and capture all output to log file
"$build_script" > "$log_file" 2>&1
local exit_code=$?
# Return to original directory
cd "$original_pwd"
if [[ $exit_code -eq 0 ]]; then
printf "\033[32m✓\033[0m Success!\n"
# Rename firmware files to include language code
local git_sha=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
local base_name="${target}-${git_sha}"
local lang_suffix="-${language,,}" # Convert language to lowercase
# Check for and rename .bin file
if [[ -f "${base_name}.bin" ]]; then
mv "${base_name}.bin" "${base_name}${lang_suffix}.bin"
fi
# Check for and rename .uf2 file
if [[ -f "${base_name}.uf2" ]]; then
mv "${base_name}.uf2" "${base_name}${lang_suffix}.uf2"
fi
# Clean up log file on success
rm -f "$log_file"
else
printf "\033[31m✗\033[0m Failed!\n"
printf " Build log saved to: %s\n" "$log_file"
if [[ "${CONTINUE_ON_FAILURE:-0}" != "1" ]]; then
printf " Terminating current batch due to build failure\n"
fi
return 1
fi
return 0
}
# Function to build all languages for a target
build_target_all_languages() {
local target=$1
local languages
local failed_builds=0
local start_time=$(date +%s)
languages=$(get_valid_languages_for_target "$target")
if [[ "${FORCE_ALL_LANGUAGES:-}" == "1" ]]; then
echo "Building target '$target' with ALL languages (forced): $languages"
echo " → Force mode - building all languages including logographic for B&W screens"
else
echo "Building target '$target' with languages: $languages"
if is_color_screen_target "$target"; then
echo " → Color screen target - building all languages"
else
echo " → B&W screen target - excluding logographic languages ($(get_logographic_languages))"
fi
fi
for language in $languages; do
if ! build_target_language "$target" "$language"; then
((failed_builds++))
# Stop building more languages for this target after first failure unless --continue-on-failure is set
if [[ "${CONTINUE_ON_FAILURE:-}" != "1" ]]; then
break
fi
fi
done
local end_time=$(date +%s)
local elapsed=$((end_time - start_time))
local elapsed_formatted
# Format elapsed time nicely
if [[ $elapsed -ge 3600 ]]; then
# Hours, minutes, seconds
elapsed_formatted=$(printf "%dh %dm %ds" $((elapsed / 3600)) $(((elapsed % 3600) / 60)) $((elapsed % 60)))
elif [[ $elapsed -ge 60 ]]; then
# Minutes and seconds
elapsed_formatted=$(printf "%dm %ds" $((elapsed / 60)) $((elapsed % 60)))
else
# Just seconds
elapsed_formatted="${elapsed}s"
fi
if [[ $failed_builds -gt 0 ]]; then
echo "Warning: $failed_builds language builds failed for target $target (${elapsed_formatted})"
return 1
else
echo "Target $target completed successfully (${elapsed_formatted})"
fi
return 0
}
# Function to build all targets with all valid languages
build_all_targets() {
local all_targets
local failed_targets=0
local overall_start_time=$(date +%s)
all_targets=$(get_all_targets)
echo "Building all firmware variants with appropriate languages..."
echo
echo "Detected targets: $(echo $all_targets | tr ' ' ',')"
echo
for target in $all_targets; do
echo "========================================"
echo "Processing target: $target"
echo "========================================"
if ! build_target_all_languages "$target"; then
((failed_targets++))
echo "Failed to build all languages for target $target"
fi
echo
done
# Calculate overall elapsed time
local overall_end_time=$(date +%s)
local overall_elapsed=$((overall_end_time - overall_start_time))
# Format overall elapsed time
local overall_elapsed_formatted
if [[ $overall_elapsed -ge 3600 ]]; then
overall_elapsed_formatted=$(printf "%dh %dm %ds" $((overall_elapsed / 3600)) $(((overall_elapsed % 3600) / 60)) $((overall_elapsed % 60)))
elif [[ $overall_elapsed -ge 60 ]]; then
overall_elapsed_formatted=$(printf "%dm %ds" $((overall_elapsed / 60)) $((overall_elapsed % 60)))
else
overall_elapsed_formatted="${overall_elapsed}s"
fi
if [[ $failed_targets -gt 0 && "${CONTINUE_ON_FAILURE:-}" != "1" ]]; then
echo "========================================="
echo "BUILD SUMMARY"
echo "========================================="
printf "\033[31m✗\033[0m Warning: %d targets had build failures\n" "$failed_targets"
echo "Total build time: ${overall_elapsed_formatted}"
return 1
else
echo "========================================="
echo "BUILD SUMMARY"
echo "========================================="
if [[ $failed_targets -gt 0 && "${CONTINUE_ON_FAILURE:-}" == "1" ]]; then
printf "\033[32m✓\033[0m All targets attempted successfully!\n"
echo " Note: Some language builds failed but were ignored due to --continue-on-failure"
else
printf "\033[32m✓\033[0m All targets built successfully!\n"
fi
echo "Total build time: ${overall_elapsed_formatted}"
return 0
fi
}
show_usage() {
echo "EdgeTX Multi-Language Multi-Variant Build Script"
echo
echo "Usage: $0 [OPTIONS] [TARGET]"
echo
echo "Options:"
echo " -h, --help Show this help message"
echo " -l, --list-targets List all available firmware targets"
echo " -c, --list-color List color screen targets"
echo " -b, --list-bw List black & white screen targets"
echo " -L, --list-languages List all available languages (auto-detected)"
echo " --list-logographic List logographic/character-based languages"
echo " --list-color-targets-detected Show auto-detected color screen targets"
echo " --list-processors List targets grouped by STM32 processor type"
echo " -n, --dry-run Show what would be built without building"
echo " -f, --force-all Force build all languages for target (ignores screen type)"
echo " --continue-on-failure Continue building other languages/targets even after failures"
echo " --color-only Build all color screen targets only"
echo " --bw-only Build all black & white screen targets only"
echo " --processor PROC Build targets with specific STM32 processor (F2|F4|H7)"
echo " --exclude-processor PROC Build targets excluding specific processor"
echo " --filter-processor PROC Filter list commands by processor (F2|F4|H7|exclude-F2|etc.)"
echo
echo "Arguments:"
echo " TARGET [TARGET...] Build all valid languages for specific target(s)"
echo " (if not specified, builds all targets with screen type rules)"
echo
echo "Examples:"
echo " $0 # Build all languages for all targets"
echo " $0 tx16s # Build all languages for TX16S"
echo " $0 tx16s gx12 x9d # Build all languages for multiple targets"
echo " $0 x7 # Build non-logographic languages for X7 (B&W)"
echo " $0 -f x7 # Force build ALL languages for X7 (including CN,JP,KO,TW)"
echo " $0 -f tx16s st16 x9d # Force build ALL languages for multiple targets"
echo " $0 --color-only # Build all color screen targets with appropriate languages"
echo " $0 --bw-only # Build all B&W screen targets (excluding logographic)"
echo " $0 --bw-only -f # Build all B&W screen targets with ALL languages"
echo " $0 --processor F2 # Build all F2 processor targets"
echo " $0 --bw-only --processor F2 # Build F2 B&W targets only"
echo " $0 --exclude-processor F2 # Build all targets except F2"
echo " $0 -l # List all available targets"
echo " $0 -l --filter-processor F4 # List only F4 targets"
echo " $0 -c --filter-processor H7 # List only H7 color screen targets"
echo " $0 -b --filter-processor exclude-F2 # List B&W targets excluding F2"
echo " $0 --list-processors # Show all targets grouped by processor type"
echo " $0 -n x7 # Show what languages would be built for X7"
echo " $0 -n tx16s gx12 # Show what would be built for multiple targets"
echo " $0 --continue-on-failure x7 # Build all languages for X7 even if some fail"
echo " $0 --continue-on-failure --color-only # Build all color targets, continue on failures"
echo
echo "Language Rules:"
echo " - Color screen radios: All languages ($(get_radio_languages))"
echo " - B&W screen radios: All languages except logographic ($(get_logographic_languages))"
echo " - Force mode (-f): All languages regardless of screen type"
echo
echo "Auto-Detection:"
echo " - Languages: Parsed from radio/src/CMakeLists.txt"
echo " - Color targets: Detected from radio/src/targets/*/CMakeLists.txt"
echo " - Logographic languages: Configurable (override with LOGOGRAPHIC_LANGUAGES_OVERRIDE)"
}
# Function to list targets by screen type with optional processor filtering
list_targets_by_type() {
local screen_filter="$1" # "color", "bw", or "all"
local processor_filter="$2" # Optional: "F2", "F4", "H7", "exclude-F2", etc.
echo "Scanning for available targets..." >&2
local all_targets
local color_targets=""
local bw_targets=""
all_targets=$(get_all_targets)
# First, categorize by screen type
for target in $all_targets; do
if is_color_screen_target "$target"; then
color_targets="$color_targets $target"
else
bw_targets="$bw_targets $target"
fi
done
# Apply processor filter if specified
if [[ -n "$processor_filter" ]]; then
local filtered_color_targets=""
local filtered_bw_targets=""
if [[ "$processor_filter" == exclude-* ]]; then
# Exclude specific processor
local exclude_proc="${processor_filter#exclude-}"
for target in $color_targets; do
local target_processor=$(get_stm32_processor_type "$target")
if [[ "$target_processor" != "$exclude_proc" && "$target_processor" != "UNKNOWN" ]]; then
filtered_color_targets="$filtered_color_targets $target"
fi
done
for target in $bw_targets; do
local target_processor=$(get_stm32_processor_type "$target")
if [[ "$target_processor" != "$exclude_proc" && "$target_processor" != "UNKNOWN" ]]; then
filtered_bw_targets="$filtered_bw_targets $target"
fi
done
else
# Include only specific processor
for target in $color_targets; do
local target_processor=$(get_stm32_processor_type "$target")
if [[ "$target_processor" == "$processor_filter" ]]; then
filtered_color_targets="$filtered_color_targets $target"
fi
done
for target in $bw_targets; do
local target_processor=$(get_stm32_processor_type "$target")
if [[ "$target_processor" == "$processor_filter" ]]; then
filtered_bw_targets="$filtered_bw_targets $target"
fi
done
fi
color_targets="$filtered_color_targets"
bw_targets="$filtered_bw_targets"
fi
# Display results based on screen filter
case "$screen_filter" in
"color")
if [[ -n "$processor_filter" ]]; then
echo "Color screen targets with processor filter ($processor_filter):"
else
echo "Color screen targets:"
fi
for target in $color_targets; do
echo " $target ($(get_stm32_processor_type "$target"))"
done
;;
"bw")
if [[ -n "$processor_filter" ]]; then
echo "Black & white screen targets with processor filter ($processor_filter):"
else
echo "Black & white screen targets:"
fi
for target in $bw_targets; do
echo " $target ($(get_stm32_processor_type "$target"))"
done
;;
"all")
if [[ -n "$processor_filter" ]]; then
echo "Color screen targets with processor filter ($processor_filter):"
else
echo "Color screen targets:"
fi
for target in $color_targets; do
echo " $target ($(get_stm32_processor_type "$target"))"
done
echo
if [[ -n "$processor_filter" ]]; then
echo "Black & white screen targets with processor filter ($processor_filter):"
else
echo "Black & white screen targets:"
fi
for target in $bw_targets; do
echo " $target ($(get_stm32_processor_type "$target"))"
done
;;
esac
}
# Function to list targets by processor type
list_targets_by_processor() {
local all_targets=$(get_all_targets)
local f2_targets=""
local f4_targets=""
local h7_targets=""
local unknown_targets=""
for target in $all_targets; do
local processor=$(get_stm32_processor_type "$target")
case "$processor" in
F2) f2_targets="$f2_targets $target" ;;
F4) f4_targets="$f4_targets $target" ;;
H7) h7_targets="$h7_targets $target" ;;
*) unknown_targets="$unknown_targets $target" ;;
esac
done
echo "Targets grouped by STM32 processor type:"
echo
echo "STM32F2 targets:"
for target in $f2_targets; do
echo " $target"
done
echo
echo "STM32F4 targets:"
for target in $f4_targets; do
echo " $target"
done
echo
echo "STM32H7 targets:"
for target in $h7_targets; do
echo " $target"
done
if [[ -n "$unknown_targets" ]]; then
echo
echo "Unknown processor targets:"
for target in $unknown_targets; do
echo " $target"
done
fi
}
# Main script logic
# Parse force flag first
FORCE_ALL_LANGUAGES=0
DRY_RUN=0
CONTINUE_ON_FAILURE=0
TARGET=""
SCREEN_TYPE_FILTER=""
PROCESSOR_FILTER=""
LIST_PROCESSOR_FILTER=""
ACTION=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-l|--list-targets)
ACTION="list-all"
shift
;;
-c|--list-color)
ACTION="list-color"
shift
;;
-b|--list-bw)
ACTION="list-bw"
shift
;;
-L|--list-languages)
ACTION="list-languages"
shift
;;
--list-logographic)
ACTION="list-logographic"
shift
;;
--list-color-targets-detected)
ACTION="list-color-detected"
shift
;;
--list-processors)
ACTION="list-processors"
shift
;;
-n|--dry-run)
DRY_RUN=1
shift
;;
-f|--force-all)
FORCE_ALL_LANGUAGES=1
shift
;;
--continue-on-failure)
CONTINUE_ON_FAILURE=1
shift
;;
--color-only)
SCREEN_TYPE_FILTER="color"
shift
;;
--bw-only)
SCREEN_TYPE_FILTER="bw"
shift
;;
--processor)
if [[ -n "${2:-}" && ! "$2" =~ ^- ]]; then
PROCESSOR_FILTER="$2"
shift 2
else
echo "Error: --processor requires a processor type (F2|F4|H7)"
exit 1
fi
;;
--exclude-processor)
if [[ -n "${2:-}" && ! "$2" =~ ^- ]]; then
PROCESSOR_FILTER="exclude-$2"
shift 2
else
echo "Error: --exclude-processor requires a processor type (F2|F4|H7)"
exit 1
fi
;;
--filter-processor)
if [[ -n "${2:-}" && ! "$2" =~ ^- ]]; then
LIST_PROCESSOR_FILTER="$2"
shift 2
else
echo "Error: --filter-processor requires a processor type (F2|F4|H7|exclude-F2|etc.)"
exit 1
fi
;;
-*)
echo "Unknown option: $1"
show_usage
exit 1
;;
*)
if [[ -z "$TARGET" ]]; then
TARGET="$1"
else
# Support multiple targets - append to TARGET with space separator
TARGET="$TARGET $1"
fi
shift
;;
esac
done
# Execute actions after all arguments are parsed
case "$ACTION" in
"list-all")
list_targets_by_type "all" "$LIST_PROCESSOR_FILTER"
exit 0
;;
"list-color")
list_targets_by_type "color" "$LIST_PROCESSOR_FILTER"
exit 0
;;
"list-bw")
list_targets_by_type "bw" "$LIST_PROCESSOR_FILTER"
exit 0
;;
"list-languages")
echo "All available languages:"
for lang in $(get_radio_languages); do
echo " $lang"
done
exit 0
;;
"list-logographic")
echo "Logographic/character-based languages:"
for lang in $(get_logographic_languages); do
echo " $lang"
done
echo
echo "These languages are not built for B&W screen radios (unless --force-all is used)."
exit 0
;;
"list-color-detected")
echo "Auto-detected color screen targets:"
for target in $(get_color_screen_targets); do
echo " $target"
done
exit 0
;;
"list-processors")
list_targets_by_processor
exit 0
;;
esac
# Export flags for use in functions
export FORCE_ALL_LANGUAGES
export DRY_RUN
export CONTINUE_ON_FAILURE
# Validate processor filter if specified
if [[ -n "$PROCESSOR_FILTER" ]]; then
proc="${PROCESSOR_FILTER#exclude-}"
if [[ "$proc" != "F2" && "$proc" != "F4" && "$proc" != "H7" ]]; then
echo "Error: Invalid processor type '$proc'. Valid options: F2, F4, H7"
exit 1
fi
fi
# Validate list processor filter if specified
if [[ -n "$LIST_PROCESSOR_FILTER" ]]; then
proc="${LIST_PROCESSOR_FILTER#exclude-}"
if [[ "$proc" != "F2" && "$proc" != "F4" && "$proc" != "H7" ]]; then
echo "Error: Invalid filter processor type '$proc'. Valid options: F2, F4, H7, exclude-F2, exclude-F4, exclude-H7"
exit 1
fi
fi
# Handle screen type filtering (--color-only or --bw-only)
if [[ -n "$SCREEN_TYPE_FILTER" ]]; then
if [[ -n "$TARGET" ]]; then
echo "Error: Cannot specify both screen type filter and individual target"
show_usage
exit 1
fi
build_targets_by_screen_type "$SCREEN_TYPE_FILTER" "$PROCESSOR_FILTER"
exit $?
fi
# Handle processor-only filtering (--processor or --exclude-processor without screen filter)
if [[ -n "$PROCESSOR_FILTER" && -z "$SCREEN_TYPE_FILTER" ]]; then
if [[ -n "$TARGET" ]]; then
echo "Error: Cannot specify both processor filter and individual target"
show_usage
exit 1
fi
# Build all targets (both color and B&W) with processor filter
filtered_targets=""
if [[ "$PROCESSOR_FILTER" == exclude-* ]]; then
filtered_targets=$(get_targets_excluding_processor "${PROCESSOR_FILTER#exclude-}")
else
filtered_targets=$(get_targets_by_processor "$PROCESSOR_FILTER")
fi
if [[ -z "$filtered_targets" ]]; then
echo "No targets match the processor filter: $PROCESSOR_FILTER"
exit 0
fi
echo "Building targets with processor filter: $PROCESSOR_FILTER"
echo "Selected targets: $filtered_targets"
echo
failed_targets=0
for target in $filtered_targets; do
echo "========================================"
echo "Processing target: $target ($(get_stm32_processor_type "$target") processor)"
echo "========================================"
if ! build_target_all_languages "$target"; then
((failed_targets++))
echo "Failed to build all languages for target $target"
fi
echo
done
if [[ $failed_targets -gt 0 ]]; then
echo "Warning: $failed_targets targets had build failures"
exit 1
else
echo "All targets built successfully!"
exit 0
fi
fi
# Handle dry-run mode
if [[ $DRY_RUN -eq 1 ]]; then
if [[ -n "$TARGET" ]]; then
all_targets=$(get_all_targets)
# Convert TARGET string to array and validate all targets exist
read -ra target_array <<< "$TARGET"
for target in "${target_array[@]}"; do
if ! echo "$all_targets" | grep -q "^$target$"; then
echo "Error: Unknown target '$target'"
echo
echo "Available targets:"
list_targets_by_type "all"
exit 1
fi
done
# Build all specified targets in dry-run mode
echo "DRY-RUN: Building specified targets: ${target_array[*]}"
echo
for target in "${target_array[@]}"; do
echo "========================================"
echo "Processing target: $target"
echo "========================================"
build_target_all_languages "$target"
echo
done
exit 0
fi
# If no specific target, let it fall through to the default behavior below
# The dry-run logic is handled in build_single_target function
fi
# Handle target building
if [[ -n "$TARGET" ]]; then
all_targets=$(get_all_targets)
# Convert TARGET string to array and validate all targets exist
read -ra target_array <<< "$TARGET"
for target in "${target_array[@]}"; do
if ! echo "$all_targets" | grep -q "^$target$"; then
echo "Error: Unknown target '$target'"
echo
echo "Available targets:"
list_targets_by_type "all"
exit 1
fi
done
# Build all specified targets
failed_targets=0
overall_start_time=$(date +%s)
echo "Building specified targets: ${target_array[*]}"
echo
# Temporarily disable exit on error for target building
set +e
for target in "${target_array[@]}"; do
echo "========================================"
echo "Processing target: $target"
echo "========================================"
if ! build_target_all_languages "$target"; then
((failed_targets++))
echo "Failed to build all languages for target $target"
fi
echo
done
# Re-enable exit on error
set -e
# Calculate overall elapsed time
overall_end_time=$(date +%s)
overall_elapsed=$((overall_end_time - overall_start_time))
# Format overall elapsed time
if [[ $overall_elapsed -ge 3600 ]]; then
overall_elapsed_formatted=$(printf "%dh %dm %ds" $((overall_elapsed / 3600)) $(((overall_elapsed % 3600) / 60)) $((overall_elapsed % 60)))
elif [[ $overall_elapsed -ge 60 ]]; then
overall_elapsed_formatted=$(printf "%dm %ds" $((overall_elapsed / 60)) $((overall_elapsed % 60)))
else
overall_elapsed_formatted="${overall_elapsed}s"
fi
if [[ $failed_targets -gt 0 && "${CONTINUE_ON_FAILURE:-}" != "1" ]]; then
echo "========================================="
echo "BUILD SUMMARY"
echo "========================================="
printf "\033[31m✗\033[0m Warning: %d targets had build failures\n" "$failed_targets"
echo "Total build time: ${overall_elapsed_formatted}"
exit 1
else
echo "========================================="
echo "BUILD SUMMARY"
echo "========================================="
if [[ $failed_targets -gt 0 && "${CONTINUE_ON_FAILURE:-}" == "1" ]]; then
printf "\033[32m✓\033[0m All targets attempted successfully!\n"
echo " Note: Some language builds failed but were ignored due to --continue-on-failure"
else
printf "\033[32m✓\033[0m All targets built successfully!\n"
fi
echo "Total build time: ${overall_elapsed_formatted}"
exit 0
fi
else
# No target specified - build all targets
build_all_targets
exit $?
fi