#!/usr/bin/env bash # # @license Apache-2.0 # # Copyright (c) 2019 The Stdlib Authors. # # 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. # Build script to run continuous integration tasks on [GitHub][1]. # # [1]: https://help.github.com/en/articles/about-github-actions # shellcheck disable=SC2181 # Ensure that the exit status of pipelines is non-zero in the event that at least one of the commands in a pipeline fails: set -o pipefail # VARIABLES # # Get the name of the build task as the first argument to the build script: task="$1" # Get the path to a log file as the second argument to the build script: log_file="$2" # Define a heartbeat interval to periodically print messages in order to prevent CI from prematurely ending a build due to long running commands: heartbeat_interval='30s' # Declare a variable for storing the heartbeat process id: heartbeat_pid="" # FUNCTIONS # # Error handler. # # $1 - error status on_error() { echo 'ERROR: An error was encountered during execution.' >&2 cleanup exit "$1" } # Runs clean-up tasks. cleanup() { stop_heartbeat } # Starts a heartbeat. # # $1 - heartbeat interval start_heartbeat() { echo 'Starting heartbeat...' >&2 # Create a heartbeat and send to background: heartbeat "$1" & # Capture the heartbeat pid: heartbeat_pid=$! echo "Heartbeat pid: ${heartbeat_pid}" >&2 } # Runs an infinite print loop. # # $1 - heartbeat interval heartbeat() { while true; do echo "$(date) - heartbeat..." >&2; sleep "$1"; done } # Stops the heartbeat print loop. stop_heartbeat() { echo 'Stopping heartbeat...' >&2 kill "${heartbeat_pid}" } # Prints a success message. print_success() { echo 'Success!' >&2 } # Runs benchmarks. # # $1 - log file benchmark() { echo 'Running benchmarks...' >&2 make NODE_FLAGS='--no-deprecation' benchmark >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Benchmarks failed.' >&2 echo 'Retry 1 of 1...' >&2 sleep 15s echo 'Running benchmarks...' >&2 make NODE_FLAGS='--no-deprecation' benchmark >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Benchmarks failed.' >&2 return 1 fi fi echo 'Successfully ran benchmarks.' >&2 return 0 } # Checks dependencies. # # $1 - log file check_deps() { echo 'Checking dependencies...' >&2 make check-deps >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Dependencies are out-of-date.' >&2 return 1 fi echo 'Dependencies are up-to-date.' >&2 return 0 } # Checks licenses. # # $1 - log file check_licenses() { echo 'Checking licenses...' >&2 make check-licenses-production >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Detected dependency licensing issues.' >&2 return 1 fi echo 'No dependency licensing issues detected.' >&2 return 0 } # Runs examples. # # $1 - log file examples() { echo 'Running examples...' >&2 make NODE_FLAGS='--no-deprecation' examples >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Examples failed.' >&2 echo 'Retry 1 of 1...' >&2 sleep 15s echo 'Running examples...' >&2 make NODE_FLAGS='--no-deprecation' examples >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Examples failed.' >&2 return 1 fi fi echo 'Successfully ran examples.' >&2 return 0 } # Performs install tasks. # # $1 - log file install() { echo 'Installing...' >&2 make install >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Error occurred during install.' >&2 echo 'Retry 1 of 2...' >&2 sleep 15s echo 'Installing...' >&2 make install >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Error occurred during install.' >&2 echo 'Retry 2 of 2...' >&2 sleep 30s echo 'Installing...' >&2 make install >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Error occurred during install.' >&2 echo 'Failed to install 3 times. Aborting install.' >&2 return 1 fi fi fi echo 'Install successful.' >&2 return 0 } # Performs lint tasks. # # $1 - log file lint() { echo 'Initializing development environment...' >&2 make init >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Linting failed as unable to initialize development environment.' >&2 return 1 fi echo 'Initialization successful.' >&2 echo 'Linting filenames...' >&2 make lint-filenames >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Linting filenames failed.' >&2 return 1 fi make lint-header-filenames >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Linting filenames failed.' >&2 return 1 fi echo 'Linting package.json files...' >&2 make lint-pkg-json >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Linting package.json failed.' >&2 return 1 fi # FIXME: linting of Markdown files takes too long; need to speed-up via distributed tasks or only linting files which changed # echo 'Linting Markdown files...' >&2 # make lint-markdown >> "$1" 2>&1 # if [[ "$?" -ne 0 ]]; then # echo 'Linting Markdown failed.' >&2 # return 1 # fi echo 'Linting passed.' >&2 return 0 } # Runs unit tests. # # $1 - log file tests() { echo 'Running tests...' >&2 make NODE_FLAGS='--no-deprecation' test >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Tests failed.' >&2 echo 'Retry 1 of 1...' >&2 sleep 15s echo 'Running tests...' >&2 make NODE_FLAGS='--no-deprecation' test >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Tests failed.' >&2 return 1 fi fi echo 'Tests passed.' >&2 return 0 } # Runs test coverage. # # $1 - log file test_coverage() { echo 'Running test coverage...' >&2 make NODE_FLAGS='--no-deprecation' test-cov >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Tests failed.' >&2 echo 'Retry 1 of 1...' >&2 sleep 15s echo 'Running test coverage...' >&2 make NODE_FLAGS='--no-deprecation' test-cov >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Tests failed.' >&2 return 1 fi fi echo 'Tests passed.' >&2 return 0 } # Sends test coverage report to coverage service. # # $1 - report title # $2 - log file test_coverage_report() { echo 'Sending coverage report to coverage service...' >&2 make COVERAGE_NAME="$1" coverage >> "$2" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Sending coverage report to coverage service failed.' >&2 echo 'Retry 1 of 1...' >&2 sleep 15s echo 'Sending coverage report to coverage service...' >&2 make COVERAGE_NAME="$1" coverage >> "$2" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Sending coverage report to coverage service failed.' >&2 return 1 fi fi echo 'Coverage report sent to coverage service.' >&2 return 0 } # Tests whether the project successfully installs via `npm`. # # $1 - log file test_npm_install() { echo 'Testing npm install...' >&2 make test-npm-install >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Installation failed.' >&2 echo 'Retry 1 of 1...' >&2 sleep 15s echo 'Testing npm install...' >&2 make test-npm-install >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Installation failed.' >&2 return 1 fi fi echo 'Successfully installed.' >&2 echo 'Testing npm install (via GitHub)...' >&2 make test-npm-install-github >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Installation (via GitHub) failed.' >&2 echo 'Retry 1 of 1...' >&2 sleep 15s echo 'Testing npm install (via GitHub)...' >&2 make test-npm-install-github >> "$1" 2>&1 if [[ "$?" -ne 0 ]]; then echo 'Installation (via GitHub) failed.' >&2 return 1 fi fi echo 'Successfully installed (via GitHub).' >&2 return 0 } # Main execution sequence. main() { start_heartbeat "${heartbeat_interval}" echo "Task: ${task}." >&2 # Note: please keep tasks in alphabetical order... if [[ "${task}" = "benchmark" ]]; then benchmark "${log_file}" if [[ "$?" -ne 0 ]]; then on_error 1 fi elif [[ "${task}" = "check_deps" ]]; then check_deps "${log_file}" if [[ "$?" -ne 0 ]]; then on_error 1 fi elif [[ "${task}" = "check_licenses" ]]; then check_licenses "${log_file}" if [[ "$?" -ne 0 ]]; then on_error 1 fi elif [[ "${task}" = "examples" ]]; then examples "${log_file}" if [[ "$?" -ne 0 ]]; then on_error 1 fi elif [[ "${task}" = "install" ]]; then install "${log_file}" if [[ "$?" -ne 0 ]]; then on_error 1 fi elif [[ "${task}" = "lint" ]]; then lint "${log_file}" if [[ "$?" -ne 0 ]]; then on_error 1 fi elif [[ "${task}" = "test" ]]; then tests "${log_file}" if [[ "$?" -ne 0 ]]; then on_error 1 fi elif [[ "${task}" = "test-coverage" ]]; then test_coverage "${log_file}" if [[ "$?" -ne 0 ]]; then on_error 1 fi test_coverage_report "unit_test" "${log_file}" if [[ "$?" -ne 0 ]]; then on_error 1 fi elif [[ "${task}" = "test-npm-install" ]]; then test_npm_install "${log_file}" if [[ "$?" -ne 0 ]]; then on_error 1 fi else echo "ERROR: unknown task: ${task}." >&2 on_error 1 fi cleanup print_success exit 0 } # Set an error handler to print captured output and perform any clean-up tasks: trap 'on_error' ERR # Run main: main