blob: 988bc74d90d98d5b8fb94fc5c308bb0adf8cb0dd [file] [log] [blame] [edit]
#!/bin/bash
#
# Copyright (c) 2022 R. Diez - Licensed under the GNU AGPLv3
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
set -o errexit
set -o nounset
set -o pipefail
# set -x # Enable tracing of this script.
declare -r SCRIPT_NAME="GeneratePicolibcCrossFile.sh"
declare -r VERSION_NUMBER="1.02"
declare -r -i EXIT_CODE_SUCCESS=0
declare -r -i EXIT_CODE_ERROR=1
abort ()
{
echo >&2 && echo "Error in script \"$0\": $*" >&2
exit $EXIT_CODE_ERROR
}
display_help ()
{
cat - <<EOF
This script generates a file with the cross-compilation settings needed to build Picolibc.
Copyright (c) 2022 R. Diez - Licensed under the GNU AGPLv3
Syntax:
$SCRIPT_NAME [options]
The generated file contents are sent to stdout.
Options:
--help displays this help text
--version displays this tool's version number (currently $VERSION_NUMBER)
--license prints license information
--target-arch=triplet The GCC cross-compilation triplet. Example: arm-none-eabi
--system=name The system name does not really matter. The default is 'unknown-system'.
--cpu-family=name The Meson build system documents all supported CPU family names.
Example CPU family name: arm
--cpu=name The CPU name does not really matter. The default is 'unknown-cpu'.
Example CPU name: cortex-m3
--endianness=little/big The CPU endianness. Example: 'little'.
--c-compiler=xxx The default value of Meson setting 'c' is <triplet>-gcc.
This option allows you to override it. Specify --c-compiler=xxx multiple
times to add further compiler arguments.
--linker=xxxx Add Meson setting 'c_ld'. Specify --linker=xxx multiple times
to add further linker arguments.
--cflag=xxx Add a C compiler flag for the target. Example: --cflag="-O2"
These flags land in Meson setting 'c_args'.
--lflag=xxx Add a linker flag for the target. Example: --lflag="-L/target/libs"
These flags land in Meson setting 'c_link_args'.
--exewrap=xxx The first time, add Meson setting 'exe_wrapper' with the given
argument, and add Meson setting "needs_exe_wrapper = true".
Subsequent --exewrap=xxx options add further arguments to 'exe_wrapper'.
--meson-compat=0.55 Provides compability with Meson versions up to 0.55.
Usage example:
./$SCRIPT_NAME \\
--target-arch=arm-none-eabi \\
--cpu-family=arm \\
--cpu=cortex-m3 \\
--endianness=little \\
--cflag="-g" \\
--cflag="-O2" \\
>cross-build-settings.txt
If you are calling this script from a GNU Make makefile, and you have a variable with all compiler flags,
you can generate the corresponding --cflags=xxx options like this:
\$(patsubst %,--cflag=%,\$(PICOLIBC_C_FLAGS_FOR_TARGET))
Exit status: 0 means success. Any other value means error.
EOF
}
display_license()
{
cat - <<EOF
Copyright (c) 2022 R. Diez
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version 3 as published by
the Free Software Foundation.
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 Affero General Public License version 3 for more details.
You should have received a copy of the GNU Affero General Public License version 3
along with this program. If not, see L<http://www.gnu.org/licenses/>.
EOF
}
process_command_line_argument ()
{
case "$OPTION_NAME" in
help)
display_help
exit $EXIT_CODE_SUCCESS
;;
version)
echo "$VERSION_NUMBER"
exit $EXIT_CODE_SUCCESS
;;
license)
display_license
exit $EXIT_CODE_SUCCESS
;;
target-arch)
if [[ $OPTARG = "" ]]; then
abort "Option --target-arch has an empty value.";
fi
TARGET_ARCH="$OPTARG"
;;
cpu-family)
if [[ $OPTARG = "" ]]; then
abort "Option --cpu-family has an empty value.";
fi
CPU_FAMILY_NAME="$OPTARG"
;;
cpu)
if [[ $OPTARG = "" ]]; then
abort "Option --cpu has an empty value.";
fi
CPU_NAME="$OPTARG"
;;
system)
if [[ $OPTARG = "" ]]; then
abort "Option --system has an empty value.";
fi
SYSTEM_NAME="$OPTARG"
;;
c-compiler)
if [[ $OPTARG = "" ]]; then
abort "Option --c-compiler has an empty value.";
fi
C_COMPILER+=("$OPTARG")
;;
linker)
if [[ $OPTARG = "" ]]; then
abort "Option --linker has an empty value.";
fi
LINKER+=("$OPTARG")
;;
endianness)
if [[ $OPTARG = "" ]]; then
abort "Option --endianness has an empty value.";
fi
ENDIANNESS="$OPTARG"
;;
cflag)
if [[ $OPTARG = "" ]]; then
abort "Option --cflag has an empty value.";
fi
CFLAGS_FOR_TARGET+=("$OPTARG")
;;
lflag)
if [[ $OPTARG = "" ]]; then
abort "Option --lflag has an empty value.";
fi
LINKER_FLAGS_FOR_TARGET+=("$OPTARG")
;;
exewrap)
if [[ $OPTARG = "" ]]; then
abort "Option --exewrap has an empty value.";
fi
EXE_WRAPPER+=("$OPTARG")
;;
meson-compat)
if [[ $OPTARG = "" ]]; then
abort "Option --meson-compat has an empty value.";
fi
MESON_COMPATIBILITY="$OPTARG"
;;
*) # We should actually never land here, because parse_command_line_arguments() already checks if an option is known.
abort "Unknown command-line option \"--${OPTION_NAME}\".";;
esac
}
parse_command_line_arguments ()
{
# The way command-line arguments are parsed below was originally described on the following page:
# http://mywiki.wooledge.org/ComplexOptionParsing
# But over the years I have rewritten or amended most of the code myself.
if false; then
echo "USER_SHORT_OPTIONS_SPEC: $USER_SHORT_OPTIONS_SPEC"
echo "Contents of USER_LONG_OPTIONS_SPEC:"
for key in "${!USER_LONG_OPTIONS_SPEC[@]}"; do
printf -- "- %s=%s\\n" "$key" "${USER_LONG_OPTIONS_SPEC[$key]}"
done
fi
# The first colon (':') means "use silent error reporting".
# The "-:" means an option can start with '-', which helps parse long options which start with "--".
local MY_OPT_SPEC=":-:$USER_SHORT_OPTIONS_SPEC"
local OPTION_NAME
local OPT_ARG_COUNT
local OPTARG # This is a standard variable in Bash. Make it local just in case.
local OPTARG_AS_ARRAY
while getopts "$MY_OPT_SPEC" OPTION_NAME; do
case "$OPTION_NAME" in
-) # This case triggers for options beginning with a double hyphen ('--').
# If the user specified "--longOpt" , OPTARG is then "longOpt".
# If the user specified "--longOpt=xx", OPTARG is then "longOpt=xx".
if [[ "$OPTARG" =~ .*=.* ]] # With this --key=value format, only one argument is possible.
then
OPTION_NAME=${OPTARG/=*/}
OPTARG=${OPTARG#*=}
OPTARG_AS_ARRAY=("")
if ! test "${USER_LONG_OPTIONS_SPEC[$OPTION_NAME]+string_returned_if_exists}"; then
abort "Unknown command-line option \"--$OPTION_NAME\"."
fi
# Retrieve the number of arguments for this option.
OPT_ARG_COUNT=${USER_LONG_OPTIONS_SPEC[$OPTION_NAME]}
if (( OPT_ARG_COUNT != 1 )); then
abort "Command-line option \"--$OPTION_NAME\" does not take 1 argument."
fi
process_command_line_argument
else # With this format, multiple arguments are possible, like in "--key value1 value2".
OPTION_NAME="$OPTARG"
if ! test "${USER_LONG_OPTIONS_SPEC[$OPTION_NAME]+string_returned_if_exists}"; then
abort "Unknown command-line option \"--$OPTION_NAME\"."
fi
# Retrieve the number of arguments for this option.
OPT_ARG_COUNT=${USER_LONG_OPTIONS_SPEC[$OPTION_NAME]}
if (( OPT_ARG_COUNT == 0 )); then
OPTARG=""
OPTARG_AS_ARRAY=("")
process_command_line_argument
elif (( OPT_ARG_COUNT == 1 )); then
# If this is the last option, and its argument is missing, then OPTIND is out of bounds.
if (( OPTIND > $# )); then
abort "Option '--$OPTION_NAME' expects one argument, but it is missing."
fi
OPTARG="${!OPTIND}"
OPTARG_AS_ARRAY=("")
process_command_line_argument
else
OPTARG=""
# OPTARG_AS_ARRAY is not standard in Bash. I have introduced it to make it clear that
# arguments are passed as an array in this case. It also prevents many Shellcheck warnings.
OPTARG_AS_ARRAY=("${@:OPTIND:OPT_ARG_COUNT}")
if [ ${#OPTARG_AS_ARRAY[@]} -ne "$OPT_ARG_COUNT" ]; then
abort "Command-line option \"--$OPTION_NAME\" needs $OPT_ARG_COUNT arguments."
fi
process_command_line_argument
fi;
((OPTIND+=OPT_ARG_COUNT))
fi
;;
*) # This processes only single-letter options.
# getopts knows all valid single-letter command-line options, see USER_SHORT_OPTIONS_SPEC above.
# If it encounters an unknown one, it returns an option name of '?'.
if [[ "$OPTION_NAME" = "?" ]]; then
abort "Unknown command-line option \"$OPTARG\"."
else
# Process a valid single-letter option.
OPTARG_AS_ARRAY=("")
process_command_line_argument
fi
;;
esac
done
shift $((OPTIND-1))
ARGS=("$@")
}
escape_for_meson_string ()
{
local STR="$1"
local -r RESULT_VAR_NAME="$2"
if [[ $STR =~ [[:cntrl:]] ]]; then
abort "The argument to escape for a meson string contains ASCII control characters."
fi
STR="${STR//\\/\\\\}" # Replace \ with \\
STR="${STR//\'/\\\'}" # Replace ' with \'
printf -v "$RESULT_VAR_NAME" "%s" "$STR"
}
# Examples of the 3 types of output:
#
# c_args = []
#
# c_args = [ '-O2' ]
#
# c_args = [
# '-g',
# '-O2'
# ]
generate_setting_as_meson_array ()
{
local SETTING_NAME="$1"
local -n ARRAY="$2" # Create a reference to the array passed by name.
local -i ARRAY_LEN="${#ARRAY[@]}"
SETTING_AS_MESON_ARRAY="$SETTING_NAME = ["
if (( ARRAY_LEN == 0 )); then
SETTING_AS_MESON_ARRAY+="]"$'\n'
return
fi
local ESCAPED_ARG
if (( ARRAY_LEN == 1 )); then
escape_for_meson_string "${ARRAY[0]}" "ESCAPED_ARG"
SETTING_AS_MESON_ARRAY+=" '${ESCAPED_ARG}' ]"$'\n'
return
fi
SETTING_AS_MESON_ARRAY+=$'\n'
local -i BASE_INDENTATION_LEN="$(( ${#SETTING_NAME} + 3 ))"
local BASE_INDENTATION
printf -v BASE_INDENTATION "%*s" "$BASE_INDENTATION_LEN" ""
local -r EXTRA_INDENTATION=" "
local -i INDEX
for ((INDEX=0; INDEX < ARRAY_LEN; INDEX++)); do
escape_for_meson_string "${ARRAY[$INDEX]}" "ESCAPED_ARG"
SETTING_AS_MESON_ARRAY+="${BASE_INDENTATION}${EXTRA_INDENTATION}'$ESCAPED_ARG'"
if (( INDEX != ARRAY_LEN - 1 )); then
SETTING_AS_MESON_ARRAY+=","
fi
SETTING_AS_MESON_ARRAY+=$'\n'
done
SETTING_AS_MESON_ARRAY+="${BASE_INDENTATION}]"$'\n'
}
# If the command argument array is empty, no setting is generated.
generate_command_setting ()
{
local SETTING_NAME="$1"
local ARRAY_NAME="$2"
local -n ARRAY="$ARRAY_NAME" # Create a reference to the array passed by name.
local -i ARRAY_LEN="${#ARRAY[@]}"
if (( ARRAY_LEN == 0 )); then
COMMAND_SETTING=""
return
fi
if (( ARRAY_LEN == 1 )); then
# We could generate an array with just one element, but a string looks nicer.
# Other sibling settings like 'ar' or 'strip' are usually given as strings too.
local ESCAPED_ARG
escape_for_meson_string "${ARRAY[0]}" "ESCAPED_ARG"
COMMAND_SETTING="$SETTING_NAME = '${ESCAPED_ARG}'"$'\n'
return
fi
local SETTING_AS_MESON_ARRAY
generate_setting_as_meson_array "$SETTING_NAME" "$ARRAY_NAME"
COMMAND_SETTING="$SETTING_AS_MESON_ARRAY"
}
# ----- Entry point -----
USER_SHORT_OPTIONS_SPEC=""
# Use an associative array to declare how many arguments every long option expects.
# All known options must be listed, even those with 0 arguments.
declare -A USER_LONG_OPTIONS_SPEC
USER_LONG_OPTIONS_SPEC+=( [help]=0 )
USER_LONG_OPTIONS_SPEC+=( [version]=0 )
USER_LONG_OPTIONS_SPEC+=( [license]=0 )
USER_LONG_OPTIONS_SPEC+=( [target-arch]=1 )
USER_LONG_OPTIONS_SPEC+=( [cpu-family]=1 )
USER_LONG_OPTIONS_SPEC+=( [cpu]=1 )
USER_LONG_OPTIONS_SPEC+=( [system]=1 )
USER_LONG_OPTIONS_SPEC+=( [endianness]=1 )
USER_LONG_OPTIONS_SPEC+=( [c-compiler]=1 )
USER_LONG_OPTIONS_SPEC+=( [linker]=1 )
USER_LONG_OPTIONS_SPEC+=( [cflag]=1 )
USER_LONG_OPTIONS_SPEC+=( [lflag]=1 )
USER_LONG_OPTIONS_SPEC+=( [exewrap]=1 )
USER_LONG_OPTIONS_SPEC+=( [meson-compat]=1 )
declare -a C_COMPILER=()
declare -a LINKER=()
declare -a CFLAGS_FOR_TARGET=()
declare -a LINKER_FLAGS_FOR_TARGET=()
declare -a EXE_WRAPPER=()
TARGET_ARCH=""
CPU_FAMILY_NAME=""
CPU_NAME=""
ENDIANNESS=""
MESON_COMPATIBILITY=""
# The system name is not really used by Meson.
SYSTEM_NAME="unknown-system"
# The CPU name is not really used by Meson, see:
# https://github.com/mesonbuild/meson/issues/7037
parse_command_line_arguments "$@"
if (( ${#ARGS[@]} != 0 )); then
abort "Invalid number of command-line arguments. Run this tool with the --help option for usage information."
fi
if [[ $TARGET_ARCH = "" ]]; then
abort "Option --target-arch is required."
fi
if [[ $CPU_FAMILY_NAME = "" ]]; then
CPU_FAMILY_NAME=`expr "$TARGET_ARCH" : '\([^-]*\)-.*'`
fi
if [[ $ENDIANNESS = "" ]]; then
case "$CPU_FAMILY_NAME" in
arm*|aarch*|x86*|riscv*)
ENDIANNESS=little
;;
68*|sparc*)
ENDIANNESS=big
;;
*)
abort "Option --endianness is required."
;;
esac
fi
if (( ${#C_COMPILER[@]} == 0)); then
C_COMPILER+=("$TARGET_ARCH-gcc")
fi
if [[ "$CPU_NAME" = "" ]]; then
CPU_NAME="$CPU_FAMILY_NAME"
fi
escape_for_meson_string "$TARGET_ARCH" "ESCAPED_TARGET_ARCH"
escape_for_meson_string "$SYSTEM_NAME" "ESCAPED_SYSTEM_NAME"
escape_for_meson_string "$CPU_FAMILY_NAME" "ESCAPED_CPU_FAMILY_NAME"
escape_for_meson_string "$CPU_NAME" "ESCAPED_CPU_NAME"
escape_for_meson_string "$ENDIANNESS" "ESCAPED_ENDIANNESS"
FILE_CONTENTS=""
FILE_CONTENTS+="[binaries]"$'\n'
generate_command_setting "c" "C_COMPILER"
FILE_CONTENTS+="$COMMAND_SETTING"
generate_command_setting "c_ld" "LINKER"
FILE_CONTENTS+="$COMMAND_SETTING"
set +o errexit # When 'read' reaches end of file, a non-zero status code is returned.
read -r -d '' PART_1 <<EOF
ar = '$ESCAPED_TARGET_ARCH-ar'
as = '$ESCAPED_TARGET_ARCH-as'
nm = '$ESCAPED_TARGET_ARCH-nm'
strip = '$ESCAPED_TARGET_ARCH-strip'
EOF
set -o errexit
FILE_CONTENTS+="$PART_1"$'\n'
generate_command_setting "exe_wrapper" "EXE_WRAPPER"
FILE_CONTENTS+="$COMMAND_SETTING"
set +o errexit # When 'read' reaches end of file, a non-zero status code is returned.
read -r -d '' PART_2 <<EOF
[host_machine]
system = '$ESCAPED_SYSTEM_NAME'
cpu_family = '$ESCAPED_CPU_FAMILY_NAME'
cpu = '$ESCAPED_CPU_NAME'
endian = '$ESCAPED_ENDIANNESS'
[properties]
skip_sanity_check = true
EOF
set -o errexit
FILE_CONTENTS+=$'\n'"$PART_2"$'\n'
if (( ${#EXE_WRAPPER[@]} > 0 )); then
FILE_CONTENTS+="needs_exe_wrapper = true"$'\n'
fi
case "$MESON_COMPATIBILITY" in
'') FILE_CONTENTS+=$'\n'"[built-in options]"$'\n';;
0.55) # Since Meson version 0.56.0, released on 2020-10-30, you get the following warning:
# DEPRECATION: c_args in the [properties] section of the machine file is deprecated, use the [built-in options] section.
# See this commit: https://github.com/mesonbuild/meson/pull/6597
;;
*) abort "Unsupported value of --meson-compat: $MESON_COMPATIBILITY";;
esac
if (( ${#CFLAGS_FOR_TARGET[@]} > 0 )); then
generate_setting_as_meson_array "c_args" "CFLAGS_FOR_TARGET"
FILE_CONTENTS+="$SETTING_AS_MESON_ARRAY"
fi
if (( ${#LINKER_FLAGS_FOR_TARGET[@]} > 0 )); then
generate_setting_as_meson_array "c_link_args" "LINKER_FLAGS_FOR_TARGET"
FILE_CONTENTS+="$SETTING_AS_MESON_ARRAY"
fi
echo -n "$FILE_CONTENTS"