qmk_firmare/bin/fp_build.sh

394 lines
13 KiB
Bash
Executable File

#!/bin/bash
# error handling
if ! command -v jq &> /dev/null
then
echo "jq is a dependency, and could not be found, see https://stedolan.github.io/jq for installation details"
exit
fi
Help()
{
echo "Usage: $0 -k <keyboard> -m <keymap> -c <convert_to controller> -r -h"
echo ""
echo " -l list valid keyboards (optional, overrides all other options)"
echo " -k keyboard directory (optional, default is all fingerpunch keyboards)"
echo " -m keymap (optional, defaults to the 'default' keymap)"
echo " -c add CONVERT_TO parameter for a controller (eg -c stemcell)"
echo " -i (interactive mode, take feature selection user input to generate build command)"
echo " -r (run the build command(s), defaults to outputting the build string)"
echo " -e (add environment variables, only used in interactive mode, e.g. RGB_MATRIX_REACTIVE_LAYERS=yes or -e \"RGB_LED_RING=yes RGBLIGHT_SNAKE_LAYERS=yes\")"
echo " -h (show this dialog)"
echo ""
echo "Examples: "
echo "--------"
echo "fp_build.sh -i -k \"rockon/v2\" -m sadekbaroudi -r"
echo "fp_build.sh -i -m sadekbaroudi"
echo "fp_build.sh -k \"barobord\""
}
get_valid_keyboards() {
valid_keyboards=""
directories=$(find ${1}/* -maxdepth 0 -type d)
echo "${directories}" | while read line; do
# first we do a basic test to see if fp_build.json is in the keyboard root directory
if [[ -e "${line}/fp_build.json" ]]; then
echo -n "${line} "
fi
# check for all the supported versions of the keyboard in the keyboard root directory
for i in {0..9}
do
if [[ -e "${line}/v${i}/fp_build.json" ]]; then
echo -n "${line}/v${i} "
fi
# handle format with subversions, like v3_1
for j in {0..9}
do
if [[ -e "${line}/v${i}_${j}/fp_build.json" ]]; then
echo -n "${line}/v${i}_${j} "
fi
done
# special case for pinkies out v2 extended
if [[ -e "${line}/v${i}_ext/fp_build.json" ]]; then
echo -n "${line}/v${i}_ext "
fi
done
# special case for tenbit
for i in {4..5}
do
if [[ -e "${line}/${i}x12/fp_build.json" ]]; then
echo -n "${line}/${i}x12 "
fi
done
# special case for vulpes minora byomcu
if [[ -e "${line}/byomcu/fp_build.json" ]]; then
echo -n "${line}/byomcu "
fi
# special case for vulpes minora rp2040zero
if [[ -e "${line}/rp2040zero" ]]; then
echo -n "${line}/rp2040zero "
fi
# special case for vulpes minora xivik
if [[ -e "${line}/xivik" ]]; then
echo -n "${line}/xivik "
fi
# if we have a second parameter, then we don't want to recurse again
if [ "$#" -lt 2 ]; then
# now check for byomcu version, repeating the logic above
if [[ -e "${line}/byomcu" ]]; then
echo $(get_valid_keyboards "${line}/byomcu" "false")
fi
# now check for atmega version
if [[ -e "${line}/atmega" ]]; then
echo $(get_valid_keyboards "${line}/atmega" "false")
fi
# now check for rp2040 version
if [[ -e "${line}/rp" ]]; then
echo $(get_valid_keyboards "${line}/rp" "false")
fi
# now check for stm version
if [[ -e "${line}/stm" ]]; then
echo $(get_valid_keyboards "${line}/stm" "false")
fi
fi
done
}
build_keyboard_user_input() {
local build_json="${1}/fp_build.json"
local keyboard_base_dir="${1}"
local keyboard_name="${1#${2}/}"
local run_build="${4}"
local build_string="make ${keyboard_base_dir#keyboards\/}:${3}"
echo "${build_string}"
# get the total number of paramters
top_level_element_count=$(cat "${build_json}" | jq 'length')
# loop through each parameter
for ((param_iter = 0 ; param_iter < top_level_element_count ; param_iter++)); do
# get the parameter type to decide how to handle it
param_type=$(cat "${build_json}" | jq -r ".[${param_iter}].type")
# get the string to present to the user for their input
user_input_string=$(cat "${build_json}" | jq -r ".[${param_iter}].user_input")
# if it's a "one-of"... in other words, pick an option from a list
if [[ "${param_type}" == "one-of" ]]; then
options_count=$(cat "${build_json}" | jq ".[${param_iter}].names | length")
user_input_string+=" (0-${options_count}): "
echo -n "${user_input_string}"
read user_choice
while [[ $user_choice -lt 0 || $user_choice -gt $options_count ]]; do
echo "Invalid choice: ${user_choice}"
echo -n "${user_input_string}"
read user_choice
done
# start at 1, because 0 should always be "none"
param_names_counter=1
param_names=$(cat "${build_json}" | jq -r ".[${param_iter}].names | @sh" | tr -d \')
for param_name in $param_names; do
if [[ $param_names_counter -eq $user_choice ]]; then
build_string+=" ${param_name}=yes"
fi
((param_names_counter+=1))
done
# if it's a single value choice for a parmeter, or "yes or no" question
elif [[ "${param_type}" == "single" ]]; then
user_input_string+=" (yes/no): "
echo -n "${user_input_string}"
read user_choice
while [[ $user_choice != "yes" && $user_choice != "no" && $user_choice != "y" && $user_choice != "n" ]]; do
echo "Invalid choice: ${user_choice}"
echo -n "${user_input_string}"
read user_choice
done
if [[ "${user_choice}" == "y" ]]; then
user_choice="yes"
fi
if [[ "${user_choice}" == "n" ]]; then
user_choice="no"
fi
param_name=$(cat "${build_json}" | jq -r ".[${param_iter}].name")
build_string+=" ${param_name}=${user_choice}"
elif [[ "${param_type}" == "convert-to" ]]; then
# Do nothing, we can skip this for interactive mode
param_name=$(cat "${build_json}" | jq -r ".[${param_iter}].name")
else
echo "invalid type in json file: ${param_type}"
exit
fi
done
if [[ -n "${5}" && "${5}" != "no" ]]; then
build_string+=" CONVERT_TO=${5}"
fi
if [[ -n "${6}" && "${6}" != "no" ]]; then
build_string+=" ${6}"
fi
process_build_string "${build_string}" "${run_build}"
}
build_keyboard_all_combinations() {
local build_json="${1}/fp_build.json"
local keyboard_base_dir="${1}"
local keyboard_name="${1#${2}/}"
local run_build="${4}"
local build_string_base="make ${keyboard_base_dir#keyboards\/}:${3}"
if [[ -n "${5}" && "${5}" != "no" ]]; then
build_string_base+=" CONVERT_TO=${5}"
fi
make_build_string_recursive "${build_json}" "${run_build}" 0 "${build_string_base}"
}
# make_build_string_recursive "${build_json}" "${run_build}" "${param_number}" "${build_string_base}"
make_build_string_recursive() {
local build_json="${1}"
local run_build="${2}"
local param_number=$3
local build_string_base="${4}"
local top_level_element_count=$(cat "${build_json}" | jq 'length')
if [[ $((param_number)) -ge $((top_level_element_count)) ]]; then
process_build_string "${build_string_base}" "${run_build}"
return;
fi
local param_type=$(cat "${build_json}" | jq -r ".[${param_number}].type")
local next_param_number=$((param_number + 1))
# if it's a "one-of"... in other words, pick an option from a list
if [[ "${param_type}" == "one-of" ]]; then
local param_names=$(cat "${build_json}" | jq -r ".[${param_number}].names | @sh" | tr -d \')
for param_name in $param_names; do
make_build_string_recursive "${build_json}" "${run_build}" $next_param_number "${build_string_base} ${param_name}=yes"
done
# if it's a single value choice for a parmeter, or "yes or no" question
elif [[ "${param_type}" == "single" ]]; then
local param_name=$(cat "${build_json}" | jq -r ".[${param_number}].name")
make_build_string_recursive "${build_json}" "${run_build}" $next_param_number "${build_string_base} ${param_name}=yes"
make_build_string_recursive "${build_json}" "${run_build}" $next_param_number "${build_string_base} ${param_name}=no"
elif [[ "${param_type}" == "convert-to" ]]; then
local param_name=$(cat "${build_json}" | jq -r ".[${param_number}].name")
make_build_string_recursive "${build_json}" "${run_build}" $next_param_number "${build_string_base} CONVERT_TO=${param_name}"
else
echo "invalid type in json file: ${param_type}"
exit
fi
}
# rename_file_from_build_string $build_string $target_filename_suffix
rename_file_from_build_string() {
tokens=( $1 )
target_filename_suffix=""
if [[ ! -z "${2}" ]]; then
target_filename_suffix="_${2}"
fi
# Calculate the qmk build filename prefix to move it
token_file_prefix="${tokens[1]//\//_}"
token_file_prefix="${token_file_prefix//:/_}"
# Start the new filename, which will be appended to below
target_filename="${token_file_prefix}"
# check if token_i>1
# check if first paramter is CONVERT_TO, then grab other side of = (stemcell for example)
# otherwise grab first parameter (RGBLIGHT_ENABLE for example)
# rename file accordingly
token_i=0
for token in "${tokens[@]}"
do
if [[ $token_i -gt 1 ]]; then
config_param="${token%%=*}"
config_value="${token#*=}"
if [[ "${config_param}" == "CONVERT_TO" ]]; then
token_file_prefix+="_${config_value}"
target_filename+="_${config_value}"
else
# Make sure that the value is yes (it's enabled), otherwise we shouldn't include in the filename
if [[ "${config_value}" == "yes" ]]; then
# ,, converts to lowercase
target_filename+="_${config_param,,}"
# remove _enable suffix as it's implied
target_filename=${target_filename%"_enable"}
fi
fi
fi
token_i+=1
done
echo "${0}: filename token is ${token_file_prefix}"
echo "${0}: target filename is ${target_filename}"
hex_source_file="${token_file_prefix}.hex"
uf2_source_file="${token_file_prefix}.uf2"
hex_target_file="${target_filename}${target_filename_suffix}.hex"
uf2_target_file="${target_filename}${target_filename_suffix}.uf2"
if test -f "${hex_source_file}"; then
echo "${0}: Renaming file '${hex_source_file}' to '${hex_target_file}'"
if [ "${hex_source_file}" = "${hex_target_file}" ]; then
echo "${0}: Skipping rename, since the file is already named appropriately"
else
mv "${hex_source_file}" "${hex_target_file}"
fi
else
echo "${0}: Could not find hex source file ${hex_source_file} to rename."
fi
if test -f "${uf2_source_file}"; then
echo "${0}: Renaming file '${uf2_source_file}' to '${uf2_target_file}'"
if [ "${uf2_source_file}" = "${uf2_target_file}" ]; then
echo "${0}: Skipping rename, since the file is already named appropriately"
else
mv "${uf2_source_file}" "${uf2_target_file}"
fi
else
echo "${0}: Could not find uf2 source file ${uf2_source_file} to rename."
fi
}
# process_build_string $build_string $run_build
process_build_string() {
build_string="$1"
run_build="$2"
append_to_filename=""
echo "${build_string}"
if [[ "${run_build}" == "yes" ]]; then
echo "fp_build.sh: Running QMK Build...."
echo ""
build_run_output=`${build_string}`
build_run_status=$?
echo "output: ${build_run_output}"
echo "exit status: ${build_run_status}"
if [[ $build_run_status -ne 0 ]]; then
# if the firmware is too large, we proceed, but append to filename, otherwise we error out
if [[ "${build_run_output}" == *"The firmware is too large"* ]]; then
append_to_filename="FIRMWARE_SIZE_CHECK_FAILED"
else
echo "${0} build run failed with status ${build_run_status}"
exit $build_run_status
fi
fi
rename_file_from_build_string "${build_string}" "${append_to_filename}"
fi
}
Keyboard=""
Keymap="default"
RunBuild="no"
ConvertTo="no"
Interactive="no"
ListKeyboards="no"
EnvVariables="no"
while getopts "k:m:c:e:rhil" option; do
case $option in
l) ListKeyboards="yes";;
k) Keyboard=${OPTARG};;
m) Keymap=${OPTARG};;
c) ConvertTo=${OPTARG};;
i) Interactive="yes";;
r) RunBuild="yes";;
e) EnvVariables="${OPTARG}";;
h) Help
exit;;
esac
done
#### code starts here
# set up variables
FP_KB_DIR="keyboards/fingerpunch"
FP_KB=("${Keyboard}")
if [[ -z "${Keyboard}" || "${ListKeyboards}" == "yes" ]]; then
FP_KB=$(get_valid_keyboards "${FP_KB_DIR}")
else
FP_KB=("${FP_KB_DIR}/${FP_KB}")
if [[ ! -e "${FP_KB}" ]]; then
echo "${FP_KB} is not a valid file, can't build"
exit
fi
fi;
if [[ "${ListKeyboards}" == "yes" ]]; then
# remove last space from FP_KB and convert spaces to newlines
echo "${FP_KB%?}" | tr " " "\n"
else
for filename in $FP_KB; do
if [[ "${Interactive}" == "yes" ]]; then
echo "Running for ${filename}"
build_keyboard_user_input "${filename}" "${FP_KB_DIR}" "${Keymap}" "${RunBuild}" "${ConvertTo}" "${EnvVariables}"
else
build_keyboard_all_combinations "${filename}" "${FP_KB_DIR}" "${Keymap}" "${RunBuild}" "${ConvertTo}" ""
fi
done
fi