#!/usr/bin/env bash set -e ###### ###### Build and output configuration ###### build_version () { GIT_VERSION="$(cd "$(find_sketch)"; git describe --abbrev=4 --dirty --always)" LIB_VERSION="$(cd "$(find_sketch)"; (grep version= ../../library.properties 2>/dev/null || echo version=0.0.0) | cut -d= -f2)-g${GIT_VERSION}" BUILD_PATH="${BUILD_PATH:-"$(mktemp -d 2>/dev/null || mktemp -d -t 'build')"}" OUTPUT_DIR="${OUTPUT_DIR:-output/${LIBRARY}}" OUTPUT_PATH="${OUTPUT_PATH:-${SOURCEDIR}/${OUTPUT_DIR}}" } build_filenames () { OUTPUT_FILE_PREFIX="${SKETCH}-${LIB_VERSION}" HEX_FILE_PATH="${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.hex" HEX_FILE_WITH_BOOTLOADER_PATH="${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}-with-bootloader.hex" ELF_FILE_PATH="${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.elf" } firmware_size () { if [ "${BOARD}" = "virtual" ]; then echo "[Size not computed for virtual build]" return fi ## This is a terrible hack, please don't hurt me. - algernon MAX_PROG_SIZE=28672 output="$("$@" | grep "\\(Program\\|Data\\):" | sed -e 's,^, - ,' && echo)" PROGSIZE="$(echo "${output}" | grep "Program:" | cut -d: -f2 | awk '{print $1}')" PERCENT="$(echo "${PROGSIZE}" "${MAX_PROG_SIZE}" | awk "{ printf \"%02.01f\", \$1 / \$2 * 100 }")" # we want the sed there, doing with shell builtins would be worse. # shellcheck disable=SC2001 echo "${output}" | sed -e "s/\(Program:.*\)(\([0-9\.]*%\) Full)/\1(${PERCENT}% Full)/" } find_sketch () { SKETCH="${SKETCH:-${DEFAULT_SKETCH}}" LIBRARY="${LIBRARY:-${SKETCH}}" if [ -z "${SKETCH}" ] || [ -z "${LIBRARY}" ] || [ -z "${ROOT}" ] || [ -z "${SOURCEDIR}" ]; then echo "SKETCH, LIBRARY, SOURCEDIR, and ROOT need to be set before including this file!" >&2 exit 1 fi for path in "examples/${LIBRARY}" \ "src" \ "."; do if [ -f "${path}/${SKETCH}.ino" ]; then echo "${path}" return fi done echo "Couldn't find sketch (.ino file)" >&2 exit 1 } prepare_to_flash () { if [ ! -e "${HEX_FILE_PATH}" ]; then compile size fi echo "Press ENTER when ready..." # We do not want to permit line continuations here. We just want a newline. # shellcheck disable=SC2162 read } flash () { prepare_to_flash reset_device sleep 3s find_bootloader_ports flash_over_usb || flash_over_usb } flash_over_usb () { if [ -z "${DEVICE_PORT_BOOTLOADER}" ]; then echo 'DEVICE_PORT_BOOTLOADER is empty (did you hold "Prog" to enable bootloader mode?)' >&2 return 1 fi sleep 1s ${AVRDUDE} -q -q -C "${AVRDUDE_CONF}" -p"${MCU}" -cavr109 -D -P "${DEVICE_PORT_BOOTLOADER}" -b57600 "-Uflash:w:${HEX_FILE_PATH}:i" } flash_from_bootloader() { prepare_to_flash find_bootloader_ports flash_over_usb || flash_over_usb } program() { prepare_to_flash flash_with_programmer } flash_with_programmer() { ${AVRDUDE} -v \ -C "${AVRDUDE_CONF}" \ -p"${MCU}" \ -cusbtiny \ -D \ -B 1 \ "-Uflash:w:${HEX_FILE_PATH}:i" } hex_with_bootloader () { if [ ! -e "${HEX_FILE_PATH}" ]; then compile fi awk '/^:00000001FF/ == 0' "${HEX_FILE_PATH}" > "${HEX_FILE_WITH_BOOTLOADER_PATH}" echo "Using ${BOOTLOADER_PATH}" ${MD5} "${BOOTLOADER_PATH}" cat "${BOOTLOADER_PATH}" >> "${HEX_FILE_WITH_BOOTLOADER_PATH}" ln -sf -- "${HEX_FILE_WITH_BOOTLOADER_PATH}" "${OUTPUT_PATH}/${SKETCH}-latest-with-bootloader.hex" cat <<- EOF Combined firmware and bootloader are now at ${HEX_FILE_WITH_BOOTLOADER_PATH} Make sure you have the bootloader version you expect. And TEST THIS ON REAL HARDWARE BEFORE YOU GIVE IT TO ANYONE EOF } build () { compile "$@" size "$@" } compile () { build_version build_filenames install -d "${OUTPUT_PATH}" echo "Building ${OUTPUT_DIR}/${SKETCH} (${LIB_VERSION}) ..." # This is defined in the (optional) user config. # shellcheck disable=SC2154 ${compile_HOOKS} if [ -d "${ARDUINO_LOCAL_LIB_PATH}/libraries" ]; then # shellcheck disable=SC2089 # We want literal backslashes here, not arrays. local_LIBS="-libraries \"${ARDUINO_LOCAL_LIB_PATH}/libraries\"" fi ARDUINO_PACKAGES="" if [ -d "${ARDUINO_PACKAGE_PATH}" ]; then # shellcheck disable=SC2089 # We want literal backslashes here, not arrays. ARDUINO_PACKAGES="-hardware \"${ARDUINO_PACKAGE_PATH}\"" fi # SC2091: We do not care if quotes or backslashes are not respected. # SC2086: We want word splitting. # shellcheck disable=SC2086,SC2090 ${ARDUINO_BUILDER} \ -compile \ ${ARDUINO_PACKAGES} \ -hardware "${ARDUINO_PATH}/hardware" \ -hardware "${BOARD_HARDWARE_PATH}" \ ${ARDUINO_TOOLS_PARAM} \ -tools "${ARDUINO_PATH}/tools-builder" \ -fqbn "${FQBN}" \ -libraries "." \ -libraries "${ROOT}" \ -libraries "${BOARD_HARDWARE_PATH}/.." \ ${local_LIBS} \ ${EXTRA_BUILDER_ARGS} \ -build-path "${BUILD_PATH}" \ -ide-version "${ARDUINO_IDE_VERSION}" \ -prefs "compiler.cpp.extra_flags=-std=c++11 -Woverloaded-virtual -Wno-unused-parameter -Wno-unused-variable -Wno-ignored-qualifiers ${ARDUINO_CFLAGS} ${LOCAL_CFLAGS}" \ -warnings all \ ${ARDUINO_VERBOSE} \ ${ARDUINO_AVR_GCC_PREFIX_PARAM} \ "$(find_sketch)/${SKETCH}.ino" cp "${BUILD_PATH}/${SKETCH}.ino.hex" "${HEX_FILE_PATH}" cp "${BUILD_PATH}/${SKETCH}.ino.elf" "${ELF_FILE_PATH}" ln -sf "${OUTPUT_FILE_PREFIX}.hex" "${OUTPUT_PATH}/${SKETCH}-latest.hex" ln -sf "${OUTPUT_FILE_PREFIX}.elf" "${OUTPUT_PATH}/${SKETCH}-latest.elf" if [ "${ARDUINO_VERBOSE}" = "-verbose" ]; then echo "Build artifacts can be found in ${BUILD_PATH}"; else rm -rf "${BUILD_PATH}" fi } _find_all () { for plugin in ./*.ino \ examples/* \ src/*.ino; do if [ -d "$(dirname "${plugin}")" ] || [ -f "${plugin}" ]; then p="$(basename "${plugin}" .ino)" if [ "${p}" != '*' ]; then echo "${p}" fi fi done | sort } build_all () { plugins="$(_find_all)" for plugin in ${plugins}; do export SKETCH="${plugin}" export LIBRARY="${plugin}" $0 "${plugin}" build done } size () { if [ ! -e "${HEX_FILE_PATH}" ]; then compile fi echo "- Size: firmware/${LIBRARY}/${OUTPUT_FILE_PREFIX}.elf" firmware_size "${AVR_SIZE}" -C --mcu="${MCU}" "${ELF_FILE_PATH}" echo } size_map () { if [ ! -e "${HEX_FILE_PATH}" ]; then compile fi "${AVR_NM}" --size-sort -C -r -l -t decimal "${ELF_FILE_PATH}" } disassemble () { if [ ! -e "${HEX_FILE_PATH}" ]; then compile fi "${AVR_OBJDUMP}" -C -d "${ELF_FILE_PATH}" } decompile () { disassemble } clean () { rm -rf -- "${OUTPUT_PATH}" } reset_device() { find_device_port check_device_port reset_device_cmd } check_device_port () { if [ -z "$DEVICE_PORT" ]; then cat <&2 Couldn't autodetect the keyboard's serial port. If you see this message and your keyboard is connected to your computer, it probably means that our serial port detection logic is buggy or incomplete. Please report this issue at https://github.com/keyboardio/Kaleidoscope EOF exit 1 elif echo "$DEVICE_PORT" | grep -q '[[:space:]]'; then cat <&2 Unexpected whitespace found in detected serial port: $DEVICE_PORT If you see this message, it means that our serial port detection logic is buggy or incomplete. Please report this issue at https://github.com/keyboardio/Kaleidoscope EOF exit 1 fi if ! [ -w "$DEVICE_PORT" ]; then cat <&2 $DEVICE_PORT is not writable: $(ls -l "$DEVICE_PORT") You are currently in the following groups: $(id -Gn) Please ensure you have followed the instructions on setting up your account to be in the right group: https://github.com/keyboardio/Kaleidoscope/wiki/Install-Arduino-support-on-Linux EOF exit 1 fi } usage () { cat <<- EOF Usage: $0 SKETCH commands... Runs all of the commands in the context of the Sketch. Available commands: help This help screen. compile Compiles the sketch. size Reports the size of the compiled sketch. build Runs compile and report-size. clean Cleans up the output directory. size-map Displays the size map for the sketch. disassemble Decompile the sketch. reset-device Reset the device. flash Flashes the firmware using avrdude. build-all Build all Sketches we can find. EOF } help () { usage } if [ $# -lt 1 ]; then usage exit 1 fi ## Parse the command-line ## - anything that has a =, is an env var ## - from the remaining stuff, the first one is the Library/Sketch ## - everything else are commands ## ## - if there is only one argument, that's a command ROOT="$(cd "$(dirname "$0")"/..; pwd)" export ROOT # shellcheck disable=SC2155 export SOURCEDIR="$(pwd)" if [ -e "${HOME}/.kaleidoscope-builder.conf" ]; then # shellcheck disable=SC1090 . "${HOME}/.kaleidoscope-builder.conf" fi if [ -e "${SOURCEDIR}/.kaleidoscope-builder.conf" ]; then # shellcheck disable=SC1090 . "${SOURCEDIR}/.kaleidoscope-builder.conf" fi if [ -e "${SOURCEDIR}/kaleidoscope-builder.conf" ]; then # shellcheck disable=SC1090 . "${SOURCEDIR}/kaleidoscope-builder.conf" fi # shellcheck disable=SC1090 . "${ROOT}/etc/kaleidoscope-builder.conf" if [ ! -z "${VERBOSE}" ] && [ "${VERBOSE}" -gt 0 ]; then ARDUINO_VERBOSE="-verbose" else ARDUINO_VERBOSE="-quiet" fi cmds="" ## Export vars for i in $(seq 1 $#); do v="$1" shift case "${v}" in *=*) # Exporting an expansion is *precisely* what we want here. # shellcheck disable=SC2163 export ${v} ;; *) cmds="${cmds} ${v}" ;; esac done # Word splitting is desired here. # shellcheck disable=SC2086 set -- ${cmds} if [ $# -eq 1 ]; then cmd="$(echo "$1" | tr '-' '_')" ${cmd} exit $? fi SKETCH="$1" shift if [ "${SKETCH}" = "default" ]; then SKETCH="${DEFAULT_SKETCH}" fi cmds="" # shellcheck disable=2034 for i in $(seq 1 $#); do cmds="${cmds} $(echo "$1" | tr '-' '_')" shift done LIBRARY="${SKETCH}" export SKETCH export LIBRARY for cmd in ${cmds}; do ${cmd} done