1#! /bin/sh 2# 3# SPDX-License-Identifier: BSD-2-Clause 4# 5# Copyright (c) 2018-2023 Gavin D. Howard and contributors. 6# 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted provided that the following conditions are met: 9# 10# * Redistributions of source code must retain the above copyright notice, this 11# list of conditions and the following disclaimer. 12# 13# * Redistributions in binary form must reproduce the above copyright notice, 14# this list of conditions and the following disclaimer in the documentation 15# and/or other materials provided with the distribution. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27# POSSIBILITY OF SUCH DAMAGE. 28# 29 30# This script is NOT meant to be run! It is meant to be sourced by other 31# scripts. 32 33# Reads and follows a link until it finds a real file. This is here because the 34# readlink utility is not part of the POSIX standard. Sigh... 35# @param f The link to find the original file for. 36readlink() { 37 38 _readlink_f="$1" 39 shift 40 41 _readlink_arrow="-> " 42 _readlink_d=$(dirname "$_readlink_f") 43 44 _readlink_lsout="" 45 _readlink_link="" 46 47 _readlink_lsout=$(ls -dl "$_readlink_f") 48 _readlink_link=$(printf '%s' "${_readlink_lsout#*$_readlink_arrow}") 49 50 while [ -z "${_readlink_lsout##*$_readlink_arrow*}" ]; do 51 _readlink_f="$_readlink_d/$_readlink_link" 52 _readlink_d=$(dirname "$_readlink_f") 53 _readlink_lsout=$(ls -dl "$_readlink_f") 54 _readlink_link=$(printf '%s' "${_readlink_lsout#*$_readlink_arrow}") 55 done 56 57 printf '%s' "${_readlink_f##*$_readlink_d/}" 58} 59 60# Quick function for exiting with an error. 61# @param 1 A message to print. 62# @param 2 The exit code to use. 63err_exit() { 64 65 if [ "$#" -ne 2 ]; then 66 printf 'Invalid number of args to err_exit\n' 67 exit 1 68 fi 69 70 printf '%s\n' "$1" 71 exit "$2" 72} 73 74# Function for checking the "d"/"dir" argument of scripts. This function expects 75# a usage() function to exist in the caller. 76# @param 1 The argument to check. 77check_d_arg() { 78 79 if [ "$#" -ne 1 ]; then 80 printf 'Invalid number of args to check_d_arg\n' 81 exit 1 82 fi 83 84 _check_d_arg_arg="$1" 85 shift 86 87 if [ "$_check_d_arg_arg" != "bc" ] && [ "$_check_d_arg_arg" != "dc" ]; then 88 _check_d_arg_msg=$(printf 'Invalid d arg: %s\nMust be either "bc" or "dc".\n\n' \ 89 "$_check_d_arg_arg") 90 usage "$_check_d_arg_msg" 91 fi 92} 93 94# Function for checking the boolean arguments of scripts. This function expects 95# a usage() function to exist in the caller. 96# @param 1 The argument to check. 97check_bool_arg() { 98 99 if [ "$#" -ne 1 ]; then 100 printf 'Invalid number of args to check_bool_arg\n' 101 exit 1 102 fi 103 104 _check_bool_arg_arg="$1" 105 shift 106 107 if [ "$_check_bool_arg_arg" != "0" ] && [ "$_check_bool_arg_arg" != "1" ]; then 108 _check_bool_arg_msg=$(printf 'Invalid bool arg: %s\nMust be either "0" or "1".\n\n' \ 109 "$_check_bool_arg_arg") 110 usage "$_check_bool_arg_msg" 111 fi 112} 113 114# Function for checking the executable arguments of scripts. This function 115# expects a usage() function to exist in the caller. 116# @param 1 The argument to check. 117check_exec_arg() { 118 119 if [ "$#" -ne 1 ]; then 120 printf 'Invalid number of args to check_exec_arg\n' 121 exit 1 122 fi 123 124 _check_exec_arg_arg="$1" 125 shift 126 127 if [ ! -x "$_check_exec_arg_arg" ]; then 128 if ! command -v "$_check_exec_arg_arg" >/dev/null 2>&1; then 129 _check_exec_arg_msg=$(printf 'Invalid exec arg: %s\nMust be an executable file.\n\n' \ 130 "$_check_exec_arg_arg") 131 usage "$_check_exec_arg_msg" 132 fi 133 fi 134} 135 136# Function for checking the file arguments of scripts. This function expects a 137# usage() function to exist in the caller. 138# @param 1 The argument to check. 139check_file_arg() { 140 141 if [ "$#" -ne 1 ]; then 142 printf 'Invalid number of args to check_file_arg\n' 143 exit 1 144 fi 145 146 _check_file_arg_arg="$1" 147 shift 148 149 if [ ! -f "$_check_file_arg_arg" ]; then 150 _check_file_arg_msg=$(printf 'Invalid file arg: %s\nMust be a file.\n\n' \ 151 "$_check_file_arg_arg") 152 usage "$_check_file_arg_msg" 153 fi 154} 155 156# Check the return code on a test and exit with a fail if it's non-zero. 157# @param d The calculator under test. 158# @param err The return code. 159# @param name The name of the test. 160checktest_retcode() { 161 162 _checktest_retcode_d="$1" 163 shift 164 165 _checktest_retcode_err="$1" 166 shift 167 168 _checktest_retcode_name="$1" 169 shift 170 171 if [ "$_checktest_retcode_err" -ne 0 ]; then 172 printf 'FAIL!!!\n' 173 err_exit "$_checktest_retcode_d failed test '$_checktest_retcode_name' with error code $_checktest_retcode_err" 1 174 fi 175} 176 177# Check the result of a test. First, it checks the error code using 178# checktest_retcode(). Then it checks the output against the expected output 179# and fails if it doesn't match. 180# @param d The calculator under test. 181# @param err The error code. 182# @param name The name of the test. 183# @param test_path The path to the test. 184# @param results_name The path to the file with the expected result. 185checktest() { 186 187 _checktest_d="$1" 188 shift 189 190 _checktest_err="$1" 191 shift 192 193 _checktest_name="$1" 194 shift 195 196 _checktest_test_path="$1" 197 shift 198 199 _checktest_results_name="$1" 200 shift 201 202 checktest_retcode "$_checktest_d" "$_checktest_err" "$_checktest_name" 203 204 _checktest_diff=$(diff "$_checktest_test_path" "$_checktest_results_name") 205 206 _checktest_err="$?" 207 208 if [ "$_checktest_err" -ne 0 ]; then 209 printf 'FAIL!!!\n' 210 printf '%s\n' "$_checktest_diff" 211 err_exit "$_checktest_d failed test $_checktest_name" 1 212 fi 213} 214 215# Die. With a message. 216# @param d The calculator under test. 217# @param msg The message to print. 218# @param name The name of the test. 219# @param err The return code from the test. 220die() { 221 222 _die_d="$1" 223 shift 224 225 _die_msg="$1" 226 shift 227 228 _die_name="$1" 229 shift 230 231 _die_err="$1" 232 shift 233 234 _die_str=$(printf '\n%s %s on test:\n\n %s\n' "$_die_d" "$_die_msg" "$_die_name") 235 236 err_exit "$_die_str" "$_die_err" 237} 238 239# Check that a test did not crash and die if it did. 240# @param d The calculator under test. 241# @param error The error code. 242# @param name The name of the test. 243checkcrash() { 244 245 _checkcrash_d="$1" 246 shift 247 248 _checkcrash_error="$1" 249 shift 250 251 _checkcrash_name="$1" 252 shift 253 254 255 if [ "$_checkcrash_error" -gt 127 ]; then 256 die "$_checkcrash_d" "crashed ($_checkcrash_error)" \ 257 "$_checkcrash_name" "$_checkcrash_error" 258 fi 259} 260 261# Check that a test had an error or crash. 262# @param d The calculator under test. 263# @param error The error code. 264# @param name The name of the test. 265# @param out The file that the test results were output to. 266# @param exebase The name of the executable. 267checkerrtest() 268{ 269 _checkerrtest_d="$1" 270 shift 271 272 _checkerrtest_error="$1" 273 shift 274 275 _checkerrtest_name="$1" 276 shift 277 278 _checkerrtest_out="$1" 279 shift 280 281 _checkerrtest_exebase="$1" 282 shift 283 284 checkcrash "$_checkerrtest_d" "$_checkerrtest_error" "$_checkerrtest_name" 285 286 if [ "$_checkerrtest_error" -eq 0 ]; then 287 die "$_checkerrtest_d" "returned no error" "$_checkerrtest_name" 127 288 fi 289 290 # This is to check for memory errors with Valgrind, which is told to return 291 # 100 on memory errors. 292 if [ "$_checkerrtest_error" -eq 100 ]; then 293 294 _checkerrtest_output=$(cat "$_checkerrtest_out") 295 _checkerrtest_fatal_error="Fatal error" 296 297 if [ "${_checkerrtest_output##*$_checkerrtest_fatal_error*}" ]; then 298 printf "%s\n" "$_checkerrtest_output" 299 die "$_checkerrtest_d" "had memory errors on a non-fatal error" \ 300 "$_checkerrtest_name" "$_checkerrtest_error" 301 fi 302 fi 303 304 if [ ! -s "$_checkerrtest_out" ]; then 305 die "$_checkerrtest_d" "produced no error message" "$_checkerrtest_name" "$_checkerrtest_error" 306 fi 307 308 # To display error messages, uncomment this line. This is useful when 309 # debugging. 310 #cat "$_checkerrtest_out" 311} 312 313# Replace a substring in a string with another. This function is the *real* 314# workhorse behind configure.sh's generation of a Makefile. 315# 316# This function uses a sed call that uses exclamation points `!` as delimiters. 317# As a result, needle can never contain an exclamation point. Oh well. 318# 319# @param str The string that will have any of the needle replaced by 320# replacement. 321# @param needle The needle to replace in str with replacement. 322# @param replacement The replacement for needle in str. 323substring_replace() { 324 325 _substring_replace_str="$1" 326 shift 327 328 _substring_replace_needle="$1" 329 shift 330 331 _substring_replace_replacement="$1" 332 shift 333 334 _substring_replace_result=$(printf '%s\n' "$_substring_replace_str" | \ 335 sed -e "s!$_substring_replace_needle!$_substring_replace_replacement!g") 336 337 printf '%s' "$_substring_replace_result" 338} 339 340# Generates an NLS path based on the locale and executable name. 341# 342# This is a monstrosity for a reason. 343# 344# @param nlspath The $NLSPATH 345# @param locale The locale. 346# @param execname The name of the executable. 347gen_nlspath() { 348 349 _gen_nlspath_nlspath="$1" 350 shift 351 352 _gen_nlspath_locale="$1" 353 shift 354 355 _gen_nlspath_execname="$1" 356 shift 357 358 # Split the locale into its modifier and other parts. 359 _gen_nlspath_char="@" 360 _gen_nlspath_modifier="${_gen_nlspath_locale#*$_gen_nlspath_char}" 361 _gen_nlspath_tmplocale="${_gen_nlspath_locale%%$_gen_nlspath_char*}" 362 363 # Split the locale into charset and other parts. 364 _gen_nlspath_char="." 365 _gen_nlspath_charset="${_gen_nlspath_tmplocale#*$_gen_nlspath_char}" 366 _gen_nlspath_tmplocale="${_gen_nlspath_tmplocale%%$_gen_nlspath_char*}" 367 368 # Check for an empty charset. 369 if [ "$_gen_nlspath_charset" = "$_gen_nlspath_tmplocale" ]; then 370 _gen_nlspath_charset="" 371 fi 372 373 # Split the locale into territory and language. 374 _gen_nlspath_char="_" 375 _gen_nlspath_territory="${_gen_nlspath_tmplocale#*$_gen_nlspath_char}" 376 _gen_nlspath_language="${_gen_nlspath_tmplocale%%$_gen_nlspath_char*}" 377 378 # Check for empty territory and language. 379 if [ "$_gen_nlspath_territory" = "$_gen_nlspath_tmplocale" ]; then 380 _gen_nlspath_territory="" 381 fi 382 383 if [ "$_gen_nlspath_language" = "$_gen_nlspath_tmplocale" ]; then 384 _gen_nlspath_language="" 385 fi 386 387 # Prepare to replace the format specifiers. This is done by wrapping the in 388 # pipe characters. It just makes it easier to split them later. 389 _gen_nlspath_needles="%%:%L:%N:%l:%t:%c" 390 391 _gen_nlspath_needles=$(printf '%s' "$_gen_nlspath_needles" | tr ':' '\n') 392 393 for _gen_nlspath_i in $_gen_nlspath_needles; do 394 _gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "$_gen_nlspath_i" "|$_gen_nlspath_i|") 395 done 396 397 # Replace all the format specifiers. 398 _gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%%" "%") 399 _gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%L" "$_gen_nlspath_locale") 400 _gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%N" "$_gen_nlspath_execname") 401 _gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%l" "$_gen_nlspath_language") 402 _gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%t" "$_gen_nlspath_territory") 403 _gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%c" "$_gen_nlspath_charset") 404 405 # Get rid of pipe characters. 406 _gen_nlspath_nlspath=$(printf '%s' "$_gen_nlspath_nlspath" | tr -d '|') 407 408 # Return the result. 409 printf '%s' "$_gen_nlspath_nlspath" 410} 411 412ALL=0 413NOSKIP=1 414SKIP=2 415 416# Filters text out of a file according to the build type. 417# @param in File to filter. 418# @param out File to write the filtered output to. 419# @param type Build type. 420filter_text() { 421 422 _filter_text_in="$1" 423 shift 424 425 _filter_text_out="$1" 426 shift 427 428 _filter_text_buildtype="$1" 429 shift 430 431 # Set up some local variables. 432 _filter_text_status="$ALL" 433 _filter_text_last_line="" 434 435 # We need to set IFS, so we store it here for restoration later. 436 _filter_text_ifs="$IFS" 437 438 # Remove the file- that will be generated. 439 rm -rf "$_filter_text_out" 440 441 # Here is the magic. This loop reads the template line-by-line, and based on 442 # _filter_text_status, either prints it to the markdown manual or not. 443 # 444 # Here is how the template is set up: it is a normal markdown file except 445 # that there are sections surrounded tags that look like this: 446 # 447 # {{ <build_type_list> }} 448 # ... 449 # {{ end }} 450 # 451 # Those tags mean that whatever build types are found in the 452 # <build_type_list> get to keep that section. Otherwise, skip. 453 # 454 # Obviously, the tag itself and its end are not printed to the markdown 455 # manual. 456 while IFS= read -r _filter_text_line; do 457 458 # If we have found an end, reset the status. 459 if [ "$_filter_text_line" = "{{ end }}" ]; then 460 461 # Some error checking. This helps when editing the templates. 462 if [ "$_filter_text_status" -eq "$ALL" ]; then 463 err_exit "{{ end }} tag without corresponding start tag" 2 464 fi 465 466 _filter_text_status="$ALL" 467 468 # We have found a tag that allows our build type to use it. 469 elif [ "${_filter_text_line#\{\{* $_filter_text_buildtype *\}\}}" != "$_filter_text_line" ]; then 470 471 # More error checking. We don't want tags nested. 472 if [ "$_filter_text_status" -ne "$ALL" ]; then 473 err_exit "start tag nested in start tag" 3 474 fi 475 476 _filter_text_status="$NOSKIP" 477 478 # We have found a tag that is *not* allowed for our build type. 479 elif [ "${_filter_text_line#\{\{*\}\}}" != "$_filter_text_line" ]; then 480 481 if [ "$_filter_text_status" -ne "$ALL" ]; then 482 err_exit "start tag nested in start tag" 3 483 fi 484 485 _filter_text_status="$SKIP" 486 487 # This is for normal lines. If we are not skipping, print. 488 else 489 if [ "$_filter_text_status" -ne "$SKIP" ]; then 490 if [ "$_filter_text_line" != "$_filter_text_last_line" ]; then 491 printf '%s\n' "$_filter_text_line" >> "$_filter_text_out" 492 fi 493 _filter_text_last_line="$_filter_text_line" 494 fi 495 fi 496 497 done < "$_filter_text_in" 498 499 # Reset IFS. 500 IFS="$_filter_text_ifs" 501} 502