chrome-ec/util/ide-config.sh

476 lines
12 KiB
Bash
Executable File

#!/bin/bash
# Copyright 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Usage: ./util/ide-config.sh vscode all all:RO | tee .vscode/c_cpp_properties.json
# This tool needs to be run from the base ec directory.
#
# Future works should be put towards adding new IDE generators and adding
# mechanism for passing IDE specific parameters to the IDE generator.
DEFAULT_IMAGE=RW
INDENT_WIDTH=${INDENT_WIDTH:-1}
INDENT_CHAR=${INDENT_CHAR:-$'\t'}
INCLUDES_DROP_ROOT=${INCLUDES_DROP_ROOT:-false}
PARALLEL_CACHE_FILL=${PARALLEL_CACHE_FILL:-true}
FORCE_INCLUDE_CONFIG_H=${FORCE_INCLUDE_CONFIG_H:-false}
# JOB_BATCH_SIZE is the number of jobs allowed to spawn at any given point.
# This is an inefficient manner of resource management, but at least it
# throttles process creation. Due to the low utilization or each job,
# multiply the number of real processors by 2.
JOB_BATCH_SIZE=${JOB_BATCH_SIZE:-$(($(nproc) * 2))}
MAKE_CACHE_DIR=""
init() {
MAKE_CACHE_DIR=$(mktemp -t -d ide-config.XXXX)
mkdir -p "${MAKE_CACHE_DIR}/defines"
mkdir -p "${MAKE_CACHE_DIR}/includes"
trap deinit EXIT
}
deinit() {
rm -rf "${MAKE_CACHE_DIR}"
}
usage() {
cat <<-HEREDOC
Usage: ide-config.sh <vscode|eclipse> [BOARD:IMAGE] [BOARD:IMAGE...]
Generate a C language configuration for a given IDE and EC board.
Examples:
ide-config.sh vscode all:RW all:RO | tee .vscode/c_cpp_properties.json
ide-config.sh vscode nocturne # implicitly :RW
ide-config.sh vscode nocturne_fp:RO
ide-config.sh vscode nocturne:RO hatch:RW
ide-config.sh vscode all # implicitly :RW
ide-config.sh eclipse nocturne_fp > ~/Downloads/nocturne_fp-RW.xml
HEREDOC
}
# Usage: iprintf <indent-level> <printf-fmt> [printf-args...]
iprintf() {
local level=$1
shift
local n=$((INDENT_WIDTH*level))
if [[ $n -ne 0 ]]; then
eval printf '"${INDENT_CHAR}%.0s"' "{1..$n}"
fi
# shellcheck disable=SC2059
printf "$@"
return $?
}
# Usage: parse-cfg-board <cfg-string>
#
# Example: parse-cfg-board nocturne:RW
parse-cfg-board() {
local cfg=$1
# Remove possible :RW or :RO
local board=${cfg%%:*}
if [[ -z ${board} ]]; then
return 1
fi
echo "${board}"
}
# Usage: parse-cfg-image <cfg-string>
#
# Example: parse-cfg-image nocturne:RW
# Example: parse-cfg-image nocturne
parse-cfg-image() {
local cfg=$1
local board
if ! board=$(parse-cfg-board "${cfg}"); then
return 1
fi
# Remove known board part
cfg=${cfg#${board}}
cfg=${cfg#":"}
# Use default image if none set
cfg=${cfg:-${DEFAULT_IMAGE}}
case ${cfg} in
RW|RO)
echo "${cfg}"
return 0
;;
*)
return 1
;;
esac
}
# Usage: make-defines <board> <RO|RW>
make-defines() {
local board=$1
local image=$2
local cache="${MAKE_CACHE_DIR}/defines/${board}-${image}"
if [[ ! -f "${cache}" ]]; then
make print-defines BOARD="${board}" BLD="${image}" >"${cache}"
fi
cat "${cache}"
}
# Usage: make-includes <board> <RO|RW>
#
# Rerun a newline separated list of include directories relative to the ec's
# root directory.
make-includes() {
local board=$1
local image=$2
local cache="${MAKE_CACHE_DIR}/includes/${board}-${image}"
if [[ ! -f "${cache}" ]]; then
make print-includes BOARD="${board}" BLD="${image}" \
| xargs realpath --relative-to=. \
| {
if [[ "${INCLUDES_DROP_ROOT}" == true ]]; then
grep -v "^\.$"
else
cat
fi
} >"${cache}"
fi
cat "${cache}"
}
# Usage: make-boards
make-boards() {
local cache="${MAKE_CACHE_DIR}/boards"
if [[ ! -f "${cache}" ]]; then
make print-boards >"${cache}"
fi
cat "${cache}"
}
# Usage: <newline-list> | join <left> <right> <separator>
#
# JSON: includes nocturne_fp RW | join '"' '"' ',\n'
# C: includes nocturne_fp RW | join '"' '",' '\n'
join() {
local left=$1
local right=$2
local sep=$3
local first=true
while read -r line; do
# JSON is ridiculous for not allowing a trailing ,
if [[ "${first}" == true ]]; then
first=false
else
printf "%b" "${sep}"
fi
printf "%b%s%b" "${left}" "${line}" "${right}"
done
echo
}
# Usage: <content> | encap <header> <footer> [indent]
#
# Encapsulate the content from stdin with a header, footer, and indentation.
encap() {
local header=$1
local footer=$2
local indent=${3:-0}
iprintf "${indent}" "%b" "${header}"
while IFS="" read -r line; do
iprintf $((1+indent)) "%s\n" "${line}"
done
iprintf "${indent}" "%b" "${footer}"
}
##########################################################################
# Usage: vscode [cfg...]
#
# Generate the content for one c_cpp_properties.json that contains
# multiple selectable configurations for each board-image pair.
# In VSCode you can select the config in the bottom right, next to the
# "Select Language Mode". You will only see this option when a C/C++ file
# is open. Additionally, you can select a configuration by pressing
# Ctrl-Shift-P and selecting the "C/C++ Select a Configuration..." option.
vscode() {
local first=true
{
for cfg; do
local board image
if ! board=$(parse-cfg-board "${cfg}"); then
echo "Failed to parse board from cfg '${cfg}'"
return 1
fi
if ! image=$(parse-cfg-image "${cfg}"); then
echo "Failed to parse image from cfg '${cfg}'"
return 1
fi
{
printf '"name": "%s",\n' "${board}:${image}"
make-includes "${board}" "${image}" \
| join '"' '"' ',\n' \
| encap '"includePath": [\n' '],\n'
make-defines "${board}" "${image}" \
| join '"' '"' ',\n' \
| encap '"defines": [\n' '],\n'
if [[ "${FORCE_INCLUDE_CONFIG_H}" == true ]]; then
echo '"include/config.h"' \
| encap '"forcedInclude": [\n' '],\n'
fi
echo '"compilerPath": "/usr/bin/arm-none-eabi-gcc",'
# echo '"compilerArgs": [],'
# The macro __STDC_VERSION__ is 201710L, which corresponds to c18.
# VSCode doesn't have a C18 option, so go with C17. Since we seem
# to use a lot of GNUC features, let's go with gnu17 instead of c17.
echo '"cStandard": "gnu17",'
# echo '"cppStandard": "c++17",'
echo '"intelliSenseMode": "gcc-x64"'
} | {
# A single named configuration
if [[ "${first}" == true ]]; then
encap '{\n' '}'
else
encap ',\n{\n' '}'
fi
}
first=false
done
echo
} \
| {
encap '"configurations": [\n' '],\n'
echo '"version": 4'
} \
| encap '{\n' '}\n'
}
# Usage: eclipse-cdt-import [cfg...]
#
# * Add odd EC "File Types"
# - *.tasklist *.mocklist *.testlist *.irqlist "C Header File"
# - *.inc "C Header File"
# - gpio.wrap "C Header File"
# * Enable the indexer to follow current build configuration
# - Enable "Index all header variants"
# - Enable "Use active build configuration" (ensure this options sticks)
# - Probably bump up the Cache Limit->"Absolute Limit"
# * Remove default system includes
# - Depending on your eclipse toolchain configuration, it may be possible
# to remove the standard C headers.
# This helps satisfy the EC's -nostdinc usage.
eclipse-cdt-import() {
if [[ $# -gt 1 ]]; then
echo "Error - eclipse generator can only process one board cfg" >&2
return 1
fi
local cfg=$1
local board image
board=$(parse-cfg-board "${cfg}")
image=$(parse-cfg-image "${cfg}")
local includes
# Grab workspace/project local paths
includes+="$(make-includes "${board}" "${image}" | grep -v "^\.\." \
| join '<includepath workspace_path="true">' '</includepath>' '\n\t\t\t')"
# Add separator manually
includes+="$(printf "\n\t\t\t")"
# Grab external paths (wil start with .. because forced relative)
# shellcheck disable=SC2016
includes+="$(make-includes "${board}" "${image}" | grep "^\.\." \
| join '<includepath>${ProjDirPath}/' '</includepath>' '\n\t\t\t')"
local macros
macros="$(
while read -r name value; do
{
join '<name>' '</name>' '' <<<"${name}"
join '<value>' '</value>' '' <<<"${value}"
} | encap '<macro>\n' '</macro>\n' 3
done < <(make-defines "${board}" "${image}" | tr '=' '\t')
)"
cat <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated for ${board}:${image} -->
<cdtprojectproperties>
<section name="org.eclipse.cdt.internal.ui.wizards.settingswizards.IncludePaths">
<!-- Start Normal CDT -->
<language name="holder for library settings"></language>
<language name="Assembly">
${includes}
</language>
<language name="GNU C++">
${includes}
</language>
<language name="GNU C">
${includes}
</language>
<!-- End Normal CDT -->
<!-- Start CDT Cross/Embedded -->
<language name="C Source File">
${includes}
</language>
<language name="Object File">
</language>
<language name="Assembly Source File">
${includes}
</language>
<!-- End CDT Cross/Embedded -->
</section>
<section name="org.eclipse.cdt.internal.ui.wizards.settingswizards.Macros">
<!-- Start Normal CDT -->
<language name="holder for library settings"></language>
<language name="Assembly">
${macros}
</language>
<language name="GNU C++">
${macros}
</language>
<language name="GNU C">
${macros}
</language>
<!-- End Normal CDT -->
<!-- Start CDT Cross/Embedded -->
<language name="C Source File">
${macros}
</language>
<language name="Object File">
</language>
<language name="Assembly Source File">
${macros}
</language>
<!-- End CDT Cross/Embedded -->
</section>
</cdtprojectproperties>
EOF
}
# Usage: main <ide> [cfgs...]
main() {
# Disaply help if no args
if [[ $# -lt 1 ]]; then
usage
exit 1
fi
# Search for help flag
for flag; do
case ${flag} in
help|--help|-h)
usage
exit 0
;;
esac
done
local ide=$1
shift
# Expand possible "all" cfgs
local board image
local -a cfgs=( )
for cfg; do
# We parse both board and image to pre-sanatize the input
if ! board=$(parse-cfg-board "${cfg}"); then
echo "Failed to parse board from cfg '${cfg}'" >&2
exit 1
fi
if ! image=$(parse-cfg-image "${cfg}"); then
echo "Failed to parse image from cfg '${cfg}'" >&2
exit 1
fi
# Note "all:*" could be specified multiple times for RO and RW
# Note "all:*" could be specified with other specific board:images
if [[ "${board}" == all ]]; then
local -a allboards=( )
mapfile -t allboards < <(make-boards)
cfgs+=( "${allboards[@]/%/:${image}}" )
else
cfgs+=( "${cfg}" )
fi
done
# Technically, we have not sanitized the cfgs generated from
# "all" expression.
# Make configs unique (and sorted)
mapfile -t cfgs < <(echo "${cfgs[@]}" | tr ' ' '\n' | sort -u)
echo "# Generating a config for the following board:images: " >&2
echo "${cfgs[@]}" | tr ' ' '\n' | sort -u | column >&2
# Prefill the make cache in parallel
# This is important because each make request take about 700ms.
# When running on all boards, this could result in (127*2)*700ms = 3mins.
if [[ "${PARALLEL_CACHE_FILL}" == true ]]; then
echo "# Fetching make defines and includes. Please wait." >&2
# We run into process limits if we launch all processes at the
# same time, so we must split them in half.
# This need some jobserver management.
# Run make for RWs and ROs
local -i jobs=0
for cfg in "${cfgs[@]}"; do
if ! board="$(parse-cfg-board "${cfg}")"; then
echo "Failed to parse board from cfg '${cfg}'" >&2
exit 1
fi
if ! image="$(parse-cfg-image "${cfg}")"; then
echo "Failed to parse image from cfg '${cfg}'" >&2
exit 1
fi
make-defines "${board}" "${image}" >/dev/null &
((++jobs % JOB_BATCH_SIZE == 0)) && wait
make-includes "${board}" "${image}" >/dev/null &
((++jobs % JOB_BATCH_SIZE == 0)) && wait
done
wait
fi
# Run the IDE's generator
case "${ide}" in
vscode)
vscode "${cfgs[@]}" || exit $?
;;
eclipse)
eclipse-cdt-import "${cfgs[@]}" || exit $?
;;
*)
echo "Error - IDE '${ide}' is an unsupported." >&2
exit 1
;;
esac
}
init
# Only start if not being sourced
if [[ "$0" == "${BASH_SOURCE[0]}" ]]; then
main "$@"
fi