476 lines
12 KiB
Bash
Executable File
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
|