1#!/bin/bash 2# 3# Copyright (c) 2022 R. Diez - Licensed under the GNU AGPLv3 4# 5# This program is free software: you can redistribute it and/or modify 6# it under the terms of the GNU Affero General Public License as 7# published by the Free Software Foundation, either version 3 of the 8# License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU Affero General Public License for more details. 14# 15# You should have received a copy of the GNU Affero General Public License 16# along with this program. If not, see <https://www.gnu.org/licenses/>. 17# 18 19set -o errexit 20set -o nounset 21set -o pipefail 22 23# set -x # Enable tracing of this script. 24 25declare -r SCRIPT_NAME="GeneratePicolibcCrossFile.sh" 26declare -r VERSION_NUMBER="1.02" 27 28declare -r -i EXIT_CODE_SUCCESS=0 29declare -r -i EXIT_CODE_ERROR=1 30 31 32abort () 33{ 34 echo >&2 && echo "Error in script \"$0\": $*" >&2 35 exit $EXIT_CODE_ERROR 36} 37 38 39display_help () 40{ 41cat - <<EOF 42 43This script generates a file with the cross-compilation settings needed to build Picolibc. 44 45Copyright (c) 2022 R. Diez - Licensed under the GNU AGPLv3 46 47Syntax: 48 $SCRIPT_NAME [options] 49 50The generated file contents are sent to stdout. 51 52Options: 53 --help displays this help text 54 --version displays this tool's version number (currently $VERSION_NUMBER) 55 --license prints license information 56 --target-arch=triplet The GCC cross-compilation triplet. Example: arm-none-eabi 57 --system=name The system name does not really matter. The default is 'unknown-system'. 58 --cpu-family=name The Meson build system documents all supported CPU family names. 59 Example CPU family name: arm 60 --cpu=name The CPU name does not really matter. The default is 'unknown-cpu'. 61 Example CPU name: cortex-m3 62 --endianness=little/big The CPU endianness. Example: 'little'. 63 --c-compiler=xxx The default value of Meson setting 'c' is <triplet>-gcc. 64 This option allows you to override it. Specify --c-compiler=xxx multiple 65 times to add further compiler arguments. 66 --linker=xxxx Add Meson setting 'c_ld'. Specify --linker=xxx multiple times 67 to add further linker arguments. 68 --cflag=xxx Add a C compiler flag for the target. Example: --cflag="-O2" 69 These flags land in Meson setting 'c_args'. 70 --lflag=xxx Add a linker flag for the target. Example: --lflag="-L/target/libs" 71 These flags land in Meson setting 'c_link_args'. 72 --exewrap=xxx The first time, add Meson setting 'exe_wrapper' with the given 73 argument, and add Meson setting "needs_exe_wrapper = true". 74 Subsequent --exewrap=xxx options add further arguments to 'exe_wrapper'. 75 --meson-compat=0.55 Provides compability with Meson versions up to 0.55. 76 77Usage example: 78 ./$SCRIPT_NAME \\ 79 --target-arch=arm-none-eabi \\ 80 --cpu-family=arm \\ 81 --cpu=cortex-m3 \\ 82 --endianness=little \\ 83 --cflag="-g" \\ 84 --cflag="-O2" \\ 85 >cross-build-settings.txt 86 87If you are calling this script from a GNU Make makefile, and you have a variable with all compiler flags, 88you can generate the corresponding --cflags=xxx options like this: 89 \$(patsubst %,--cflag=%,\$(PICOLIBC_C_FLAGS_FOR_TARGET)) 90 91Exit status: 0 means success. Any other value means error. 92 93EOF 94} 95 96 97display_license() 98{ 99cat - <<EOF 100 101Copyright (c) 2022 R. Diez 102 103This program is free software: you can redistribute it and/or modify 104it under the terms of the GNU Affero General Public License version 3 as published by 105the Free Software Foundation. 106 107This program is distributed in the hope that it will be useful, 108but WITHOUT ANY WARRANTY; without even the implied warranty of 109MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 110GNU Affero General Public License version 3 for more details. 111 112You should have received a copy of the GNU Affero General Public License version 3 113along with this program. If not, see L<http://www.gnu.org/licenses/>. 114 115EOF 116} 117 118 119process_command_line_argument () 120{ 121 case "$OPTION_NAME" in 122 help) 123 display_help 124 exit $EXIT_CODE_SUCCESS 125 ;; 126 127 version) 128 echo "$VERSION_NUMBER" 129 exit $EXIT_CODE_SUCCESS 130 ;; 131 132 license) 133 display_license 134 exit $EXIT_CODE_SUCCESS 135 ;; 136 137 target-arch) 138 if [[ $OPTARG = "" ]]; then 139 abort "Option --target-arch has an empty value."; 140 fi 141 TARGET_ARCH="$OPTARG" 142 ;; 143 144 cpu-family) 145 if [[ $OPTARG = "" ]]; then 146 abort "Option --cpu-family has an empty value."; 147 fi 148 CPU_FAMILY_NAME="$OPTARG" 149 ;; 150 151 cpu) 152 if [[ $OPTARG = "" ]]; then 153 abort "Option --cpu has an empty value."; 154 fi 155 CPU_NAME="$OPTARG" 156 ;; 157 158 system) 159 if [[ $OPTARG = "" ]]; then 160 abort "Option --system has an empty value."; 161 fi 162 SYSTEM_NAME="$OPTARG" 163 ;; 164 165 c-compiler) 166 if [[ $OPTARG = "" ]]; then 167 abort "Option --c-compiler has an empty value."; 168 fi 169 C_COMPILER+=("$OPTARG") 170 ;; 171 172 linker) 173 if [[ $OPTARG = "" ]]; then 174 abort "Option --linker has an empty value."; 175 fi 176 LINKER+=("$OPTARG") 177 ;; 178 179 endianness) 180 if [[ $OPTARG = "" ]]; then 181 abort "Option --endianness has an empty value."; 182 fi 183 ENDIANNESS="$OPTARG" 184 ;; 185 186 cflag) 187 if [[ $OPTARG = "" ]]; then 188 abort "Option --cflag has an empty value."; 189 fi 190 CFLAGS_FOR_TARGET+=("$OPTARG") 191 ;; 192 193 lflag) 194 if [[ $OPTARG = "" ]]; then 195 abort "Option --lflag has an empty value."; 196 fi 197 LINKER_FLAGS_FOR_TARGET+=("$OPTARG") 198 ;; 199 200 exewrap) 201 if [[ $OPTARG = "" ]]; then 202 abort "Option --exewrap has an empty value."; 203 fi 204 EXE_WRAPPER+=("$OPTARG") 205 ;; 206 207 meson-compat) 208 if [[ $OPTARG = "" ]]; then 209 abort "Option --meson-compat has an empty value."; 210 fi 211 MESON_COMPATIBILITY="$OPTARG" 212 ;; 213 214 *) # We should actually never land here, because parse_command_line_arguments() already checks if an option is known. 215 abort "Unknown command-line option \"--${OPTION_NAME}\".";; 216 esac 217} 218 219 220parse_command_line_arguments () 221{ 222 # The way command-line arguments are parsed below was originally described on the following page: 223 # http://mywiki.wooledge.org/ComplexOptionParsing 224 # But over the years I have rewritten or amended most of the code myself. 225 226 if false; then 227 echo "USER_SHORT_OPTIONS_SPEC: $USER_SHORT_OPTIONS_SPEC" 228 echo "Contents of USER_LONG_OPTIONS_SPEC:" 229 for key in "${!USER_LONG_OPTIONS_SPEC[@]}"; do 230 printf -- "- %s=%s\\n" "$key" "${USER_LONG_OPTIONS_SPEC[$key]}" 231 done 232 fi 233 234 # The first colon (':') means "use silent error reporting". 235 # The "-:" means an option can start with '-', which helps parse long options which start with "--". 236 local MY_OPT_SPEC=":-:$USER_SHORT_OPTIONS_SPEC" 237 238 local OPTION_NAME 239 local OPT_ARG_COUNT 240 local OPTARG # This is a standard variable in Bash. Make it local just in case. 241 local OPTARG_AS_ARRAY 242 243 while getopts "$MY_OPT_SPEC" OPTION_NAME; do 244 245 case "$OPTION_NAME" in 246 247 -) # This case triggers for options beginning with a double hyphen ('--'). 248 # If the user specified "--longOpt" , OPTARG is then "longOpt". 249 # If the user specified "--longOpt=xx", OPTARG is then "longOpt=xx". 250 251 if [[ "$OPTARG" =~ .*=.* ]] # With this --key=value format, only one argument is possible. 252 then 253 254 OPTION_NAME=${OPTARG/=*/} 255 OPTARG=${OPTARG#*=} 256 OPTARG_AS_ARRAY=("") 257 258 if ! test "${USER_LONG_OPTIONS_SPEC[$OPTION_NAME]+string_returned_if_exists}"; then 259 abort "Unknown command-line option \"--$OPTION_NAME\"." 260 fi 261 262 # Retrieve the number of arguments for this option. 263 OPT_ARG_COUNT=${USER_LONG_OPTIONS_SPEC[$OPTION_NAME]} 264 265 if (( OPT_ARG_COUNT != 1 )); then 266 abort "Command-line option \"--$OPTION_NAME\" does not take 1 argument." 267 fi 268 269 process_command_line_argument 270 271 else # With this format, multiple arguments are possible, like in "--key value1 value2". 272 273 OPTION_NAME="$OPTARG" 274 275 if ! test "${USER_LONG_OPTIONS_SPEC[$OPTION_NAME]+string_returned_if_exists}"; then 276 abort "Unknown command-line option \"--$OPTION_NAME\"." 277 fi 278 279 # Retrieve the number of arguments for this option. 280 OPT_ARG_COUNT=${USER_LONG_OPTIONS_SPEC[$OPTION_NAME]} 281 282 if (( OPT_ARG_COUNT == 0 )); then 283 OPTARG="" 284 OPTARG_AS_ARRAY=("") 285 process_command_line_argument 286 elif (( OPT_ARG_COUNT == 1 )); then 287 # If this is the last option, and its argument is missing, then OPTIND is out of bounds. 288 if (( OPTIND > $# )); then 289 abort "Option '--$OPTION_NAME' expects one argument, but it is missing." 290 fi 291 OPTARG="${!OPTIND}" 292 OPTARG_AS_ARRAY=("") 293 process_command_line_argument 294 else 295 OPTARG="" 296 # OPTARG_AS_ARRAY is not standard in Bash. I have introduced it to make it clear that 297 # arguments are passed as an array in this case. It also prevents many Shellcheck warnings. 298 OPTARG_AS_ARRAY=("${@:OPTIND:OPT_ARG_COUNT}") 299 300 if [ ${#OPTARG_AS_ARRAY[@]} -ne "$OPT_ARG_COUNT" ]; then 301 abort "Command-line option \"--$OPTION_NAME\" needs $OPT_ARG_COUNT arguments." 302 fi 303 304 process_command_line_argument 305 fi; 306 307 ((OPTIND+=OPT_ARG_COUNT)) 308 fi 309 ;; 310 311 *) # This processes only single-letter options. 312 # getopts knows all valid single-letter command-line options, see USER_SHORT_OPTIONS_SPEC above. 313 # If it encounters an unknown one, it returns an option name of '?'. 314 if [[ "$OPTION_NAME" = "?" ]]; then 315 abort "Unknown command-line option \"$OPTARG\"." 316 else 317 # Process a valid single-letter option. 318 OPTARG_AS_ARRAY=("") 319 process_command_line_argument 320 fi 321 ;; 322 esac 323 done 324 325 shift $((OPTIND-1)) 326 ARGS=("$@") 327} 328 329 330escape_for_meson_string () 331{ 332 local STR="$1" 333 local -r RESULT_VAR_NAME="$2" 334 335 if [[ $STR =~ [[:cntrl:]] ]]; then 336 abort "The argument to escape for a meson string contains ASCII control characters." 337 fi 338 339 STR="${STR//\\/\\\\}" # Replace \ with \\ 340 STR="${STR//\'/\\\'}" # Replace ' with \' 341 342 printf -v "$RESULT_VAR_NAME" "%s" "$STR" 343} 344 345 346# Examples of the 3 types of output: 347# 348# c_args = [] 349# 350# c_args = [ '-O2' ] 351# 352# c_args = [ 353# '-g', 354# '-O2' 355# ] 356 357generate_setting_as_meson_array () 358{ 359 local SETTING_NAME="$1" 360 local -n ARRAY="$2" # Create a reference to the array passed by name. 361 362 local -i ARRAY_LEN="${#ARRAY[@]}" 363 364 SETTING_AS_MESON_ARRAY="$SETTING_NAME = [" 365 366 if (( ARRAY_LEN == 0 )); then 367 SETTING_AS_MESON_ARRAY+="]"$'\n' 368 return 369 fi 370 371 local ESCAPED_ARG 372 373 if (( ARRAY_LEN == 1 )); then 374 escape_for_meson_string "${ARRAY[0]}" "ESCAPED_ARG" 375 SETTING_AS_MESON_ARRAY+=" '${ESCAPED_ARG}' ]"$'\n' 376 return 377 fi 378 379 SETTING_AS_MESON_ARRAY+=$'\n' 380 381 local -i BASE_INDENTATION_LEN="$(( ${#SETTING_NAME} + 3 ))" 382 local BASE_INDENTATION 383 printf -v BASE_INDENTATION "%*s" "$BASE_INDENTATION_LEN" "" 384 385 local -r EXTRA_INDENTATION=" " 386 387 local -i INDEX 388 389 for ((INDEX=0; INDEX < ARRAY_LEN; INDEX++)); do 390 391 escape_for_meson_string "${ARRAY[$INDEX]}" "ESCAPED_ARG" 392 393 SETTING_AS_MESON_ARRAY+="${BASE_INDENTATION}${EXTRA_INDENTATION}'$ESCAPED_ARG'" 394 395 if (( INDEX != ARRAY_LEN - 1 )); then 396 SETTING_AS_MESON_ARRAY+="," 397 fi 398 399 SETTING_AS_MESON_ARRAY+=$'\n' 400 401 done 402 403 SETTING_AS_MESON_ARRAY+="${BASE_INDENTATION}]"$'\n' 404} 405 406 407# If the command argument array is empty, no setting is generated. 408 409generate_command_setting () 410{ 411 local SETTING_NAME="$1" 412 local ARRAY_NAME="$2" 413 414 local -n ARRAY="$ARRAY_NAME" # Create a reference to the array passed by name. 415 416 local -i ARRAY_LEN="${#ARRAY[@]}" 417 418 if (( ARRAY_LEN == 0 )); then 419 COMMAND_SETTING="" 420 return 421 fi 422 423 if (( ARRAY_LEN == 1 )); then 424 425 # We could generate an array with just one element, but a string looks nicer. 426 # Other sibling settings like 'ar' or 'strip' are usually given as strings too. 427 428 local ESCAPED_ARG 429 escape_for_meson_string "${ARRAY[0]}" "ESCAPED_ARG" 430 431 COMMAND_SETTING="$SETTING_NAME = '${ESCAPED_ARG}'"$'\n' 432 return 433 fi 434 435 local SETTING_AS_MESON_ARRAY 436 437 generate_setting_as_meson_array "$SETTING_NAME" "$ARRAY_NAME" 438 439 COMMAND_SETTING="$SETTING_AS_MESON_ARRAY" 440} 441 442 443# ----- Entry point ----- 444 445USER_SHORT_OPTIONS_SPEC="" 446 447# Use an associative array to declare how many arguments every long option expects. 448# All known options must be listed, even those with 0 arguments. 449declare -A USER_LONG_OPTIONS_SPEC 450USER_LONG_OPTIONS_SPEC+=( [help]=0 ) 451USER_LONG_OPTIONS_SPEC+=( [version]=0 ) 452USER_LONG_OPTIONS_SPEC+=( [license]=0 ) 453USER_LONG_OPTIONS_SPEC+=( [target-arch]=1 ) 454USER_LONG_OPTIONS_SPEC+=( [cpu-family]=1 ) 455USER_LONG_OPTIONS_SPEC+=( [cpu]=1 ) 456USER_LONG_OPTIONS_SPEC+=( [system]=1 ) 457USER_LONG_OPTIONS_SPEC+=( [endianness]=1 ) 458USER_LONG_OPTIONS_SPEC+=( [c-compiler]=1 ) 459USER_LONG_OPTIONS_SPEC+=( [linker]=1 ) 460USER_LONG_OPTIONS_SPEC+=( [cflag]=1 ) 461USER_LONG_OPTIONS_SPEC+=( [lflag]=1 ) 462USER_LONG_OPTIONS_SPEC+=( [exewrap]=1 ) 463USER_LONG_OPTIONS_SPEC+=( [meson-compat]=1 ) 464 465declare -a C_COMPILER=() 466declare -a LINKER=() 467declare -a CFLAGS_FOR_TARGET=() 468declare -a LINKER_FLAGS_FOR_TARGET=() 469declare -a EXE_WRAPPER=() 470 471TARGET_ARCH="" 472CPU_FAMILY_NAME="" 473CPU_NAME="" 474ENDIANNESS="" 475MESON_COMPATIBILITY="" 476 477# The system name is not really used by Meson. 478SYSTEM_NAME="unknown-system" 479 480# The CPU name is not really used by Meson, see: 481# https://github.com/mesonbuild/meson/issues/7037 482 483parse_command_line_arguments "$@" 484 485if (( ${#ARGS[@]} != 0 )); then 486 abort "Invalid number of command-line arguments. Run this tool with the --help option for usage information." 487fi 488 489if [[ $TARGET_ARCH = "" ]]; then 490 abort "Option --target-arch is required." 491fi 492 493if [[ $CPU_FAMILY_NAME = "" ]]; then 494 CPU_FAMILY_NAME=`expr "$TARGET_ARCH" : '\([^-]*\)-.*'` 495fi 496 497if [[ $ENDIANNESS = "" ]]; then 498 case "$CPU_FAMILY_NAME" in 499 arm*|aarch*|x86*|riscv*) 500 ENDIANNESS=little 501 ;; 502 68*|sparc*) 503 ENDIANNESS=big 504 ;; 505 *) 506 abort "Option --endianness is required." 507 ;; 508 esac 509fi 510 511if (( ${#C_COMPILER[@]} == 0)); then 512 C_COMPILER+=("$TARGET_ARCH-gcc") 513fi 514 515if [[ "$CPU_NAME" = "" ]]; then 516 CPU_NAME="$CPU_FAMILY_NAME" 517fi 518 519escape_for_meson_string "$TARGET_ARCH" "ESCAPED_TARGET_ARCH" 520escape_for_meson_string "$SYSTEM_NAME" "ESCAPED_SYSTEM_NAME" 521escape_for_meson_string "$CPU_FAMILY_NAME" "ESCAPED_CPU_FAMILY_NAME" 522escape_for_meson_string "$CPU_NAME" "ESCAPED_CPU_NAME" 523escape_for_meson_string "$ENDIANNESS" "ESCAPED_ENDIANNESS" 524 525 526FILE_CONTENTS="" 527 528FILE_CONTENTS+="[binaries]"$'\n' 529 530 531generate_command_setting "c" "C_COMPILER" 532 533FILE_CONTENTS+="$COMMAND_SETTING" 534 535 536generate_command_setting "c_ld" "LINKER" 537 538FILE_CONTENTS+="$COMMAND_SETTING" 539 540 541set +o errexit # When 'read' reaches end of file, a non-zero status code is returned. 542 543read -r -d '' PART_1 <<EOF 544ar = '$ESCAPED_TARGET_ARCH-ar' 545as = '$ESCAPED_TARGET_ARCH-as' 546nm = '$ESCAPED_TARGET_ARCH-nm' 547strip = '$ESCAPED_TARGET_ARCH-strip' 548EOF 549 550set -o errexit 551 552FILE_CONTENTS+="$PART_1"$'\n' 553 554 555generate_command_setting "exe_wrapper" "EXE_WRAPPER" 556 557FILE_CONTENTS+="$COMMAND_SETTING" 558 559 560set +o errexit # When 'read' reaches end of file, a non-zero status code is returned. 561 562read -r -d '' PART_2 <<EOF 563[host_machine] 564system = '$ESCAPED_SYSTEM_NAME' 565cpu_family = '$ESCAPED_CPU_FAMILY_NAME' 566cpu = '$ESCAPED_CPU_NAME' 567endian = '$ESCAPED_ENDIANNESS' 568 569[properties] 570skip_sanity_check = true 571EOF 572 573set -o errexit 574 575FILE_CONTENTS+=$'\n'"$PART_2"$'\n' 576 577 578if (( ${#EXE_WRAPPER[@]} > 0 )); then 579 FILE_CONTENTS+="needs_exe_wrapper = true"$'\n' 580fi 581 582 583case "$MESON_COMPATIBILITY" in 584 585 '') FILE_CONTENTS+=$'\n'"[built-in options]"$'\n';; 586 587 0.55) # Since Meson version 0.56.0, released on 2020-10-30, you get the following warning: 588 # DEPRECATION: c_args in the [properties] section of the machine file is deprecated, use the [built-in options] section. 589 # See this commit: https://github.com/mesonbuild/meson/pull/6597 590 ;; 591 592 *) abort "Unsupported value of --meson-compat: $MESON_COMPATIBILITY";; 593esac 594 595 596if (( ${#CFLAGS_FOR_TARGET[@]} > 0 )); then 597 598 generate_setting_as_meson_array "c_args" "CFLAGS_FOR_TARGET" 599 600 FILE_CONTENTS+="$SETTING_AS_MESON_ARRAY" 601 602fi 603 604 605if (( ${#LINKER_FLAGS_FOR_TARGET[@]} > 0 )); then 606 607 generate_setting_as_meson_array "c_link_args" "LINKER_FLAGS_FOR_TARGET" 608 609 FILE_CONTENTS+="$SETTING_AS_MESON_ARRAY" 610 611fi 612 613echo -n "$FILE_CONTENTS" 614