diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..c24ca3c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# Lines starting with '#' are comments. +# Each line is a file pattern followed by one or more owners. + +* @rfprod diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..e78af2d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Reproduction** +Steps to reproduce the unwanted behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**User specification** +Filling in the specification helps address the issue faster. + +- **Device** + - type: [e.g. desktop, laptop, tablet, smartphone] + - OS: [e.g. Ubuntu 20.04, Windows 10, Android 12, iOS 8.1] +- **Browser** + - name: [e.g. Chromium, Chrome, Firefox] + - version: [e.g. Version 25.0.1364.126 (Official Build) snap (64-bit), Version 100.0.4896.75 (Official Build) (64-bit), 99.0 (64-bit)] + +**Additional context** +Add any extra context and/or screenshots about the feature request. + +**Preconditions for fixing** +Add any preconditions for fixing the problem. +This section is filled in by the maintainers in general case. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..8be251e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any extra context and/or screenshots about the feature request. + +**Preconditions for implementation** +Add any preconditions for implementation of the feature. +This section is filled in by the maintainers mainly. diff --git a/.github/ISSUE_TEMPLATE/maintenance.md b/.github/ISSUE_TEMPLATE/maintenance.md new file mode 100644 index 0000000..dfdfa6c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/maintenance.md @@ -0,0 +1,20 @@ +--- +name: Maintenance request +about: Suggest an idea for this project maintenance +title: '' +labels: '' +assignees: '' +--- + +**Describe the proposal** +A clear and concise description of the idea. Ex. improve project modularity, break down big libraries into smaller ones, decouple unrelated logic, update the project dependencies [...] + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions you've considered. + +**Additional context** +Add any other context and/or screenshots about the maintenance request. + +**Preconditions for maintenance** +Add any preconditions for performing the maintenance. +This section is filled in by the maintainers mainly. diff --git a/.github/actions/.gitkeep b/.github/actions/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.github/actions/workspace-cache/action.yml b/.github/actions/workspace-cache/action.yml new file mode 100644 index 0000000..dea378b --- /dev/null +++ b/.github/actions/workspace-cache/action.yml @@ -0,0 +1,46 @@ +# For more information see: +# - https://docs.github.com/en/actions/creating-actions/creating-a-composite-action +# - https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#outputsoutput_id + +# The action can be referenced in workflows like: +# - rfprod/cpp-workspace/.github/actions/workspace-cache@main +# ./.github/actions/workspace-cache + +name: workspace-cache +description: Configure and use workspace cache +outputs: + os: + description: Runner os + value: ${{ runner.os }} + commitsha: + description: GitHub commit SHA + value: ${{ steps.get-variables.outputs.commitsha }} + compilerversion: + description: gcc version + value: ${{ steps.get-variables.outputs.compilerversion }} + cache-hit: + description: Workspace cache hit + value: ${{ steps.workspace-cache.outputs.cache-hit }} + +runs: + using: 'composite' + steps: + - name: Get variables (commit hash, gcc version) + id: get-variables + shell: bash + run: | + echo "::set-output name=commitsha::$(echo ${GITHUB_SHA})"; + echo "::set-output name=compilerversion::$(g++ --version | head -n1 | sed -r s/\\s/-/g)"; + + - name: Workspace cache + uses: actions/cache@v5 + id: workspace-cache + with: + path: | + build/ + ~/.conan2/ + key: ${{ runner.os }}-cpp-${{ steps.get-variables.outputs.compilerversion }}-conan-${{ hashFiles('**/CMakeLists.txt', '**/conanfile.txt', '**/conanfile.py') }}-sha-${{ steps.get-variables.outputs.commitsha }} + restore-keys: | + ${{ runner.os }}-cpp-${{ steps.get-variables.outputs.compilerversion }}-conan-${{ hashFiles('**/CMakeLists.txt', '**/conanfile.txt', '**/conanfile.py') }}-sha- + ${{ runner.os }}-cpp-${{ steps.get-variables.outputs.compilerversion }}-conan- + ${{ runner.os }}-cpp- diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..fce2645 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,38 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/.github/workflows' + schedule: + interval: 'weekly' + day: 'monday' + time: '04:00' + groups: + all-actions: + patterns: + - '*' + commit-message: + prefix: 'ci' + include: 'scope' + open-pull-requests-limit: 1 + labels: + - 'CI/CD' + - 'Dependencies' + - 'Maintenance' + - package-ecosystem: 'github-actions' + directory: '/.github/actions' + schedule: + interval: 'weekly' + day: 'monday' + time: '04:00' + groups: + all-actions: + patterns: + - '*' + commit-message: + prefix: 'ci' + include: 'scope' + open-pull-requests-limit: 1 + labels: + - 'CI/CD' + - 'Dependencies' + - 'Maintenance' diff --git a/.github/workflows/trunk.yml b/.github/workflows/trunk.yml new file mode 100644 index 0000000..6728c00 --- /dev/null +++ b/.github/workflows/trunk.yml @@ -0,0 +1,313 @@ +name: trunk + +on: + push: + branches: [main] + +defaults: + run: + shell: bash --noprofile --norc -euo pipefail {0} + +env: + HISTFILE: '' + +concurrency: + group: ${{ github.ref_name }}.${{ github.sha }}.trunk + cancel-in-progress: true + +jobs: + test-build-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v7 + with: + fetch-depth: 2 + + - name: Source changed + id: src-changed + run: | + bash ./tools/shell/actions/detect-changes.sh \ + --pattern '^src/.*$' \ + --compare-with HEAD~1 + if [[ -f "$GITHUB_OUTPUT" ]] && grep -q "changed_files" "$GITHUB_OUTPUT"; then + echo "change=true" >> $GITHUB_OUTPUT + else + echo "change=false" >> $GITHUB_OUTPUT + fi + + - name: Dependencies changed + id: deps-changed + run: | + bash ./tools/shell/actions/detect-changes.sh \ + --pattern '^(conanfile\.(txt|py)|CMakeLists\.txt)$' \ + --compare-with HEAD~1 + if [[ -f "$GITHUB_OUTPUT" ]] && grep -q "changed_files" "$GITHUB_OUTPUT"; then + echo "change=true" >> $GITHUB_OUTPUT + else + echo "change=false" >> $GITHUB_OUTPUT + fi + + - name: Shell tools changed + id: shelltools-changed + run: | + bash ./tools/shell/actions/detect-changes.sh \ + --pattern '^tools/shell/.*$' \ + --compare-with HEAD~1 + if [[ -f "$GITHUB_OUTPUT" ]] && grep -q "changed_files" "$GITHUB_OUTPUT"; then + echo "change=true" >> $GITHUB_OUTPUT + else + echo "change=false" >> $GITHUB_OUTPUT + fi + + - name: Workspace cache + uses: ./.github/actions/workspace-cache + + - name: Lint shell + if: steps.shelltools-changed.outputs.change == 'true' + run: | + sudo apt-get install -y shellcheck + shellcheck tools/shell/*.sh tools/shell/utils/*.sh + + - name: Install C++ compiler and build tools + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + sudo apt-get update + sudo apt-get install -y build-essential cmake git python3-pip clang-format clang-tidy cppcheck + + - name: Install Conan + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + python3 -m venv .venv + source .venv/bin/activate + pip install conan + conan --version + deactivate + + - name: Install project dependencies + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + source .venv/bin/activate + conan install . --build=missing -s build_type="${BUILD_TYPE}" + deactivate + env: + BUILD_TYPE: Release + + - name: Check formatting + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + find src -name "*.cpp" -o -name "*.hpp" | xargs clang-format --dry-run -Werror + + - name: Lint sources (cppcheck) + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + cppcheck --enable=all --error-exitcode=1 --suppress=missingIncludeSystem src/ + + - name: Lint sources (clang-tidy) + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + cmake -S . -B build/clang-tidy \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -DCMAKE_TOOLCHAIN_FILE="$PWD/build/${BUILD_TYPE}/generators/conan_toolchain.cmake" \ + -DCMAKE_CXX_COMPILER=g++-13 \ + -DCMAKE_CXX_CLANG_TIDY=clang-tidy + cmake --build build/clang-tidy --parallel + env: + BUILD_TYPE: Release + + - name: Build release + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + id: build-release + run: | + cmake -S . -B "build/${BUILD_TYPE}" \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -DCMAKE_TOOLCHAIN_FILE="$PWD/build/${BUILD_TYPE}/generators/conan_toolchain.cmake" \ + -DCMAKE_CXX_COMPILER=g++-13 + cmake --build "build/${BUILD_TYPE}" \ + --parallel + echo "build_dir=build" >> $GITHUB_OUTPUT + env: + BUILD_TYPE: Release + + - name: Unit test + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + ctest --test-dir "build/${BUILD_TYPE}" \ + --build-config "${BUILD_TYPE}" \ + --output-on-failure + env: + BUILD_TYPE: Release + + - name: Determine version + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + id: version + run: | + VERSION=$(grep -oP 'project\(\s*\w+\s+VERSION\s+\K[0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt) + + BUILD_NUMBER="$(date +%s)" + + RELEASE_TAG="v${VERSION}+${BUILD_NUMBER}" + ARTIFACT_VERSION="${VERSION}-build-${BUILD_NUMBER}" + + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT + echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT + echo "artifact_version=${ARTIFACT_VERSION}" >> $GITHUB_OUTPUT + echo "đŸ“Ļ Version: ${VERSION}, Build: ${BUILD_NUMBER}, Tag: ${RELEASE_TAG}" + + - name: Package release artifacts + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + env: + BUILD_TYPE: Release + RELEASE_DIR: release + ARTIFACT_VERSION: ${{ steps.version.outputs.artifact_version }} + run: | + set -euo pipefail + + BUILD_DIR="build/${BUILD_TYPE}" + + if [ ! -d "$BUILD_DIR" ]; then + echo "::error::Build directory does not exist: $BUILD_DIR" + exit 1 + fi + + rm -rf "$RELEASE_DIR" + mkdir -p "$RELEASE_DIR/bin" \ + "$RELEASE_DIR/lib" \ + "$RELEASE_DIR/include" + + echo "Copy executables..." + if [ -d "$BUILD_DIR/bin" ]; then + find "$BUILD_DIR/bin" \ + -maxdepth 1 \ + -type f \ + -executable \ + -exec cp -v {} "$RELEASE_DIR/bin/" \; + fi + + echo "Copy fallback executables..." + find "$BUILD_DIR" \ + -type f \ + -executable \ + ! -name "*.so*" \ + ! -name "*.a" \ + ! -name "*.o" \ + ! -path "*/CMakeFiles/*" \ + ! -path "*/generators/*" \ + -exec cp -v {} "$RELEASE_DIR/bin/" \; + + echo "Copy static libraries..." + if [ -d "$BUILD_DIR/lib" ]; then + find "$BUILD_DIR/lib" \ + -type f \ + -name "*.a" \ + -exec cp -v {} "$RELEASE_DIR/lib/" \; + fi + + echo "Copy shared libraries..." + find "$BUILD_DIR" \ + -type f \ + -name "*.so*" \ + ! -path "*/CMakeFiles/*" \ + ! -path "*/generators/*" \ + -exec cp -v {} "$RELEASE_DIR/lib/" \; + + echo "Copy headers..." + if [ -d include ]; then + cp -rv include/. "$RELEASE_DIR/include/" + fi + + echo "Copy documentation..." + for file in README.md LICENSE CHANGELOG.md; do + if [ -f "$file" ]; then + cp -v "$file" "$RELEASE_DIR/" + fi + done + + cat > "$RELEASE_DIR/BUILD_INFO.txt" << EOF + Version: ${{ steps.version.outputs.version }} + Build Number: ${{ steps.version.outputs.build_number }} + Build Tag: ${{ steps.version.outputs.release_tag }} + Commit: ${{ github.sha }} + Branch: ${{ github.ref_name }} + Build Time: $(date -u +'%Y-%m-%dT%H:%M:%SZ') + EOF + + if ! find "$RELEASE_DIR/bin" -type f -executable -print -quit | grep -q .; then + echo "::error::No executable was added to the release package" + exit 1 + fi + + echo "Create archives..." + tar -czf "release-${ARTIFACT_VERSION}.tar.gz" \ + -C "$RELEASE_DIR" . + + ( + cd "$RELEASE_DIR" + zip -rq "../release-${ARTIFACT_VERSION}.zip" . + ) + + echo "Release package contents:" + find "$RELEASE_DIR" -type f -print | sort + + echo "Archive files:" + ls -lh \ + "release-${ARTIFACT_VERSION}.tar.gz" \ + "release-${ARTIFACT_VERSION}.zip" + + echo "TAR archive contents:" + tar -tzf "release-${ARTIFACT_VERSION}.tar.gz" | head -20 + + echo "ZIP archive contents:" + unzip -l "release-${ARTIFACT_VERSION}.zip" | head -20 + + - name: Upload release artifacts + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + uses: actions/upload-artifact@v3 + with: + name: cpp-release-${{ steps.version.outputs.artifact_version }} + path: | + release-*.tar.gz + release-*.zip + retention-days: 90 + + - name: Create GitHub Release + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.version.outputs.release_tag }} + name: Release ${{ steps.version.outputs.artifact_version }} + body: | + ## Release ${{ steps.version.outputs.version }} (Build ${{ steps.version.outputs.build_number }}) + + **Full Version**: `${{ steps.version.outputs.release_tag }}` + **Commit**: `${{ github.sha }}` + **Branch**: `${{ github.ref_name }}` + **Build Time**: $(date -u +'%Y-%m-%dT%H:%M:%SZ') + + ### Build Artifacts + - `release-${{ steps.version.outputs.artifact_version }}.tar.gz` + - `release-${{ steps.version.outputs.artifact_version }}.zip` + + ### Changes + - Source files updated + - All tests passing + - Release artifacts generated + + files: | + release-*.tar.gz + release-*.zip + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Notify deployment success + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + echo "✅ Release built and deployed successfully" + echo "đŸ“Ļ Version: ${{ steps.version.outputs.version }}" + echo "đŸ—ī¸ Build Number: ${{ steps.version.outputs.build_number }}" + echo "đŸˇī¸ Release Tag: ${{ steps.version.outputs.release_tag }}" + echo "📁 Artifact: release-${{ steps.version.outputs.artifact_version }}.{tar.gz,zip}" diff --git a/.github/workflows/validate-codeowners.yml b/.github/workflows/validate-codeowners.yml new file mode 100644 index 0000000..ff9d85f --- /dev/null +++ b/.github/workflows/validate-codeowners.yml @@ -0,0 +1,30 @@ +name: codeowners-validator + +on: + schedule: + - cron: '0 0 1 * *' + +defaults: + run: + shell: bash --noprofile --norc -euo pipefail {0} + +env: + HISTFILE: '' + +concurrency: + group: ${{ github.ref_name }}.${{ github.sha }}.codeowners-validator + cancel-in-progress: true + +jobs: + validate-codeowners: + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v7 + + - name: GitHub CODEOWNERS Validator + uses: mszostok/codeowners-validator@v0.7.4 + with: + checks: 'files,duppatterns' + experimental_checks: 'notowned' diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml new file mode 100644 index 0000000..2963327 --- /dev/null +++ b/.github/workflows/validate-pr.yml @@ -0,0 +1,149 @@ +name: validate-pr + +on: + pull_request: + branches: [main] + +defaults: + run: + shell: bash --noprofile --norc -euo pipefail {0} + +env: + HISTFILE: '' + +concurrency: + group: ${{ github.head_ref }}.${{ github.sha }}.validate-pr + cancel-in-progress: true + +jobs: + premerge: + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v7 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: Source changed + id: src-changed + run: | + bash ./tools/shell/actions/detect-changes.sh \ + --pattern '^src/.*$' \ + --compare-with origin/${{ github.base_ref }} + if [[ -f "$GITHUB_OUTPUT" ]] && grep -q "changed_files" "$GITHUB_OUTPUT"; then + echo "change=true" >> $GITHUB_OUTPUT + else + echo "change=false" >> $GITHUB_OUTPUT + fi + + - name: Dependencies changed + id: deps-changed + run: | + bash ./tools/shell/actions/detect-changes.sh \ + --pattern '^(conanfile\.(txt|py)|CMakeLists\.txt)$' \ + --compare-with origin/${{ github.base_ref }} + if [[ -f "$GITHUB_OUTPUT" ]] && grep -q "changed_files" "$GITHUB_OUTPUT"; then + echo "change=true" >> $GITHUB_OUTPUT + else + echo "change=false" >> $GITHUB_OUTPUT + fi + + - name: Shell tools changed + id: shelltools-changed + run: | + bash ./tools/shell/actions/detect-changes.sh \ + --pattern '^tools/shell/.*$' \ + --compare-with origin/${{ github.base_ref }} + if [[ -f "$GITHUB_OUTPUT" ]] && grep -q "changed_files" "$GITHUB_OUTPUT"; then + echo "change=true" >> $GITHUB_OUTPUT + else + echo "change=false" >> $GITHUB_OUTPUT + fi + + - name: Workspace cache + uses: ./.github/actions/workspace-cache + + - name: Validate the commit messages + if: github.event.pull_request.user.login != 'dependabot[bot]' + run: | + python3 -m venv .venv + source .venv/bin/activate + pip install -U pip Commitizen + git checkout -b premerge + git fetch origin main:main + cz check --rev-range main..premerge + deactivate + + - name: Lint shell + if: steps.shelltools-changed.outputs.change == 'true' + run: | + sudo apt-get install -y shellcheck + shellcheck tools/shell/*.sh tools/shell/utils/*.sh + + - name: Install C++ compiler and build tools + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + sudo apt-get update + sudo apt-get install -y build-essential cmake git python3-pip clang-format clang-tidy cppcheck + + - name: Install Conan + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + source .venv/bin/activate + pip install conan + conan --version + conan profile detect --force + + - name: Install project dependencies + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + source .venv/bin/activate + conan install . --build=missing -s build_type="${BUILD_TYPE}" + deactivate + env: + BUILD_TYPE: Release + + - name: Check formatting + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + find src -name "*.cpp" -o -name "*.hpp" | xargs clang-format --dry-run -Werror + + - name: Lint sources (cppcheck) + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + cppcheck --enable=all --error-exitcode=1 --suppress=missingIncludeSystem src/ + + - name: Lint sources (clang-tidy) + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + cmake -S . -B build/clang-tidy \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -DCMAKE_TOOLCHAIN_FILE="$PWD/build/${BUILD_TYPE}/generators/conan_toolchain.cmake" \ + -DCMAKE_CXX_COMPILER=g++-13 \ + -DCMAKE_CXX_CLANG_TIDY=clang-tidy + cmake --build build/clang-tidy --parallel + env: + BUILD_TYPE: Release + + - name: Build test + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + cmake -S . -B "build/${BUILD_TYPE}" \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -DCMAKE_TOOLCHAIN_FILE="$PWD/build/${BUILD_TYPE}/generators/conan_toolchain.cmake" \ + -DCMAKE_CXX_COMPILER=g++-13 + cmake --build "build/${BUILD_TYPE}" \ + --parallel + env: + BUILD_TYPE: Release + + - name: Unit test + if: ${{ steps.src-changed.outputs.change == 'true' || steps.deps-changed.outputs.change == 'true' }} + run: | + ctest --test-dir "build/${BUILD_TYPE}" \ + --build-config "${BUILD_TYPE}" \ + --output-on-failure + env: + BUILD_TYPE: Release diff --git a/.gitignore b/.gitignore index 0ba1c63..7336de9 100644 --- a/.gitignore +++ b/.gitignore @@ -52,18 +52,46 @@ Makefile install_manifest.txt compile_commands.json +# Conan +conaninfo.txt +conanbuildinfo.cmake +conan.lock + # Temporary files *.tmp *.log *.bak *.swp -# vcpkg -vcpkg_installed/ - # debug information files *.dwo # test output & cache Testing/ .cache/ + +# IDE directories +.idea/ +.vs/ +.clion/ +.settings/ +.vscode/* +!.vscode/settings.json +!.vscode/recommended-settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# IDE user files +*.user +*.vcxproj.user +CMakeUserPresets.json + +# OS-specific +.DS_Store +Thumbs.db +.AppleDouble +core + +# environment +.env diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c086851 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,13 @@ +{ + "recommendations": [ + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.cpptools-themes", + "sadesyllas.vscode-workspace-switcher", + "editorconfig.editorconfig", + "xaver.clang-format", + "foxundermoon.shell-format", + "timonwong.shellcheck", + "redhat.vscode-yaml" + ] +} diff --git a/.vscode/recommended-settings.json b/.vscode/recommended-settings.json new file mode 100644 index 0000000..8a9f21e --- /dev/null +++ b/.vscode/recommended-settings.json @@ -0,0 +1,24 @@ +{ + "clang-format.fallbackStyle": "Google", + "clang-format.language.apex.enable": false, + "clang-format.language.c.enable": false, + "clang-format.language.cpp.enable": true, + "clang-format.language.cuda.enable": false, + "clang-format.language.glsl.enable": false, + "clang-format.language.java.enable": false, + "clang-format.language.javascript.enable": false, + "clang-format.language.objective-c.enable": false, + "clang-format.language.objective-cpp.enable": false, + "clang-format.language.typescript.enable": false, + "clang-format.style": "Google", + "files.defaultLanguage": "cpp", + "files.hotExit": "onExit", + "yaml.format.enable": true, + "yaml.format.singleQuote": true, + "yaml.format.bracketSpacing": true, + "yaml.format.proseWrap": true, + "yaml.format.printWidth": true, + "yaml.validate": true, + "yaml.hover": true, + "yaml.completion": true +} diff --git a/.vscode/setting.json b/.vscode/setting.json new file mode 100644 index 0000000..4986666 --- /dev/null +++ b/.vscode/setting.json @@ -0,0 +1,42 @@ +{ + "[cpp]": { + "editor.defaultFormatter": "xaver.clang-format" + }, + "[sh]": { + "editor.defaultFormatter": "timonwong.shellcheck" + }, + "[shellscript]": { + "editor.defaultFormatter": "foxundermoon.shell-format" + }, + "editor.codeActionsOnSave": { + "source.organizeImports": "never", + "source.fixAll.cpp": "explicit", + "source.fixAll.clang": "explicit" + }, + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "files.autoSave": "afterDelay", + "files.autoSaveDelay": 1500, + "files.trimTrailingWhitespace": true, + "shellcheck.customArgs": [ + "-x" + ], + "shellcheck.enable": true, + "shellcheck.enableQuickFix": false, + "shellcheck.exclude": [], + "shellcheck.executablePath": "shellcheck", + "shellcheck.ignorePatterns": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/node_modules/*/**": true + }, + "shellcheck.run": "onType", + "shellcheck.useWorkspaceRootAsCwd": true, + "cmake.buildDirectory": "${workspaceFolder}/build/Debug", + "cmake.configureSettings": { + "CMAKE_TOOLCHAIN_FILE": "${workspaceFolder}/build/Debug/generators/conan_toolchain.cmake", + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_C_COMPILER": "gcc-13", + "CMAKE_CXX_COMPILER": "g++-13" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..76b9cca --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.20) + +project(cpp-workspace VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_executable(cpp-workspace-app + src/main.cpp +) + +# Add find_package() and target_link_libraries() here +# for each dependency installed by Conan. + +find_package(fmt CONFIG REQUIRED) +target_link_libraries(cpp-workspace-app PRIVATE fmt::fmt) + +# enable_testing() diff --git a/README.md b/README.md index 7be1a4f..7f828f4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,119 @@ # cpp-workspace + C++ workspace template with workflow automation. + +## Requirements + +In order to run your own copy of the project one must fulfill the following requirements. + +### Supported operating systems + +- 🏆 **Debian based Linux** + - see help for available options: + ```bash + # always inspect scripts before running them on your machine + bash tools/shell/install.sh ? + ``` + - install all dependencies required to work with the project: + ```bash + # always inspect scripts before running them on your machine + bash tools/shell/install.sh all + ``` +- 🆗 **macOS** + - see help for available options: + ```bash + # always inspect scripts before running them on your machine + bash tools/shell/install.sh ? + ``` + - install all dependencies required to work with the project (some dependencies may be missing): + ```bash + # always inspect scripts before running them on your machine + bash tools/shell/install.sh all osx + ``` +- 🤷 **Windows** + - Install using one of the following approaches: + - **MSVC** (recommended): Download and install [Visual Studio Community](https://visualstudio.microsoft.com/community/) with C++ build tools + - **MinGW**: Install via [MSYS2](https://www.msys2.org/) or [Chocolatey](https://chocolatey.org/) + - **WSL2**: Use Windows Subsystem for Linux and follow the Linux instructions + - Additionally, install [CMake](https://cmake.org/download/) and [Git for Windows](https://git-scm.com/download/win). + - given that the dev environment is set up, the following commands should be used to install `shellcheck` via PowerShell; + ```powershell + iwr -useb get.scoop.sh | iex + scoop install shellcheck + ``` + - recommended shell: [Git for Windows](https://gitforwindows.org/) > `Git BASH`; + - configure Git to use LF as a carriage return + ```bash + git config --global core.autocrlf false + git config --global core.eol lf + +### Integrated development environment + +🏆 **Visual Studio Code** - recommended for all operating systems + +Suggested extensions: +- C/C++ Extension Pack (Microsoft) +- CMake Tools (Microsoft) +- CMake (twxs) + +🆗 **Visual Studio** - for Windows development + +### Core dependencies + +- [Git]((https://git-scm.com/)) +- [Python](https://www.python.org/) or [NodeJS](https://nodejs.org/) `for conventional commits` +- [Bash 5](https://www.gnu.org/software/bash/) `for scripting` +- [CMake](https://cmake.org/) +- A **Build system** (one of): + - [Make](https://en.wikipedia.org/wiki/Make_(software)) + - [Ninja](https://ninja-build.org/) +- A **C++ compiler** (one of): + - [GCC](https://gcc.gnu.org/) + - [Clang](https://clang.llvm.org/) + - [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/) + +## Committing changes to the repo + +### Linux + +Using [commitizen cli](https://github.com/commitizen/cz-cli) is mandatory. + +The commit message are validated during the premerge checks. + +It is assumed that [Node.js](https://nodejs.org/) is installed. + +Given the [NodeJS](https://nodejs.org/) is installed, and [commitizen cli is installed as a global dependency](https://github.com/commitizen/cz-cli#conventional-commit-messages-as-a-global-utility), the following command should be used to initiate the commit process + +```bash +git cz +``` + +Alternatively, given there are no conflicts with other projects that use [the commitizen npm package](https://www.npmjs.com/package/commitizen), one could install commitizen globally via `pypi` like this + +```bash +sudo pip3 install -U Commitizen +``` + +### OSX + +Using [commitizen](https://pypi.org/project/commitizen/) is mandatory. + +The commit message are validated during the premerge checks. + +After installing the package as a global utility using the following command + +```bash +brew install commitizen +``` + +one can use one of the following commands to initiate the commit process + +```bash +cz commit +``` + +or + +```bash +cz c +``` diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..e326745 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,14 @@ +from conan import ConanFile +from conan.tools.cmake import cmake_layout + + +class AppConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + + generators = "CMakeDeps", "CMakeToolchain" + + def requirements(self): + self.requires("fmt/11.2.0") # Replace with your dependencies + + def layout(self): + cmake_layout(self) diff --git a/src/.gitkeep b/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..3c8f40c --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,21 @@ +#include +#include + +using std::cout; +using std::format; + +int main() { + const char string[]{"this is a string"}; + + for (auto *p = string; *p; p++) { + cout << format("char = {}\n", *p); + } + + for (int i{0}; string[i]; i++) { + cout << format("{} char = {}\n", i, string[i]); + } + + for (const auto &el : string) { + cout << format("element = {}\n", el); + } +} diff --git a/tools/.gitkeep b/tools/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tools/shell/actions/detect-changes.md b/tools/shell/actions/detect-changes.md new file mode 100644 index 0000000..f5cd5f8 --- /dev/null +++ b/tools/shell/actions/detect-changes.md @@ -0,0 +1,103 @@ +# detect-changes.sh + +## Purpose + +Detect files changed between commits matching a regex pattern + +## Usage + +```bash +./tools/shell/actions/detect-changes.sh --pattern PATTERN [--compare-with COMPARE_WITH] +``` + +### GitHub Actions usage: + +```yaml +... +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + detect-changes: + runs-on: ubuntu-latest + outputs: + shell: ${{ steps.shell-changed.outputs.changed }} + typescript: ${{ steps.typescript-changed.outputs.changed }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Shell files changed + id: shell-changed + run: | + bash ./tools/shell/actions/detect-changes.sh \ + --pattern '\.sh$' \ + --compare-with origin/${{ github.base_ref }} + if [[ -f "$GITHUB_OUTPUT" ]] && grep -q "changed_files" "$GITHUB_OUTPUT"; then + echo "change=true" >> $GITHUB_OUTPUT + else + echo "change=false" >> $GITHUB_OUTPUT + fi + + - name: TypeScript files changed + id: typescript-changed + run: | + bash ./tools/shell/actions/detect-changes.sh \ + --pattern '\.ts$' \ + --compare-with origin/${{ github.base_ref }} + if [[ -f "$GITHUB_OUTPUT" ]] && grep -q "changed_files" "$GITHUB_OUTPUT"; then + echo "change=true" >> $GITHUB_OUTPUT + else + echo "change=false" >> $GITHUB_OUTPUT + fi +... +``` + +```yaml +... +on: + push: + branches: [main] + +jobs: + detect-changes: + runs-on: ubuntu-latest + outputs: + backend: ${{ steps.backend-changed.outputs.changed }} + frontend: ${{ steps.frontend-changed.outputs.changed }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Backend changed + id: backend-changed + run: | + bash ./tools/shell/actions/detect-changes.sh \ + --pattern '^src/backend/.*\.(ts)$' \ + --compare-with HEAD~1 + if [[ -f "$GITHUB_OUTPUT" ]] && grep -q "changed_files" "$GITHUB_OUTPUT"; then + echo "change=true" >> $GITHUB_OUTPUT + else + echo "change=false" >> $GITHUB_OUTPUT + fi + + - name: Drontend changed + id: frontend-changed + run: | + bash ./tools/shell/actions/detect-changes.sh \ + --pattern '^src/frontend/.*\.(ts|html|css)$' \ + --compare-with HEAD~1 + if [[ -f "$GITHUB_OUTPUT" ]] && grep -q "changed_files" "$GITHUB_OUTPUT"; then + echo "change=true" >> $GITHUB_OUTPUT + else + echo "change=false" >> $GITHUB_OUTPUT + fi +... +``` diff --git a/tools/shell/actions/detect-changes.sh b/tools/shell/actions/detect-changes.sh new file mode 100644 index 0000000..dbb6293 --- /dev/null +++ b/tools/shell/actions/detect-changes.sh @@ -0,0 +1,213 @@ +#!/bin/bash + +######################################################################################## +# Script: detect-changes.sh +# Purpose: Detect files changed between commits matching a regex pattern +# Usage: ./detect-changes.sh --pattern PATTERN [--compare-with COMPARE_WITH] [--verbose] +######################################################################################## + +set -euo pipefail + +# Default values +PATTERN="" +COMPARE_WITH="origin/main" +VERBOSE=false + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Print usage instructions. +print_usage() { + cat << EOF +Usage: $0 --pattern PATTERN [OPTIONS] + +Required: + --pattern PATTERN Regular expression pattern to match files + +Optional: + --compare-with COMPARE_WITH Branch or commit to compare against + (default: origin/main) + --verbose Enable verbose output + --help Display this help message + +Examples: + $0 --pattern '\.py$' + $0 --pattern '^src/.*\.ts$' --compare-with develop + $0 --pattern '(package\.json|\.lockfile)$' --compare-with HEAD~3 + +EOF +} + +# Print an informational message. +log_info() { + echo -e "${GREEN}[INFO]${NC} $*" >&2 +} + +# Print a warning message. +log_warn() { + # shellcheck disable=SC2317 + echo -e "${YELLOW}[WARN]${NC} $*" >&2 +} + +# Print an error message. +log_error() { + echo -e "${RED}[ERROR]${NC} $*" >&2 +} + +# Print a debugging message. +log_verbose() { + if [[ "$VERBOSE" == true ]]; then + echo -e "${YELLOW}[DEBUG]${NC} $*" >&2 + fi +} + +# Parse input arguments. +parse_arguments() { + while [[ $# -gt 0 ]]; do + case "$1" in + --pattern) + if [[ -z "${2:-}" ]]; then + log_error "Pattern argument requires a value" + print_usage + exit 1 + fi + PATTERN="$2" + shift 2 + ;; + --compare-with) + if [[ -z "${2:-}" ]]; then + log_error "compare-with argument requires a value" + print_usage + exit 1 + fi + COMPARE_WITH="$2" + shift 2 + ;; + --verbose) + VERBOSE=true + shift + ;; + --help) + print_usage + exit 0 + ;; + *) + log_error "Unknown option: $1" + print_usage + exit 1 + ;; + esac + done +} + +# Validate input argument values. +validate_inputs() { + if ! git rev-parse --git-dir > /dev/null 2>&1; then + log_error "Not a git repository" + exit 1 + fi + + if [[ -z "$PATTERN" ]]; then + log_error "Pattern is required" + print_usage + exit 1 + fi + + local TEST_STRING + TEST_STRING="test_file_pattern.txt" + if ! grep -E "$PATTERN" /dev/null 2>&1; then + if ! echo "$TEST_STRING" | grep -E "$PATTERN" 2>/dev/null && ! echo "" | grep -E "$PATTERN" 2>/dev/null; then + local GREP_ERROR + GREP_ERROR=$(echo "$TEST_STRING" | grep -E "$PATTERN" 2>&1 || true) + if echo "$GREP_ERROR" | grep -qi "invalid\|error"; then + log_error "Invalid regex pattern: $PATTERN" + log_error "Grep error: $GREP_ERROR" + exit 1 + fi + fi + fi + + log_verbose "Pattern: $PATTERN" + log_verbose "Compare with: $COMPARE_WITH" +} + +# Check whether a git ref exists. +check_ref_exists() { + local REF + REF="$1" + if ! git rev-parse --verify "$REF" > /dev/null 2>&1; then + log_error "Reference not found: $REF" + log_info "Available branches:" + git branch -a | sed 's/^/ /' + exit 1 + fi +} + +# Collect changed files between git refs. +get_changed_files() { + local BASE_REF + BASE_REF="$1" + local HEAD_REF + HEAD_REF="HEAD" + + log_verbose "Fetching diff between $BASE_REF and $HEAD_REF" + + git diff --name-only --diff-filter=AMDRC "$BASE_REF..$HEAD_REF" +} + +# Filter files using a pattern provided via input arguments. +filter_files_by_pattern() { + local REGX_PATTERN + REGX_PATTERN="$1" + + local FILE + while IFS= read -r FILE; do + if [[ -z "$FILE" ]]; then + continue + fi + + log_verbose "Checking file: $FILE" + + if [[ "$FILE" =~ $REGX_PATTERN ]]; then + echo "$FILE" + fi + done +} + +# Entrypoint. +main() { + parse_arguments "$@" + validate_inputs + + log_info "Detecting changes matching pattern: $PATTERN" + log_info "Comparing HEAD against: $COMPARE_WITH" + + check_ref_exists "$COMPARE_WITH" + + local CHANGED_FILES + CHANGED_FILES=$(get_changed_files "$COMPARE_WITH" | filter_files_by_pattern "$PATTERN") + + if [[ -z "$CHANGED_FILES" ]]; then + log_info "No matching files found" + exit 0 + fi + + log_info "Found matching files:" + echo "$CHANGED_FILES" | while IFS= read -r file; do + echo " - $file" + done + + if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + local FILES_JSON + FILES_JSON=$(echo "$CHANGED_FILES" | jq -R -s -c 'split("\n")[:-1]') + echo "changed_files=$FILES_JSON" >> "$GITHUB_OUTPUT" + log_verbose "GitHub Actions output set: changed=$FILES_JSON" + fi + + exit 0 +} + +main "$@" diff --git a/tools/shell/build.sh b/tools/shell/build.sh new file mode 100644 index 0000000..050d59f --- /dev/null +++ b/tools/shell/build.sh @@ -0,0 +1,214 @@ +#!/bin/bash + +set -euo pipefail + +# Color definitions +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Help function +show_help() { + echo -e "${BLUE}Usage: ./tools/build.sh [OPTIONS]${NC}" + + echo -e "${YELLOW}Options:${NC} + -h, --help Show this help message + -c, --clean Clean build directory before building + -r, --release Build in Release mode (default: Debug) + -p, --package Package release artifacts + -b DIR, --build-dir DIR Specify build directory (default: build)" + + echo -e "${YELLOW}Environment Variables:${NC} + BUILD_TYPE Set build type: Debug or Release (default: Debug) + CLEAN_BUILD Set to true to clean before building (default: false)" + + echo -e "${YELLOW}Examples:${NC} + ./tools/build.sh # Debug build + ./tools/build.sh --release # Release build + ./tools/build.sh --clean --release # Clean Release build + ./tools/build.sh --package # Debug build + package + BUILD_TYPE=Release ./tools/build.sh # Release build (env var)" +} + +# Configuration +BUILD_DIR="build" +BUILD_TYPE="${BUILD_TYPE:-Debug}" +CLEAN_BUILD="${CLEAN_BUILD:-false}" +PACKAGE_RELEASE="${PACKAGE_RELEASE:-false}" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_help + exit 0 + ;; + -c|--clean) + CLEAN_BUILD=true + shift + ;; + -r|--release) + BUILD_TYPE="Release" + shift + ;; + -p|--package) + PACKAGE_RELEASE=true + shift + ;; + -b|--build-dir) + BUILD_DIR="$2" + shift 2 + ;; + *) + echo -e "${RED}Unknown option: $1${NC}" + show_help + exit 1 + ;; + esac +done + +# Helper functions +log_info() { + echo -e "${BLUE}â„šī¸ $*${NC}" +} + +log_success() { + echo -e "${GREEN}✅ $*${NC}" +} + +log_warning() { + echo -e "${YELLOW}âš ī¸ $*${NC}" +} + +log_error() { + echo -e "${RED}❌ $*${NC}" +} + +# Extract version from CMakeLists.txt +get_version() { + grep -oP 'project\(\s*\w+\s+VERSION\s+\K[0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt 2>/dev/null || echo "0.0.0" +} + +# Main build script +main() { + log_info "Starting build process..." + log_info "Build Type: ${BUILD_TYPE}" + log_info "Build Directory: ${BUILD_DIR}" + + # Get version + local VERSION + VERSION=$(get_version) + local BUILD_NUMBER + BUILD_NUMBER="$(date +%s)" + local ARTIFACT_VERSION + ARTIFACT_VERSION="${VERSION}-build-${BUILD_NUMBER}" + + log_info "Version: ${VERSION}, Build: ${BUILD_NUMBER}" + + # Clean build directory if requested + if [ "$CLEAN_BUILD" = true ]; then + log_info "Cleaning build directory..." + rm -rf "$BUILD_DIR" + fi + + # Create build directory + mkdir -p "$BUILD_DIR" + + log_info "Installing Conan dependencies..." + if conan install . --build=missing -s build_type="$BUILD_TYPE"; then + log_success "Dependencies installed" + else + log_error "Failed to install dependencies" + exit 1 + fi + + log_info "Configuring CMake (${BUILD_TYPE})..." + if cmake -S . -B "$BUILD_DIR"/"$BUILD_TYPE" -DCMAKE_TOOLCHAIN_FILE="$PWD"/"$BUILD_DIR"/"$BUILD_TYPE"/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" -DCMAKE_C_COMPILER=gcc-13 -DCMAKE_CXX_COMPILER=g++-13; then + log_success "CMake configured" + else + log_error "CMake configuration failed" + exit 1 + fi + + log_info "Building project..." + if cmake --build "$BUILD_DIR" --config "$BUILD_TYPE"; then + log_success "Build completed" + else + log_error "Build failed" + exit 1 + fi + + log_info "Running tests..." + if ctest --test-dir "$BUILD_DIR" --output-on-failure; then + log_success "All tests passed" + else + log_warning "Some tests failed (continuing)" + fi + + local BIN_NAME + BIN_NAME=cpp-workspace-app + + if [ -f "$BUILD_DIR/bin/$BIN_NAME" ]; then + log_info "Running executable..." + "$BUILD_DIR/bin/$BIN_NAME" + log_success "Executable ran successfully" + else + log_warning "Executable not found at $BUILD_DIR/bin/$BIN_NAME" + fi + + local RELEASE_DIR + RELEASE_DIR="$BUILD_DIR/release" + + mkdir -p "$RELEASE_DIR" + cat > "$RELEASE_DIR/BUILD_INFO.txt" << EOF +Version: ${VERSION} +Build Number: ${BUILD_NUMBER} +Build Type: ${BUILD_TYPE} +Build Time: $(date -u +'%Y-%m-%dT%H:%M:%SZ') +CMake Version: $(cmake --version | head -n1) +Conan Version: $(conan --version 2>/dev/null || echo "unknown") +Build Directory: $(pwd)/$BUILD_DIR +EOF + log_success "Build info generated: $RELEASE_DIR/BUILD_INFO.txt" + + if [ "$PACKAGE_RELEASE" = true ]; then + log_info "Packaging release artifacts..." + + mkdir -p "$RELEASE_DIR" + + # Copy executable + if [ -f "$BUILD_DIR/bin/$BIN_NAME" ]; then + cp "$BUILD_DIR/bin/$BIN_NAME" "$RELEASE_DIR/" + fi + + # Copy documentation + [ -f "README.md" ] && cp README.md "$RELEASE_DIR/" + [ -f "LICENSE" ] && cp LICENSE "$RELEASE_DIR/" + [ -f "CHANGELOG.md" ] && cp CHANGELOG.md "$RELEASE_DIR/" + + # Create archives + ARCHIVE_DIR=$(pwd) + cd "$BUILD_DIR" + tar -czf "../release-${ARTIFACT_VERSION}.tar.gz" release/ + zip -r "../release-${ARTIFACT_VERSION}.zip" release/ > /dev/null + cd "$ARCHIVE_DIR" + + log_success "Release artifacts created:" + ls -lh "release-${ARTIFACT_VERSION}".{tar.gz,zip} + fi + + log_success "Build process completed successfully!" + echo "" + echo -e "${BLUE}Build Summary:${NC}" + echo " Version: ${VERSION}" + echo " Build Number: ${BUILD_NUMBER}" + echo " Build Type: ${BUILD_TYPE}" + echo " Build Directory: ${BUILD_DIR}" + [ "$PACKAGE_RELEASE" = true ] && echo " Artifacts: release-${ARTIFACT_VERSION}.{tar.gz,zip}" + echo "" +} + +# Run main function +main "$@" diff --git a/tools/shell/format.sh b/tools/shell/format.sh new file mode 100644 index 0000000..c210f23 --- /dev/null +++ b/tools/shell/format.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -euo pipefail + +clang-format src/* -i -Werror + +cppcheck --enable=all --error-exitcode=1 --suppress=missingIncludeSystem src/ diff --git a/tools/shell/install.sh b/tools/shell/install.sh new file mode 100755 index 0000000..cf524ad --- /dev/null +++ b/tools/shell/install.sh @@ -0,0 +1,140 @@ +#!/bin/bash + +source tools/shell/utils/config.sh +source tools/shell/utils/colors.sh '' +source tools/shell/utils/print-utils.sh '' + +## +# Prints the script usage instuctions. +## +print_usage() { + print_info_title "<< ${0} usage >>" + print_usage_tip "bash tools/shell/install.sh" "print help" + print_usage_tip "bash tools/shell/install.sh all" "install core tools, commitizen, and shellcheck on Linux" + print_usage_tip "bash tools/shell/install.sh all osx" "install core tools, commitizen, and shellcheck on OSX" + print_usage_tip "bash tools/shell/install.sh commitizen" "install commitizen on Linux" + print_usage_tip "bash tools/shell/install.sh commitizen osx" "install commitizen on OSX" + print_usage_tip "bash tools/shell/install.sh shellcheck" "install shellcheck on Linux" + print_usage_tip "bash tools/shell/install.sh shellcheck osx" "install shellcheck on OSX" + print_gap +} + +## +# Core tools for Linux. +## +install_core_tools_linux() { + sudo apt install -y build-essential git cmake g++ gdb clang-format clang-tidy cppcheck + print_gap + + git --version + print_gap + + cmake --version + print_gap + + g++ --version + print_gap + + gdb --version + print_gap +} + +## +# Core tools for OSX. +## +install_core_tools_osx() { + brew install cmake gcc git +} + +install_core_tools() { + if [ "$1" = "osx" ]; then + install_commitizen_osx + else + install_core_tools_linux + fi +} + +## +# Install commitizen, cz-conventional-changelog, and npm-check-updates on Linux. +# It is assumed that NodeJS is installed. +## +install_commitizen_linux() { + print_info_title "<< Intalling commitizen, cz-conventional-changelog, and npm-check-updates via NPM globaly >>" + print_gap + + sudo npm install -g commitizen@latest cz-conventional-changelog@latest npm-check-updates@latest || exit 1 +} + +## +# Install commitizen on OSX. +# It is assumed that Python is installed. +## +install_commitizen_osx() { + print_info_title "<< Intalling commitizen via pypi globally >>" + print_gap + + brew install commitizen || exit 1 +} + +## +# Install global dependencies. +# Ref: https://commitizen.github.io/cz-cli/ +## +install_commitizen() { + if [ "$1" = "osx" ]; then + install_commitizen_osx + else + install_commitizen_linux + fi +} + +## +# Install shellcheck on Linux. +## +install_shellcheck_linux() { + print_info_title "<< Installing shellcheck on Linux >>" + print_gap + + sudo apt -y install shellcheck +} + +## +# Install shellcheck on OSX. +## +install_shellcheck_osx() { + print_info_title "<< Installing shellcheck on OSX >>" + print_gap + + brew install shellcheck +} + +## +# Install shellcheck. +# Ref: https://www.shellcheck.net/ +## +install_shellcheck() { + if [ "$1" = "osx" ]; then + install_shellcheck_osx + else + install_shellcheck_linux + fi + shellcheck --version +} + +## +# Dependencies installation control flow. +## +if [ "$1" = "?" ]; then + print_usage +elif [ "$1" = "all" ]; then + install_core_tools "$2" + install_shellcheck "$2" + install_commitizen "$2" +elif [ "$1" = "commitizen" ]; then + install_commitizen "$2" +elif [ "$1" = "shellcheck" ]; then + install_shellcheck "$2" +else + print_usage + exit 1 +fi diff --git a/tools/shell/utils/colors.sh b/tools/shell/utils/colors.sh new file mode 100755 index 0000000..573ab8e --- /dev/null +++ b/tools/shell/utils/colors.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +## +# Colors definitions. +# +# Black 0;30 Dark Gray 1;30 +# Red 0;31 Light Red 1;31 +# Green 0;32 Light Green 1;32 +# Brown/Orange 0;33 Yellow 1;33 +# Blue 0;34 Light Blue 1;34 +# Purple 0;35 Light Purple 1;35 +# Cyan 0;36 Light Cyan 1;36 +# Light Gray 0;37 White 1;37 +## + +# shellcheck disable=SC2034 + +DEFAULT='\033[0m' +BLACK='\033[0;30m' +DARK_GRAY='\033[1;30m' +RED='\033[0;31m' +LIGHT_RED='\033[1;31m' +GREEN='\033[0;32m' +LIGHT_GREEN='\033[1;32m' +BROWN='\033[0;33m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +LIGHT_BLUE='\033[1;34m' +PURPLE='\033[0;35m' +LIGHT_PURPLE='\033[1;35m' +CYAN='\033[0;36m' +LIGHT_CYAN='\033[1;36m' +LIGHT_GRAY='\033[0;37m' +WHITE='\033[1;37m' + +## +# Available color names. +## +REGISTERED_COLOR_NAMES=( + DEFAULT + BLACK + DARK_GRAY + RED + LIGHT_RED + GREEN + LIGHT_GREEN + BROWN + YELLOW + BLUE + LIGHT_BLUE + PURPLE + LIGHT_PURPLE + CYAN + LIGHT_CYAN + LIGHT_GRAY + WHITE +) + +## +# Available color values. +## +REGISTERED_COLOR_VARS=( + "$DEFAULT" + "$BLACK" + "$DARK_GRAY" + "$RED" + "$LIGHT_RED" + "$GREEN" + "$LIGHT_GREEN" + "$BROWN" + "$YELLOW" + "$BLUE" + "$LIGHT_BLUE" + "$PURPLE" + "$LIGHT_PURPLE" + "$CYAN" + "$LIGHT_CYAN" + "$LIGHT_GRAY" + "$WHITE" +) + +## +# Reports supported colors to the terminal. +## +print_supported_colors() { + local TITLE="<< COLORS >>" + printf " + ${LIGHT_BLUE} %s ${DEFAULT}\n" "$TITLE" + + ## + # Prints registered colors. + ## + for REGISTERED_COLOR_NAME in "${REGISTERED_COLOR_NAMES[@]}"; do printf " + ${DEFAULT} - ${!REGISTERED_COLOR_NAME}%s${DEFAULT} = ${!REGISTERED_COLOR_NAME}%s${DEFAULT}" "$REGISTERED_COLOR_NAME" "${!REGISTERED_COLOR_NAME}"; done + + local INFO="Use this colors in other module related scripts like tools/shell/lint.sh, tools/shell/test.sh etc." + printf "\n\n${DEFAULT} %s\n\n" "$INFO" +} + +## +# Colors usage. +## +if [ "$1" = "?" ]; then + print_supported_colors +fi diff --git a/tools/shell/utils/config.sh b/tools/shell/utils/config.sh new file mode 100644 index 0000000..a7b62d5 --- /dev/null +++ b/tools/shell/utils/config.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -o errexit # do not continue on error +# set -o nounset # error on unset variables +set -o pipefail # error if at least one command in a pipe fails + +# enable debug mode if a $TRACE variable is set +if [[ "${TRACE-0}" == "1" ]]; then + set -o xtrade +fi diff --git a/tools/shell/utils/print-utils.sh b/tools/shell/utils/print-utils.sh new file mode 100644 index 0000000..1414b82 --- /dev/null +++ b/tools/shell/utils/print-utils.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +source tools/shell/utils/colors.sh '' + +print_gap() { + printf "\n\n" +} + +print_usage_tip() { + local COMMAND + COMMAND=$1 + local DESCRIPTION + DESCRIPTION=$2 + printf " + ${DEFAULT} - ${YELLOW}%s${DEFAULT}: %s${DEFAULT}" "$COMMAND" "$DESCRIPTION" +} + +print_name_and_value() { + local NAME + NAME=$1 + local VALUE + VALUE=$2 + printf " + ${DEFAULT} - ${DEFAULT}%s${DEFAULT}: ${YELLOW}%s${DEFAULT}" "$NAME" "$VALUE" +} + +print_value() { + local VALUE + VALUE=$1 + printf " + ${DEFAULT} - ${YELLOW}%s${DEFAULT}" "$VALUE" +} + +print_info_title() { + local TITLE + TITLE=$1 + printf " + ${CYAN}%s + ${DEFAULT}" "$TITLE" +} + +print_info_message() { + local MESSAGE + MESSAGE=$1 + printf " + ${LIGHT_CYAN}%s${DEFAULT}" "$MESSAGE" +} + +print_error_title() { + local TITLE + TITLE=$1 + printf " + ${RED}%s + ${DEFAULT}" "$TITLE" +} + +print_warning_message() { + local MESSAGE + MESSAGE=$1 + printf " + ${LIGHT_RED}%s${DEFAULT}" "$MESSAGE" +} + +print_success_title() { + local TITLE + TITLE=$1 + printf " + ${GREEN}%s + ${DEFAULT}" "$TITLE" +} + +print_success_sessage() { + local MESSAGE + MESSAGE=$1 + printf " + ${LIGHT_GREEN}%s${DEFAULT}" "$MESSAGE" +} + +if [ "$1" = "?" ]; then + printf " + ${LIGHT_BLUE}%s\n + ${DEFAULT} Importing this file in another files exposes the following functions:\n + ${DEFAULT} - ${YELLOW} print_gap + ${DEFAULT} - ${YELLOW} print_info_title + ${DEFAULT} - ${YELLOW} print_error_title + ${DEFAULT} - ${YELLOW} print_warning_message + ${DEFAULT} - ${YELLOW} print_success_title + ${DEFAULT} - ${YELLOW} print_name_and_value + ${DEFAULT}\n\n" "USAGE" +fi