diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..10d8d66 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - directory: / + package-ecosystem: gitsubmodule + schedule: + interval: daily + - directory: / + package-ecosystem: github-actions + schedule: + interval: daily diff --git a/.github/workflows/auto_prerelease.yaml b/.github/workflows/auto_prerelease.yaml new file mode 100644 index 0000000..750279b --- /dev/null +++ b/.github/workflows/auto_prerelease.yaml @@ -0,0 +1,50 @@ +name: Automatic pre-release + +on: + push: + tags: ["v*-*"] + +jobs: + auto_pre_release: + name: Automatic pre-release + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # NOTE: + # Some automatic release action + # might need history for generate change log. + fetch-depth: 0 + + - name: Run generate-release-assets.sh + id: generate_release_assets + run: | + # NOTE: + # The pitchfork layout holds extra scripts in tools directory. + # > https://blog.black-desk.cn/pages/pintchfork-layout.html + # But the "Standard Go Project Layout" + # holds extra scripts in scripts directory. + # > https://github.com/golang-standards/project-layout#scripts + generate_release_assets=tools/generate-release-assets.sh + if [ ! -f "$generate_release_assets" ]; then + generate_release_assets=scripts/generate-release-assets.sh + fi + if [ ! -f "$generate_release_assets" ]; then + echo "generate-release-assets script not found" >&2 + exit -1 + fi + + ASSETS="$("${generate_release_assets}")" + echo assets="$ASSETS" >> $GITHUB_OUTPUT + + - name: Run marvinpinto/action-automatic-releases + uses: marvinpinto/action-automatic-releases@latest + if: needs.auto_tag.outputs.new_tag + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + automatic_release_tag: ${{ needs.auto_tag.outputs.new_tag }} + prerelease: true + files: ${{ steps.generate_release_assets.outputs.assets }} diff --git a/.github/workflows/auto_tag_and_release.yaml b/.github/workflows/auto_tag_and_release.yaml new file mode 100644 index 0000000..0526567 --- /dev/null +++ b/.github/workflows/auto_tag_and_release.yaml @@ -0,0 +1,108 @@ +name: Automatic create tag and release. +on: + push: + branches: [master] + +jobs: + auto_tag: + permissions: + contents: write + name: Automatic create new tag from tools/get_project_version.sh + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # NOTE: + # Step `check_tag` need history. + fetch-depth: 0 + + - name: Run get-project-version.sh + id: get_project_version + run: | + # NOTE: + # The pitchfork layout holds extra scripts in tools directory. + # > https://blog.black-desk.cn/pages/pintchfork-layout.html + # But the "Standard Go Project Layout" + # holds extra scripts in scripts directory. + # > https://github.com/golang-standards/project-layout#scripts + get_project_version=tools/get-project-version.sh + if [ ! -f "$get_project_version" ]; then + + get_project_version=scripts/get-project-version.sh + fi + if [ ! -f "$get_project_version" ]; then + echo "get-project-version script not found" >&2 + exit -1 + fi + + version="$("${get_project_version}")" + echo version="$version" >> $GITHUB_OUTPUT + + - name: Check if tag already exists + id: check_tag + run: | + if git rev-parse "${{ steps.get_project_version.outputs.version }}" &>/dev/null; then + echo existed=true >> $GITHUB_OUTPUT + else + echo existed=false >> $GITHUB_OUTPUT + fi + + - name: Run anothrNick/github-tag-action + id: create_tag + if: steps.check_tag.outputs.existed == 'false' + uses: anothrNick/github-tag-action@1.70.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}} + CUSTOM_TAG: ${{ steps.get_project_version.outputs.version }} + + outputs: + new_tag: ${{ steps.create_tag.outputs.new_tag }} + + auto_release: + permissions: + contents: write + name: Automatic release for new tag + runs-on: ubuntu-latest + needs: + - auto_tag + steps: + - name: Checkout repository + if: needs.auto_tag.outputs.new_tag + uses: actions/checkout@v4 + with: + # NOTE: + # Some automatic release action + # might need history for generate change log. + fetch-depth: 0 + + - name: Run tools/generate-release-assets.sh + id: generate_release_assets + if: needs.auto_tag.outputs.new_tag + run: | + # NOTE: + # The pitchfork layout holds extra scripts in tools directory. + # > https://blog.black-desk.cn/pages/pintchfork-layout.html + # But the "Standard Go Project Layout" + # holds extra scripts in scripts directory. + # > https://github.com/golang-standards/project-layout#scripts + generate_release_assets=tools/generate-release-assets.sh + if [ ! -f "$generate_release_assets" ]; then + generate_release_assets=scripts/generate-release-assets.sh + fi + if [ ! -f "$generate_release_assets" ]; then + echo "generate-release-assets script not found" >&2 + exit -1 + fi + + ASSETS="$("${generate_release_assets}")" + echo assets="$ASSETS" >> $GITHUB_OUTPUT + + - name: Run marvinpinto/action-automatic-releases + uses: marvinpinto/action-automatic-releases@latest + if: needs.auto_tag.outputs.new_tag + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + automatic_release_tag: ${{ needs.auto_tag.outputs.new_tag }} + prerelease: false + files: ${{ steps.generate_release_assets.outputs.assets }} diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml new file mode 100644 index 0000000..35840bf --- /dev/null +++ b/.github/workflows/checks.yaml @@ -0,0 +1,26 @@ +name: Checks + +on: [pull_request] + +jobs: + checks: + name: Run black-desk/checks + permissions: + checks: write + contents: read + issues: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: black-desk/checks@master + pass: + name: Pass + if: always() + needs: + - checks + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 53390ca..1fbca47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,3 +101,13 @@ pfl_add_library( COMPILE_FEATURES INTERFACE cxx_std_17) + +file(READ ${CMAKE_CURRENT_BINARY_DIR}/include/errors/config.hpp + ERRORS_CONFIG_HEADER_FILE) +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/include/errors/version.hpp + ERRORS_VERSION_HEADER_FILE) +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/include/errors/source_location.hpp + ERRORS_SOURCE_LOCATION_HEADER_FILE) +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/include/errors/error.hpp + ERRORS_ERROR_HEADER_FILE) +configure_file(./misc/errors.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/errors.hpp) diff --git a/include/errors/error.hpp b/include/errors/error.hpp index 618d973..e79b891 100644 --- a/include/errors/error.hpp +++ b/include/errors/error.hpp @@ -5,10 +5,13 @@ #include #include +#if not defined(ERRORS_SINGLE_HEADER_FILE) #include "errors/config.hpp" #include "errors/version.hpp" +#endif -#if defined(ERRORS_ENABLE_SOURCE_LOCATION) +#if defined(ERRORS_ENABLE_SOURCE_LOCATION) and \ + not defined(ERRORS_SINGLE_HEADER_FILE) #include "errors/source_location.hpp" #endif diff --git a/include/errors/version.hpp b/include/errors/version.hpp index a777e2a..f3e8931 100644 --- a/include/errors/version.hpp +++ b/include/errors/version.hpp @@ -2,7 +2,9 @@ #include +#if not defined(ERRORS_SINGLE_HEADER_FILE) #include "errors/config.hpp" +#endif namespace errors { diff --git a/misc/errors.hpp.in b/misc/errors.hpp.in new file mode 100644 index 0000000..5974b52 --- /dev/null +++ b/misc/errors.hpp.in @@ -0,0 +1,10 @@ +#pragma once + +// The single header file version of the errors library. + +#define ERRORS_SINGLE_HEADER_FILE + +@ERRORS_CONFIG_HEADER_FILE@ +@ERRORS_VERSION_HEADER_FILE@ +@ERRORS_SOURCE_LOCATION_HEADER_FILE@ +@ERRORS_ERROR_HEADER_FILE@ diff --git a/tools/generate-release-assets.sh b/tools/generate-release-assets.sh new file mode 100755 index 0000000..43c54bb --- /dev/null +++ b/tools/generate-release-assets.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# NOTE: +# Use /usr/bin/env to find shell interpreter for better portability. +# Reference: https://en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability + +# NOTE: +# Exit immediately if any commands (even in pipeline) +# exits with a non-zero status. +set -e +set -o pipefail + +# WARNING: +# This is not reliable when using POSIX sh +# and current script file is sourced by `source` or `.` +CURRENT_SOURCE_FILE_PATH="${BASH_SOURCE[0]:-$0}" +CURRENT_SOURCE_FILE_NAME="$(basename -- "$CURRENT_SOURCE_FILE_PATH")" + +# This function log messages to stderr works like printf +# with a prefix of the current script name. +# Arguments: +# $1 - The format string. +# $@ - Arguments to the format string, just like printf. +function log() { + local format="$1" + shift + # shellcheck disable=SC2059 + printf "$CURRENT_SOURCE_FILE_NAME: $format\n" "$@" >&2 || true +} + +# shellcheck disable=SC2016 +USAGE="$CURRENT_SOURCE_FILE_NAME"' + +This script generate release assets +then print paths to the generated assets to STDOUT. + +You can use this script in github action like this: + +```yaml +name: Example workflow +on: + push: + tags: * + +jobs: + auto_release: + name: Automatic release + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run tools/'"$CURRENT_SOURCE_FILE_NAME"' + id: generate_release_assets + run: | + ASSETS=$(tools/'"$CURRENT_SOURCE_FILE_NAME"') + echo assets="$ASSETS" >> $GITHUB_OUTPUT + + - name: Automatic release + uses: marvinpinto/action-automatic-releases@latest + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + prerelease: false + files: ${{ steps.generate_release_assets.outputs.assets }} +``` + +'" +Usage: + $CURRENT_SOURCE_FILE_NAME -h + $CURRENT_SOURCE_FILE_NAME + +Options: + -h Show this screen." + +while getopts ':h' option; do + case "$option" in + h) + echo "$USAGE" + exit + ;; + \?) + log "[ERROR] Unknown option: -%s" "$OPTARG" + exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + +CURRENT_SOURCE_FILE_DIR="$(dirname -- "$CURRENT_SOURCE_FILE_PATH")" +cd -- "$CURRENT_SOURCE_FILE_DIR" + +source ./utils.sh + +# ------------------------------------------------------------------------------ + +check_ci + +BUILD_DIR="../build_generate-release-assets" + +configure_cmake_project .. $BUILD_DIR -DCMAKE_BUILD_TYPE=Release >&2 + +realpath "$BUILD_DIR"/errors.hpp diff --git a/tools/get-project-version.sh b/tools/get-project-version.sh new file mode 100755 index 0000000..ed2c1f8 --- /dev/null +++ b/tools/get-project-version.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# NOTE: +# Use /usr/bin/env to find shell interpreter for better portability. +# Reference: https://en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability + +# NOTE: +# Exit immediately if any commands (even in pipeline) +# exits with a non-zero status. +set -e +set -o pipefail + +# WARNING: +# This is not reliable when using POSIX sh +# and current script file is sourced by `source` or `.` +CURRENT_SOURCE_FILE_PATH="${BASH_SOURCE[0]:-$0}" +CURRENT_SOURCE_FILE_NAME="$(basename -- "$CURRENT_SOURCE_FILE_PATH")" + +# This function log messages to stderr works like printf +# with a prefix of the current script name. +# Arguments: +# $1 - The format string. +# $@ - Arguments to the format string, just like printf. +function log() { + local format="$1" + shift + # shellcheck disable=SC2059 + printf "$CURRENT_SOURCE_FILE_NAME: $format\n" "$@" >&2 || true +} + +# shellcheck disable=SC2016 +USAGE="$CURRENT_SOURCE_FILE_NAME"' + +This script prints the project version to STDOUT +for automatically creating tag in CI. + +'" +Usage: + $CURRENT_SOURCE_FILE_NAME -h + $CURRENT_SOURCE_FILE_NAME + +Options: + -h Show this screen." + +CURRENT_SOURCE_FILE_DIR="$(dirname -- "$CURRENT_SOURCE_FILE_PATH")" +cd -- "$CURRENT_SOURCE_FILE_DIR" + +function main() { + while getopts ':h' option; do + case "$option" in + h) + echo "$USAGE" + exit + ;; + \?) + log "[ERROR] Unknown option: -%s" "$OPTARG" + exit 1 + ;; + esac + done + shift $((OPTIND - 1)) + + source ./utils.sh + + check_ci + + BUILD_DIR=../build_get-project-version + + configure_cmake_project .. $BUILD_DIR >&2 + cd $BUILD_DIR + version=$(cmake --system-information | awk -F= '$1~/CMAKE_PROJECT_VERSION:STATIC/{print$2}') + if [ -z "$version" ]; then + false + fi + echo "v$version" +} + +main "$@" diff --git a/tools/utils.sh b/tools/utils.sh new file mode 100644 index 0000000..64dc771 --- /dev/null +++ b/tools/utils.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# NOTE: +# Use /usr/bin/env to find shell interpreter for better portability. +# Reference: https://en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability + +# NOTE: +# Exit immediately if any commands (even in pipeline) +# exits with a non-zero status. +set -e +set -o pipefail + +# This function generates an absolute path from a likely relative path argument. +# Arguments: +# $1 - The file path which can be relative or absolute. +# Returns: +# The absolute path of the given file path. +function get_absolute_path() { + local file_path + file_path="$1" + shift + + if [ -n "${file_path%%/*}" ]; then + file_path="$PWD/$file_path" + fi + echo "$file_path" +} + +# This function checks if the script is running in a CI environment. +# If not, it logs a warning message. +function check_ci() { + # NOTE: + # GitHub actions sets CI environment variable. + # Reference: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables + if [ -n "$CI" ]; then + return + fi + log "[WARNING] This script is meant to be run in CI environment" +} + +# This function runs a command in a specified directory. +# Arguments: +# $1 - The directory in which to run the command. +# $@ - The command to run. +function run_in_directory() { + local directory + directory="$1" + shift + + # NOTE: + # Do not use pushd and popd here for POSIX sh compliance. + local old_pwd + old_pwd="$PWD" + cd -- "$directory" + "$@" + cd -- "$old_pwd" +} + +# This function configures a CMake project. +# +# Arguments: +# $1 - The source directory of the project. +# $2 - The directory where the build will take place. +# $@ - Additional arguments to pass to the cmake command. +function configure_cmake_project() { + local source_dir + source_dir="$(get_absolute_path "$1")" + shift + + local binary_dir + binary_dir="$(get_absolute_path "$1")" + shift + + # NOTE: + # Do not use cmake -S and -B options for better compatibility. + + mkdir -p "$binary_dir" + + run_in_directory "$binary_dir" cmake "$source_dir" "$@" +} + +# This function builds a CMake project. +# +# Arguments: +# $1 - The source directory of the project. +# $2 - The directory where the build will take place. +# $@ - Additional arguments to pass to the cmake command. +function build_cmake_project() { + local source_dir + source_dir="$(get_absolute_path "$1")" + shift + local binary_dir + binary_dir="$(get_absolute_path "$1")" + shift + + # NOTE: + # Do not use cmake -S and -B options for better compatibility. + + configure_cmake_project "$source_dir" "$binary_dir" "$@" + + run_in_directory "$binary_dir" cmake --build . +}