You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Kaleidoscope/bin/kaleidoscope-builder

615 lines
17 KiB

#!/usr/bin/env bash
# kaleidoscope-builder - Kaleidoscope helper tool
# Copyright (C) 2017-2018 Keyboard.io, Inc.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
set -e
######
###### Build and output configuration
######
identify_os() {
## Platform-specific overrides
# Shamelessly stolen from git's Makefile
uname_S=$(uname -s 2>/dev/null || echo not)
uname_O=$(uname -o 2>/dev/null || echo not)
}
adjust_for_virtual_build() {
if [ "${uname_S}" = "FreeBSD" ]; then
: "${COMPILER_PATH:=/usr/local/bin/}"
else
: "${COMPILER_PATH:=/usr/bin/}"
fi
COMPILER_PREFIX=""
}
adjust_for_cygwin() {
# The Windows arduino-builder.exe doesn't understand being told to exec against Cygwin symlinks
CCACHE_NOT_SUPPORTED=1
# It's important that all of these be underneath /cygdrive/c so they can be converted to Windows paths that the
# Windows Arduino binaries can understand.
: "${TMPDIR:=/cygdrive/c/Users/${USER}/AppData/Local/Temp}"
# We need to prevent Windows executables from being passed parameters that are absolute paths, since they won't
# be interpretable when of the form /cygdrive/c/foo. To work around this, we set the common path root variables
# to use relative paths instead of absolute paths, since those have mostly platform-agnostic behavior.
#
# Note that this trick requires that all of these paths exist on the same drive letter as the current directory,
# since otherwise even the relative paths would include Cygwin-specific components. So...
if [[ $(realpath --relative-base=/cygdrive/c .) == /* ]]; then
echo "kaleidoscope-builder's Cygwin support is currently limited to running from within /cygdrive/c"
exit 1
fi
TMPDIR="$(realpath --relative-to=./ ${TMPDIR})"
}
absolute_filename() {
echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
}
read_conf_files() {
for conf_file in \
"${HOME}/.kaleidoscope-builder.conf" \
"$(pwd)/.kaleidoscope-builder.conf" \
"$(pwd)/kaleidoscope-builder.conf"; do
if [ -e "${conf_file}" ]; then
# shellcheck disable=SC1090
. "${conf_file}"
fi
done
}
configure_arduino_cli_env() {
SYSTEM_ARDUINO_CLI="$(command -v arduino-cli || true)"
if [ -z "${SYSTEM_ARDUINO_CLI}" ]; then
: "${ARDUINO_CLI:=${KALEIDOSCOPE_BIN_DIR}/arduino-cli}"
else
: "${ARDUINO_CLI:=${SYSTEM_ARDUINO_CLI}}"
fi
: "${ARDUINO_CONTENT:=${KALEIDOSCOPE_DIR}/.arduino}"
: "${ARDUINO_DIRECTORIES_DATA:=${ARDUINO_CONTENT}/data}"
: "${ARDUINO_DIRECTORIES_DOWNLOADS:=${ARDUINO_CONTENT}/downloads}"
: "${ARDUINO_DIRECTORIES_USER:=${ARDUINO_CONTENT}/user}"
: "${ARDUINO_CLI_CONFIG:=${ARDUINO_DIRECTORIES_DATA}/arduino-cli.yaml}"
: "${ARDUINO_BOARDS_MANAGER_KALEIDOSCOPE:=https://raw.githubusercontent.com/keyboardio/boardsmanager/master/package_keyboardio_index.json}"
}
install_arduino_cli() {
# todo cd to kaleidoscope dir
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
}
configure_arduino_cli() {
if [ -z "${ARDUINO_CLI}" ]; then
install_arduino_cli
fi
if [ -z "${ARDUINO_CLI_CONFIG}" ]; then
install_arduino_cli_config
fi
}
install_arduino_cli_config() {
run_arduino_cli config init
}
install_arduino_core_kaleidoscope() {
install_arduino_core_avr
install_arduino_core "keyboardio:avr"
}
install_arduino_core_avr() {
install_arduino_core "arduino:avr"
}
install_arduino_core() {
run_arduino_cli core install "$1"
}
run_arduino_cli() {
ARDUINO_DIRECTORIES_USER=${ARDUINO_DIRECTORIES_USER} \
ARDUINO_DIRECTORIES_DATA=${ARDUINO_DIRECTORIES_DATA} \
ARDUINO_DIRECTORIES_DOWNLOADS=${ARDUINO_DIRECTORIES_DOWNLOADS} \
ARDUINO_BOARD_MANAGER_ADDITIONAL_URLS=${ARDUINO_BOARDS_MANAGER_KALEIDOSCOPE} \
"${ARDUINO_CLI}" "$@"
}
set_executable_paths() {
######
###### Executable paths
######
# Allow the compiler path to be empty for virtual builds
# should use compiler.path instead of appending bin, but we don't have substitution het
: "${COMPILER_PATH=$(_arduino_prop 'runtime.tools.avr-gcc.path')/bin}"
COMPILER_SUFFIX=""
C_COMPILER_BASENAME=$(basename "${CC:-gcc}")
CXX_COMPILER_BASENAME=$(basename "${CXX:-g++}")
AR_BASENAME=$(basename "${AR:-ar}")
# Allow the compiler prefix to be empty for virtual builds
COMPILER_PREFIX="${COMPILER_PREFIX-avr-}"
: "${AVR_OBJDUMP:=${COMPILER_PATH}/${COMPILER_PREFIX}objdump}"
: "${AVR_OBJCOPY:=${COMPILER_PATH}/${COMPILER_PREFIX}objcopy}"
: "${AVR_NM:=${COMPILER_PATH}/${COMPILER_PREFIX}nm}"
: "${AVR_AR:=${COMPILER_PATH}/${COMPILER_PREFIX}ar}"
}
build_version() {
: "${LIB_PROPERTIES_PATH:="../.."}"
GIT_VERSION="$(
cd "${SKETCH_DIR}"
if [ -d .git ]; then echo -n '-g' && git describe --abbrev=4 --dirty --always; fi
)"
LIB_VERSION="$(
cd "${SKETCH_DIR}"
(grep version= "${LIB_PROPERTIES_PATH}/library.properties" 2>/dev/null || echo version=0.0.0) | cut -d= -f2
)${GIT_VERSION}"
}
build_paths() {
# We need that echo because we\re piping to cksum
# shellcheck disable=SC2005
SKETCH_IDENTIFIER="$(echo "${SKETCH_FILE_PATH}" | cksum | cut -d ' ' -f 1)-${SKETCH_FILE_NAME}"
: "${KALEIDOSCOPE_TEMP_PATH:=${TMPDIR:-/tmp}/kaleidoscope-${USER}}"
: "${KALEIDOSCOPE_BUILD_PATH:=${KALEIDOSCOPE_TEMP_PATH}/sketch}"
: "${KALEIDOSCOPE_OUTPUT_PATH:=${KALEIDOSCOPE_TEMP_PATH}/sketch}"
: "${SKETCH_OUTPUT_DIR:=${SKETCH_IDENTIFIER}/output}"
: "${SKETCH_BUILD_DIR:=${SKETCH_IDENTIFIER}/build}"
: "${BUILD_PATH:=${KALEIDOSCOPE_BUILD_PATH}/${SKETCH_BUILD_DIR}}"
: "${OUTPUT_PATH:=${KALEIDOSCOPE_OUTPUT_PATH}/${SKETCH_OUTPUT_DIR}}"
: "${CCACHE_WRAPPER_PATH:=${KALEIDOSCOPE_TEMP_PATH}/ccache/bin}"
: "${CORE_CACHE_PATH:=${KALEIDOSCOPE_TEMP_PATH}/arduino-cores}"
mkdir -p "$CORE_CACHE_PATH"
mkdir -p "$BUILD_PATH"
}
build_filenames() {
: "${OUTPUT_FILE_PREFIX:=${SKETCH_BASE_NAME}-${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}"
: "${LIB_FILE_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.a}"
}
enable_ccache() {
if [ -z "${CCACHE_NOT_SUPPORTED}" ] && [ "$(command -v ccache)" ]; then
if ! [ -d "$CCACHE_WRAPPER_PATH" ]; then
mkdir -p "$CCACHE_WRAPPER_PATH"
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${C_COMPILER_BASENAME}" ]; then
ln -s "$(command -v ccache)" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${C_COMPILER_BASENAME}"
fi
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}" ]; then
ln -s "$(command -v ccache)" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}"
fi
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}nm" ]; then
ln -s "${AVR_NM}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}nm"
fi
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}objcopy" ]; then
ln -s "${AVR_OBJCOPY}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}objcopy"
fi
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}ar" ]; then
ln -s "${AVR_AR}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}ar"
fi
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}size" ]; then
ln -s "${AVR_SIZE}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}size"
fi
fi
export CCACHE_PATH=${COMPILER_PATH}/
CCACHE_ENABLED=1
fi
if [ $CCACHE_ENABLED ]; then
COMPILER_PATH_PROP="${CCACHE_WRAPPER_PATH}/"
else
COMPILER_PATH_PROP="${COMPILER_PATH}"
fi
}
find_sketch() {
SKETCH_DIR="${SKETCH:-$(absolute_filename "$(pwd)")}"
SKETCH_BASE_NAME=$(basename "${SKETCH_DIR}")
SKETCH_FILE_NAME="${SKETCH_BASE_NAME}.ino"
# Arduino sketches are usually a file inside directory Foo named Foo.ino, so try that as a fallback
for dir in \
"${SKETCH_DIR}" \
"src/" \
"."; do
if [ -f "${dir}/${SKETCH_FILE_NAME}" ]; then
SKETCH_DIR="${dir}"
SKETCH_FILE_PATH=$(absolute_filename "${dir}/${SKETCH_FILE_NAME}")
return
fi
done
echo "I couldn't find your sketch (.ino file)" >&2
exit 1
}
find_device_vid_pid() {
: "${VID:=$(_arduino_prop 'build.vid')}"
: "${SKETCH_PID:=$(_arduino_prop 'build.pid')}"
: "${BOOTLOADER_PID:=$(_arduino_prop 'bootloader.pid')}"
: "${BOOTLOADER_VID:=$(_arduino_prop 'bootloader.vid')}"
}
prompt_before_flashing() {
flashing_instructions=$(_arduino_prop 'build.flashing_instructions')
if [ "x${flashing_instructions}x" = "xx" ]; then
flashing_instructions="If your keyboard needs you to do something to put it in flashing mode, do that now."
fi
printf '%b\n\n' "${flashing_instructions}"
echo ""
echo "When you're ready to proceed, press 'Enter'."
# We do not want to permit line continuations here. We just want a newline.
# shellcheck disable=SC2162
read
}
flash() {
compile "$@"
find_device_vid_pid
# Check to see if we can see a keyboard bootloader port.
# If we -can-, then we should skip over the "reset to bootloader" thing
find_bootloader_ports
if [ -z "${DEVICE_PORT_BOOTLOADER}" ]; then
prompt_before_flashing
# This is defined in the (optional) user config.
# shellcheck disable=SC2154
${preFlash_HOOKS}
find_device_port
run_arduino_cli upload \
--fqbn "${FQBN}" \
--port "${DEVICE_PORT}" \
--verbose
fi
run_arduino_cli upload \
--fqbn "${FQBN}" \
--port "${DEVICE_PORT_BOOTLOADER}" \
--verbose
# This is defined in the (optional) user config.
# shellcheck disable=SC2154
${postFlash_HOOKS}
}
hex_with_bootloader() {
compile
: "${BOOTLOADER_PATH:=$(_arduino_prop 'runtime.platform.path')/bootloaders/$(_arduino_prop 'bootloader.file')}"
awk '/^:00000001FF/ == 0' "${HEX_FILE_PATH}" >"${HEX_FILE_WITH_BOOTLOADER_PATH}"
echo "Using ${BOOTLOADER_PATH}"
cat "${BOOTLOADER_PATH}" >>"${HEX_FILE_WITH_BOOTLOADER_PATH}"
ln -sf -- "${OUTPUT_FILE_PREFIX}-with-bootloader.hex" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-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
}
compile() {
find_sketch
set_executable_paths
build_version
build_paths
build_filenames
enable_ccache
install -d "${OUTPUT_PATH}"
echo "Building ${SKETCH_FILE_PATH}"
# This is defined in the (optional) user config.
# shellcheck disable=SC2154
${compile_HOOKS}
SAVED_BOARD="${BOARD}"
SAVED_FQBN="${FQBN}"
if [ -e "${SKETCH_DIR}/.kaleidoscope-builder.conf" ]; then
# shellcheck disable=SC1090
BOARD="$(. "${SKETCH_DIR}"/.kaleidoscope-builder.conf && echo "${BOARD}")"
# shellcheck disable=SC1090
FQBN="$(. "${SKETCH_DIR}"/.kaleidoscope-builder.conf && echo "${FQBN}")"
if [ -n "${BOARD}" ]; then
: "${ARCH:=avr}"
FQBN="keyboardio:${ARCH}:${BOARD}"
fi
fi
_CMD_CXX="${CXX:-${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}${COMPILER_SUFFIX}}"
_CMD_CC="${CC:-${COMPILER_PREFIX}${C_COMPILER_BASENAME}${COMPILER_SUFFIX}}"
_CMD_AR="${AR:-${COMPILER_PREFIX}${AR_BASENAME}${COMPILER_SUFFIX}}"
run_arduino_cli compile \
--fqbn "${FQBN}" \
--libraries "${KALEIDOSCOPE_DIR}/.." \
--build-path "${BUILD_PATH}" \
--output-dir "${OUTPUT_PATH}" \
--build-properties "compiler.path=${COMPILER_PATH_PROP}" \
--build-properties "compiler.c.cmd=${_CMD_CC}" \
--build-properties "compiler.cpp.cmd=${_CMD_CXX}" \
--build-properties "compiler.ar.cmd=${_CMD_AR}" \
--build-properties "compiler.c.elf.cmd=${_CMD_CXX}" \
--build-properties "compiler.cpp.extra_flags=${LOCAL_CFLAGS}" \
--warnings all \
"${SKETCH_FILE_PATH}"
if [ -z "${LIBONLY}" ]; then
cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.hex" "${HEX_FILE_PATH}"
cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.elf" "${ELF_FILE_PATH}"
ln -sf "${OUTPUT_FILE_PREFIX}.hex" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.hex"
ln -sf "${OUTPUT_FILE_PREFIX}.elf" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.elf"
else
cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.a" "${LIB_FILE_PATH}"
ln -sf "${OUTPUT_FILE_PREFIX}.a" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.a"
fi
if [ "${ARDUINO_VERBOSE}" == "--verbose" ]; then
echo "Build artifacts can be found in ${BUILD_PATH}"
fi
BOARD="${SAVED_BOARD}"
FQBN="${SAVED_FQBN}"
}
_arduino_prop() {
pref=$1
# Strip the preference name. And then strip leading and trailing quotations
MESSAGE=$(_arduino_props | grep --max-count=1 "${pref}=" | sed -e s/^.*"${pref}"=// -e 's/^"//' -e 's/"$//')
echo "$MESSAGE"
}
_arduino_props() {
if [ "x${_ARDUINO_PREFS}x" == "xx" ]; then
_ARDUINO_PREFS=$(run_arduino_cli --fqbn "${FQBN}" compile --show-properties "${SKETCH_FILE_PATH}")
fi
echo "$_ARDUINO_PREFS"
}
size_map() {
compile
"${AVR_NM}" --size-sort -C -r -l -t decimal "${ELF_FILE_PATH}"
}
disassemble() {
compile
"${AVR_OBJDUMP}" -C -d "${ELF_FILE_PATH}"
}
clean() {
find_sketch
build_paths
if [ -d "$OUTPUT_PATH" ]; then
rm -rf -- "${OUTPUT_PATH}"
fi
}
find_bootloader_ports() {
if [ -n "${DEVICE_PORT_BOOTLOADER}" ]; then
echo "DEVICE_PORT_BOOTLOADER=\"${DEVICE_PORT_BOOTLOADER}\" predefined."
return
fi
: "${BOOTLOADER_VID:=${VID}}"
if [ "${uname_S}" = "Darwin" ]; then
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-macos"
DEVICE_PORT_BOOTLOADER="$(perl ${DEVICE_PORT_PROBER} ${BOOTLOADER_VID} ${BOOTLOADER_PID})"
elif [ "${uname_O}" = "Cygwin" ]; then
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-windows.ps1"
DEVICE_PORT_BOOTLOADER="$(powershell -noprofile -executionpolicy bypass ${DEVICE_PORT_PROBER} ${BOOTLOADER_VID} ${BOOTLOADER_PID} -Format COM)"
elif [ "${uname_S}" = "FreeBSD" ]; then
DEVICE_PORT_PROBER="${KALEIDOSCPE_BIN_DIR}/find-device-port-freebsd"
DEVICE_PORT_BOOTLOADER="$(perl ${DEVICE_PORT_PROBER})"
else
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-linux-udev"
DEVICE_PORT_BOOTLOADER="$(perl ${DEVICE_PORT_PROBER} ${BOOTLOADER_VID} ${BOOTLOADER_PID})"
fi
}
find_device_port() {
if [ -n "${DEVICE_PORT}" ]; then
echo "DEVICE_PORT=\"${DEVICE_PORT}\" predefined."
return
fi
if [ "${uname_S}" = "Darwin" ]; then
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-macos"
DEVICE_PORT="$(perl ${DEVICE_PORT_PROBER} ${VID} ${SKETCH_PID})"
elif [ "${uname_O}" = "Cygwin" ]; then
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-windows.ps1"
DEVICE_PORT="$(powershell -noprofile -executionpolicy bypass ${DEVICE_PORT_PROBER} ${VID} ${SKETCH_PID} -Format Cygwin)"
DEVICE_COM_PORT="$(powershell -noprofile -executionpolicy bypass ${DEVICE_PORT_PROBER} ${VID} ${SKETCH_PID} -Format COM)"
elif [ "${uname_S}" = "FreeBSD" ]; then
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-freebsd"
DEVICE_PORT="$(perl ${DEVICE_PORT_PROBER})"
else
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-linux-udev"
DEVICE_PORT="$(perl ${DEVICE_PORT_PROBER} ${VID} ${SKETCH_PID})"
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.
clean
Cleans up the output directory.
size-map
Displays the size map for the sketch.
disassemble
Decompile the sketch.
flash
Flashes the firmware using avrdude.
EOF
}
help() {
usage
}
if [ $# -lt 1 ]; then
usage
exit 1
fi
# Temporary migration for old makefiles
# I'm not thrilled about how we default to system Arduino dirs.
if [ -n "${BOARD_HARDWARE_PATH}" ]; then
ARDUINO_DIRECTORIES_USER="${BOARD_HARDWARE_PATH}/../"
fi
: "${KALEIDOSCOPE_DIR:=$(
cd "$(dirname "$0")"/..
pwd
)}"
# shellcheck disable=SC2034
: "${KALEIDOSCOPE_BIN_DIR:=${KALEIDOSCOPE_DIR}/bin/}"
identify_os
read_conf_files
configure_arduino_cli_env
# shellcheck disable=SC1090
if [ -n "${VERBOSE}" ] && [[ "${VERBOSE}" -gt 0 ]]; then
ARDUINO_VERBOSE="--verbose"
else
ARDUINO_VERBOSE="--quiet"
fi
: "${BOARD:=model01}"
if [[ -z "${ARCH}" && -n "${FQBN}" ]]; then
ARCH=$(echo "${FQBN}" | sed -n -e 's/^[^:]\+:\([^:]\+\).*/\1/p')
fi
: "${ARCH:=avr}"
: "${FQBN:=keyboardio:${ARCH}:${BOARD}}"
if [ "${ARCH}" = "virtual" ]; then
adjust_for_virtual_build
fi
if [ "${uname_O}" = "Cygwin" ]; then
adjust_for_cygwin
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
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=SC2086,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
cmds=""
# shellcheck disable=2034
for i in $(seq 1 $#); do
cmds="${cmds} $(echo "$1" | tr '-' '_')"
shift
done
for cmd in ${cmds}; do
${cmd}
done