diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1dcdf9e..0a3b93c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,4 +33,4 @@ jobs: - uses: actions/checkout@v4 - name: Analyze shell: bash - run: ./ci/analyze --analyzer shellcheck + run: ./ci/analyze.sh --analyzer shellcheck diff --git a/README.md b/README.md index 4e516ce..b7717a6 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ git clone git@github.com:apcountryman/build-avr-gcc.git ## Usage -See the `build-avr-gcc` and `install-device-family-pack` scripts' help text for usage -details. +See the `build-avr-gcc.sh` and `install-device-family-pack.sh` scripts' help text for +usage details. ```shell -./build-avr-gcc --help -./install-device-family-packs --help +./build-avr-gcc.sh --help +./install-device-family-packs.sh --help ``` ## Versioning @@ -37,11 +37,11 @@ workflow. ## Git Hooks -To install this repository's Git hooks, run the `install` script located in the +To install this repository's Git hooks, run the `install.sh` script located in the `git/hooks` directory. -See the `install` script's help text for usage details. +See the `install.sh` script's help text for usage details. ```shell -$ ./git/hooks/install --help +$ ./git/hooks/install.sh --help ``` ## Code of Conduct diff --git a/build-avr-gcc b/build-avr-gcc.sh similarity index 84% rename from build-avr-gcc rename to build-avr-gcc.sh index dcf57f6..967f8c6 100755 --- a/build-avr-gcc +++ b/build-avr-gcc.sh @@ -35,40 +35,49 @@ function abort() exit 1 } +function validate_script() +{ + if ! shellcheck "$script"; then + abort + fi +} + function display_help_text() { - echo "NAME" - echo " $mnemonic - Build avr-gcc." - echo "SYNOPSIS" - echo " $mnemonic --help" - echo " $mnemonic --version" - echo " $mnemonic --avr-gcc-version " - echo " --install-prefix [--jobs ]" - echo "OPTIONS" - echo " --avr-gcc-version " - echo " Specify the version of avr-gcc to build. The following avr-gcc versions" - echo " are supported:" - echo " 8.3.0" - echo " 9.3.0" - echo " --help" - echo " Display this help text." - echo " --install-prefix " - echo " Specify the install prefix. '/bin' must be in '\$PATH'" - echo " prior to the execution of this script." - echo " --jobs " - echo " Specify the number of build jobs to use when building. If the number of" - echo " jobs is not specified, 'nproc - 1' jobs will be used." - echo " --version" - echo " Display the version of this script." - echo "EXAMPLES" - echo " $mnemonic --help" - echo " $mnemonic --version" - echo " $mnemonic --avr-gcc-version 8.3.0 --install-prefix ~/bin/avr-gcc/8.3.0" - echo " $mnemonic --avr-gcc-version 8.3.0 --install-prefix ~/bin/avr-gcc/8.3.0" - echo " --jobs 1" - echo " $mnemonic --avr-gcc-version 9.3.0 --install-prefix ~/bin/avr-gcc/9.3.0" - echo " $mnemonic --avr-gcc-version 9.3.0 --install-prefix ~/bin/avr-gcc/9.3.0" - echo " --jobs 1" + printf "%b" \ + "NAME\n" \ + " $mnemonic - Build avr-gcc.\n" \ + "SYNOPSIS\n" \ + " $mnemonic --help\n" \ + " $mnemonic --version\n" \ + " $mnemonic --avr-gcc-version \n" \ + " --install-prefix [--jobs ]\n" \ + "OPTIONS\n" \ + " --avr-gcc-version \n" \ + " Specify the version of avr-gcc to build. The following avr-gcc versions\n" \ + " are supported:\n" \ + " 8.3.0\n" \ + " 9.3.0\n" \ + " --help\n" \ + " Display this help text.\n" \ + " --install-prefix \n" \ + " Specify the install prefix. '/bin' must be in '\$PATH'\n" \ + " prior to the execution of this script.\n" \ + " --jobs \n" \ + " Specify the number of build jobs to use when building. If the number of\n" \ + " jobs is not specified, 'nproc - 1' jobs will be used.\n" \ + " --version\n" \ + " Display the version of this script.\n" \ + "EXAMPLES\n" \ + " $mnemonic --help\n" \ + " $mnemonic --version\n" \ + " $mnemonic --avr-gcc-version 8.3.0 --install-prefix ~/bin/avr-gcc/8.3.0\n" \ + " $mnemonic --avr-gcc-version 8.3.0 --install-prefix ~/bin/avr-gcc/8.3.0\n" \ + " --jobs 1\n" \ + " $mnemonic --avr-gcc-version 9.3.0 --install-prefix ~/bin/avr-gcc/9.3.0\n" \ + " $mnemonic --avr-gcc-version 9.3.0 --install-prefix ~/bin/avr-gcc/9.3.0\n" \ + " --jobs 1\n" \ + "" } function display_version() @@ -295,6 +304,7 @@ function build_avr_gcc() "avr-libc" ) + local component for component in "${components[@]}"; do local component_build_directory="$build_directory/$component/build" @@ -312,6 +322,9 @@ function main() { local -r script=$( readlink -f "$0" ) local -r mnemonic=$( basename "$script" ) + + validate_script + local -r repository=$( dirname "$script" ) local -r version=$( git -C "$repository" describe --match=none --always --dirty --broken ) diff --git a/ci/analyze b/ci/analyze.sh similarity index 62% rename from ci/analyze rename to ci/analyze.sh index 58a5c9e..a256964 100755 --- a/ci/analyze +++ b/ci/analyze.sh @@ -35,27 +35,50 @@ function abort() exit 1 } +function validate_script() +{ + if ! shellcheck "$script"; then + abort + fi +} + function display_help_text() { - echo "NAME" - echo " $mnemonic - Run a static analyzer against build-avr-gcc." - echo "SYNOPSIS" - echo " $mnemonic --help" - echo " $mnemonic --version" - echo " $mnemonic --analyzer " - echo "OPTIONS" - echo " --analyzer " - echo " Specify the analyzer to run against the build-avr-gcc repository. The" - echo " following analyzers are supported:" - echo " shellcheck" - echo " --help" - echo " Display this help text." - echo " --version" - echo " Display the version of this script." - echo "EXAMPLES" - echo " $mnemonic --help" - echo " $mnemonic --version" - echo " $mnemonic --analyzer shellcheck" + local analyzer + + printf "%b" \ + "NAME\n" \ + " $mnemonic - Ensure no static analysis errors are present.\n" \ + "SYNOPSIS\n" \ + " $mnemonic --help\n" \ + " $mnemonic --version\n" \ + " $mnemonic --analyzer \n" \ + "OPTIONS\n" \ + " --analyzer \n" \ + " Specify the analyzer to run. The following analyzers are supported:\n" \ + "" + + for analyzer in "${analyzers[@]}"; do + printf "%b" \ + " $analyzer\n" \ + "" + done + + printf "%b" \ + " --help\n" \ + " Display this help text.\n" \ + " --version\n" \ + " Display the version of this script.\n" \ + "EXAMPLES\n" \ + " $mnemonic --help\n" \ + " $mnemonic --version\n" \ + "" + + for analyzer in "${analyzers[@]}"; do + printf "%b" \ + " $mnemonic --analyzer $analyzer\n" \ + "" + done } function display_version() @@ -63,9 +86,24 @@ function display_version() echo "$mnemonic, version $version" } +function value_is_in_array() +{ + local -r target_value="$1"; shift + local -r array=( "$@" ) + + local value + for value in "${array[@]}"; do + if [[ "$target_value" == "$value" ]]; then + return 0; + fi + done + + return 1 +} + function run_shellcheck() { - local scripts; mapfile -t scripts < <( git -C "$repository" ls-files | xargs -r -d '\n' -I '{}' find "$repository/{}" -executable ); readonly scripts + local scripts; mapfile -t scripts < <( git -C "$repository" ls-files '*.sh' | xargs -r -d '\n' -I '{}' find "$repository/{}" ); readonly scripts if ! shellcheck "${scripts[@]}"; then abort @@ -81,9 +119,16 @@ function main() { local -r script=$( readlink -f "$0" ) local -r mnemonic=$( basename "$script" ) + + validate_script + local -r repository=$( readlink -f "$( dirname "$script" )/.." ) local -r version=$( git -C "$repository" describe --match=none --always --dirty --broken ) + local -r analyzers=( + shellcheck + ) + while [[ "$#" -gt 0 ]]; do local argument="$1"; shift @@ -99,7 +144,7 @@ function main() local -r analyzer="$1"; shift - if [[ "$analyzer" != "shellcheck" ]]; then + if ! value_is_in_array "$analyzer" "${analyzers[@]}"; then abort "'$analyzer' is not a supported analyzer" fi ;; diff --git a/git/hooks/install b/git/hooks/install deleted file mode 100755 index 626afb6..0000000 --- a/git/hooks/install +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env bash - -# build-avr-gcc -# -# Copyright 2019, 2021-2024, Andrew Countryman and the -# build-avr-gcc contributors -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this -# file except in compliance with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under -# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the specific language governing -# permissions and limitations under the License. - -# Description: Git hooks install script. - -function error() -{ - local -r message="$1" - - ( >&2 echo "$mnemonic: $message" ) -} - -function abort() -{ - if [[ "$#" -gt 0 ]]; then - local -r message="$1" - - error "$message, aborting" - fi - - exit 1 -} - -function display_help_text() -{ - echo "NAME" - echo " $mnemonic - Install the build-avr-gcc repository's Git hooks." - echo "SYNOPSIS" - echo " $mnemonic --help" - echo " $mnemonic --version" - echo " $mnemonic --all" - echo " $mnemonic []" - echo "OPTIONS" - echo " --all" - echo " Install all supported hooks." - echo " --help" - echo " Display this help text." - echo " --version" - echo " Display the version of this script." - echo " " - echo " The whitespace separated list of hooks to install. The following hooks" - echo " are supported:" - - for hook in "${supported_hooks[@]}"; do - echo " $hook" - done - - echo "EXAMPLES" - echo " $mnemonic --help" - echo " $mnemonic --version" - echo " $mnemonic --all" - echo " $mnemonic ${supported_hooks[*]}" -} - -function display_version() -{ - echo "$mnemonic, version $version" -} - -function valid_hook() -{ - local -r hook="$1" - - for supported_hook in "${supported_hooks[@]}"; do - if [[ "$hook" == "$supported_hook" ]]; then - return 0 - fi - done - - return 1 -} - -function validate_hooks() -{ - for hook in "${selected_hooks[@]}"; do - if ! valid_hook "$hook"; then - abort "'$hook' is not a supported hook" - fi - done -} - -function install_hooks() -{ - for hook in "${selected_hooks[@]}"; do - rm -f "$repository/.git/hooks/$hook" - - if ! ln -s "$hooks/$hook" "$repository/.git/hooks/$hook" > "/dev/null"; then - abort "'$hook' installation failure" - fi - done -} - -function main() -{ - local -r script=$( readlink -f "$0" ) - local -r mnemonic=$( basename "$script" ) - local -r hooks=$( dirname "$script" ) - local -r repository=$( readlink -f "$hooks/../.." ) - local -r version=$( git -C "$repository" describe --match=none --always --dirty --broken ) - - local supported_hooks; mapfile -t supported_hooks < <( git -C "$repository" ls-files 'git/hooks/' ':!:git/hooks/install' | xargs -r -d '\n' -I '{}' find "$repository/{}" -executable -printf '%f\n' ); readonly supported_hooks - - local selected_hooks=() - - while [[ "$#" -gt 0 ]]; do - local argument="$1"; shift - - case "$argument" in - --all) - selected_hooks=( "${supported_hooks[@]}" ) - break - ;; - --help) - display_help_text - exit - ;; - --version) - display_version - exit - ;; - --*) - ;& - -*) - abort "'$argument' is not a supported option" - ;; - *) - selected_hooks+=( "$argument" ) - ;; - esac - done - - readonly selected_hooks - - validate_hooks - install_hooks -} - -main "$@" diff --git a/git/hooks/install.sh b/git/hooks/install.sh new file mode 100755 index 0000000..5aceac5 --- /dev/null +++ b/git/hooks/install.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash + +# build-avr-gcc +# +# Copyright 2019, 2021-2024, Andrew Countryman and the +# build-avr-gcc contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this +# file except in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under +# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the specific language governing +# permissions and limitations under the License. + +# Description: Git hooks install script. + +function error() +{ + local -r message="$1" + + ( >&2 echo "$mnemonic: $message" ) +} + +function abort() +{ + if [[ "$#" -gt 0 ]]; then + local -r message="$1" + + error "$message, aborting" + fi + + exit 1 +} + +function validate_script() +{ + if ! shellcheck "$script"; then + abort + fi +} + +function display_help_text() +{ + printf "%b" \ + "NAME\n" \ + " $mnemonic - Install Git hooks.\n" \ + "SYNOPSIS\n" \ + " $mnemonic --help\n" \ + " $mnemonic --version\n" \ + " $mnemonic\n" \ + "OPTIONS\n" \ + " --help\n" \ + " Display this help text.\n" \ + " --version\n" \ + " Display the version of this script.\n" \ + "EXAMPLES\n" \ + " $mnemonic --help\n" \ + " $mnemonic --version\n" \ + " $mnemonic\n" \ + "" +} + +function display_version() +{ + echo "$mnemonic, version $version" +} + +function install_git_hooks() +{ + local hook_scripts; mapfile -t hook_scripts < <( git -C "$repository" ls-files 'git/hooks/' ':!:git/hooks/install.sh' | xargs -r -d '\n' -I '{}' find "$repository/{}" ); readonly hook_scripts + + local hook_script + for hook_script in "${hook_scripts[@]}"; do + local hook; hook=$( basename "$hook_script" | cut -f 1 -d '.' ) + + rm -f "$repository/.git/hooks/$hook" + + if ! ln -s "$hook_script" "$repository/.git/hooks/$hook"; then + abort "'$hook' installation failure" + fi + done +} + +function main() +{ + local -r script=$( readlink -f "$0" ) + local -r mnemonic=$( basename "$script" ) + + validate_script + + local -r repository=$( readlink -f "$( dirname "$script" )/../.." ) + local -r version=$( git -C "$repository" describe --match=none --always --dirty --broken ) + + while [[ "$#" -gt 0 ]]; do + local argument="$1"; shift + + case "$argument" in + --help) + display_help_text + exit + ;; + --version) + display_version + exit + ;; + --*) + ;& + -*) + abort "'$argument' is not a supported option" + ;; + *) + abort "'$argument' is not a valid argument" + ;; + esac + done + + install_git_hooks +} + +main "$@" diff --git a/git/hooks/pre-commit b/git/hooks/pre-commit.sh similarity index 64% rename from git/hooks/pre-commit rename to git/hooks/pre-commit.sh index c63c33d..0e37c8f 100755 --- a/git/hooks/pre-commit +++ b/git/hooks/pre-commit.sh @@ -17,18 +17,6 @@ # Description: Git pre-commit hook script. -function message() -{ - local -r content="$1" - - echo -n "$mnemonic: $content" -} - -function errors_found() -{ - echo "error(s) found" -} - function error() { local -r message="$1" @@ -47,26 +35,32 @@ function abort() exit 1 } +function validate_script() +{ + if ! shellcheck "$script"; then + abort + fi +} + function display_help_text() { - echo "NAME" - echo " $mnemonic - Ensure:" - echo " - Filenames are portable" - echo " - No whitespace errors are present" - echo " - No script errors are present" - echo "SYNOPSIS" - echo " $mnemonic --help" - echo " $mnemonic --version" - echo " $mnemonic" - echo "OPTIONS" - echo " --help" - echo " Display this help text." - echo " --version" - echo " Display the version of this script." - echo "EXAMPLES" - echo " $mnemonic --help" - echo " $mnemonic --version" - echo " $mnemonic" + printf "%b" \ + "NAME\n" \ + " $mnemonic - Ensure commit preconditions are met.\n" \ + "SYNOPSIS\n" \ + " $mnemonic --help\n" \ + " $mnemonic --version\n" \ + " $mnemonic" \ + "OPTIONS\n" \ + " --help\n" \ + " Display this help text.\n" \ + " --version\n" \ + " Display the version of this script.\n" \ + "EXAMPLES\n" \ + " $mnemonic --help\n" \ + " $mnemonic --version\n" \ + " $mnemonic\n" \ + "" } function display_version() @@ -74,55 +68,84 @@ function display_version() echo "$mnemonic, version $version" } +function message() +{ + local -r content="$1" + local -r content_length=${#content} + local -r content_length_max=47 + local -r ellipsis_count_min=3 + local -r ellipsis_count=$(( content_length_max - content_length + ellipsis_count_min )) + + if [[ "$ellipsis_count" -lt "$ellipsis_count_min" ]]; then + abort "increase content_length_max (ellipsis_count=$ellipsis_count)" + fi + + local -r ellipsis=$( head -c "$ellipsis_count" < /dev/zero | tr '\0' '.' ) + + echo -n "$mnemonic: $content $ellipsis " +} + +function message_status_no_errors_found() +{ + echo "none" +} + +function message_status_errors_found() +{ + echo "error(s) found" +} + function ensure_filenames_are_portable() { - message "checking for non-portable (non-ASCII) filenames ... " + message "checking for non-portable (non-ASCII) filenames" if [[ $( git -C "$repository" diff --cached --name-only --diff-filter=A -z "$against" | LC_ALL=C tr -d '[ -~]\0' | wc -c ) != 0 ]]; then - errors_found + message_status_errors_found error "aborting commit due to non-portable (non-ASCII) filename(s)" abort fi - echo "none" + message_status_no_errors_found } function ensure_no_whitespace_errors_are_present() { - message "checking for whitespace errors .................... " + message "checking for whitespace errors" if ! git -C "$repository" diff-index --check --cached "$against" -- > "/dev/null" 2>&1; then - errors_found + message_status_errors_found error "aborting commit due to whitespace error(s), listed below" git -C "$repository" diff-index --check --cached "$against" -- abort fi - echo "none" + message_status_no_errors_found } function ensure_no_script_errors_are_present() { - message "checking for script errors ........................ " + message "checking for script errors" - local scripts; mapfile -t scripts < <( git -C "$repository" ls-files | xargs -r -d '\n' -I '{}' find "$repository/{}" -executable ); readonly scripts + local scripts; mapfile -t scripts < <( git -C "$repository" ls-files '*.sh' | xargs -r -d '\n' -I '{}' find "$repository/{}" ); readonly scripts if ! shellcheck "${scripts[@]}" > "/dev/null" 2>&1; then - errors_found + message_status_errors_found error "aborting commit due to script error(s), listed below" shellcheck "${scripts[@]}" abort fi - echo "none" + message_status_no_errors_found } function main() { local -r script=$( readlink -f "$0" ) local -r mnemonic=$( basename "$script" ) - local -r hooks=$( dirname "$script" ) - local -r repository=$( readlink -f "$hooks/../.." ) + + validate_script + + local -r repository=$( readlink -f "$( dirname "$script" )/../.." ) local -r version=$( git -C "$repository" describe --match=none --always --dirty --broken ) while [[ "$#" -gt 0 ]]; do diff --git a/install-device-family-pack b/install-device-family-pack.sh similarity index 82% rename from install-device-family-pack rename to install-device-family-pack.sh index 86106bb..48b0990 100755 --- a/install-device-family-pack +++ b/install-device-family-pack.sh @@ -35,27 +35,36 @@ function abort() exit 1 } +function validate_script() +{ + if ! shellcheck "$script"; then + abort + fi +} + function display_help_text() { - echo "NAME" - echo " $mnemonic - Install an avr-gcc device family pack." - echo "SYNOPSIS" - echo " $mnemonic --help" - echo " $mnemonic --version" - echo " $mnemonic --install-prefix --family-pack " - echo "OPTIONS" - echo " --family-pack " - echo " Specify the name and version of the device family pack to install." - echo " --help" - echo " Display this help text." - echo " --install-prefix " - echo " Specify avr-gcc's install prefix." - echo " --version" - echo " Display the version of this script." - echo "EXAMPLES" - echo " $mnemonic --help" - echo " $mnemonic --version" - echo " $mnemonic --install-prefix ~/bin/avr-gcc/8.3.0 --family-pack ATmega 2.0.401" + printf "%b" \ + "NAME\n" \ + " $mnemonic - Install an avr-gcc device family pack.\n" \ + "SYNOPSIS\n" \ + " $mnemonic --help\n" \ + " $mnemonic --version\n" \ + " $mnemonic --install-prefix --family-pack \n" \ + "OPTIONS\n" \ + " --family-pack \n" \ + " Specify the name and version of the device family pack to install.\n" \ + " --help\n" \ + " Display this help text.\n" \ + " --install-prefix \n" \ + " Specify avr-gcc's install prefix.\n" \ + " --version\n" \ + " Display the version of this script.\n" \ + "EXAMPLES\n" \ + " $mnemonic --help\n" \ + " $mnemonic --version\n" \ + " $mnemonic --install-prefix ~/bin/avr-gcc/9.3.0 --family-pack ATmega 3.1.264\n" \ + "" } function display_version() @@ -83,6 +92,7 @@ function install_device_specs() local -r install_directory="$( find "$install_prefix/lib/gcc/avr" -type d -name "device-specs" )" + local device_spec for device_spec in "${device_specs[@]}"; do if ! cp "$device_spec" "$install_directory"; then abort "$device_spec install failure" @@ -94,9 +104,11 @@ function install_device_startup_files_and_libraries() { local devices; mapfile -t devices < <( find "$build_directory/$family_pack/gcc/dev" -mindepth 1 -maxdepth 1 -type d ); readonly devices + local device for device in "${devices[@]}"; do local device_startup_files_and_libraries; mapfile -t device_startup_files_and_libraries < <( cd "$device" && find . -mindepth 1 -maxdepth 1 -type d ! -name "device-specs" ) + local device_startup_file_and_library for device_startup_file_and_library in "${device_startup_files_and_libraries[@]}"; do if ! ( cd "$device" && tar -czvf "$device_startup_file_and_library.tar.gz" "$device_startup_file_and_library" && tar -xzvf "$device_startup_file_and_library.tar.gz" -C "$install_prefix/avr/lib" ); then abort "$device $device_startup_file_and_library install failure" @@ -110,6 +122,7 @@ function install_header_files() { local header_files; mapfile -t header_files < <( find "$build_directory/$family_pack/include/avr" -name "*.h" ); readonly header_files + local header_file for header_file in "${header_files[@]}"; do if ! cp "$header_file" "$install_prefix/avr/include/avr/"; then abort "$header_file install failure" @@ -138,6 +151,9 @@ function main() { local -r script=$( readlink -f "$0" ) local -r mnemonic=$( basename "$script" ) + + validate_script + local -r repository=$( dirname "$script" ) local -r version=$( git -C "$repository" describe --match=none --always --dirty --broken )