From 2fd51337ed568c59bae5ec798fb7af3ffa950a53 Mon Sep 17 00:00:00 2001 From: Joseph Hickey Date: Fri, 24 Feb 2023 15:24:00 -0500 Subject: [PATCH] Fix #1370, workflow to validate OSAL API Adds a new build and test workflow that includes an API validation with both C and C++ compilers. This should catch any cases where syntactical elements that work in C but do not work in C++ appear in the headers. --- .github/actions/check-coverage/action.yml | 34 ++++++ .github/scripts/lcov-output.xslt | 105 ++++++++++++++++++ .../workflows/build-osal-documentation.yml | 68 ------------ .github/workflows/codeql-cfe-build.yml | 13 --- .github/workflows/codeql-osal-default.yml | 15 --- .github/workflows/format-check.yml | 11 -- .github/workflows/local_unit_test.yml | 70 ------------ .github/workflows/standalone-build.yml | 92 +++++++++++++++ .github/workflows/static-analysis.yml | 15 --- CMakeLists.txt | 48 +++++++- check_header.c.in | 4 + check_header.cpp.in | 7 ++ 12 files changed, 288 insertions(+), 194 deletions(-) create mode 100644 .github/actions/check-coverage/action.yml create mode 100644 .github/scripts/lcov-output.xslt delete mode 100644 .github/workflows/build-osal-documentation.yml delete mode 100644 .github/workflows/codeql-cfe-build.yml delete mode 100644 .github/workflows/codeql-osal-default.yml delete mode 100644 .github/workflows/format-check.yml delete mode 100644 .github/workflows/local_unit_test.yml create mode 100644 .github/workflows/standalone-build.yml delete mode 100644 .github/workflows/static-analysis.yml create mode 100644 check_header.c.in create mode 100644 check_header.cpp.in diff --git a/.github/actions/check-coverage/action.yml b/.github/actions/check-coverage/action.yml new file mode 100644 index 000000000..59d8307c8 --- /dev/null +++ b/.github/actions/check-coverage/action.yml @@ -0,0 +1,34 @@ +name: Check Coverage Results +description: 'Extracts a summary of the code coverage results' + +inputs: + binary-dir: + description: 'Directory containing binary files' + required: true + source-dir: + description: 'Directory containing source code files' + default: ./source + +runs: + using: 'composite' + steps: + - name: Capture Results + shell: bash + run: lcov + --capture --rc lcov_branch_coverage=1 + --include '${{ github.workspace }}/*' + --directory '${{ inputs.binary-dir }}' + --output-file '${{ inputs.binary-dir }}/coverage.info' + + - name: Generate HTML + shell: bash + run: genhtml + '${{ inputs.binary-dir }}/coverage.info' + --branch-coverage + --output-directory '${{ inputs.binary-dir }}/lcov-html' + + - name: Extract Summary + shell: bash + run: xsltproc --html + '${{ inputs.source-dir }}/.github/scripts/lcov-output.xslt' + '${{ inputs.binary-dir }}/lcov-html/index.html' > '${{ inputs.binary-dir }}/lcov-summary.xml' diff --git a/.github/scripts/lcov-output.xslt b/.github/scripts/lcov-output.xslt new file mode 100644 index 000000000..77e2ea4dd --- /dev/null +++ b/.github/scripts/lcov-output.xslt @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + X + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + +
+ + +
+ + + +

LCOV Report

+ + + + + + + + + + + + +
+ + +
diff --git a/.github/workflows/build-osal-documentation.yml b/.github/workflows/build-osal-documentation.yml deleted file mode 100644 index 1f0417e59..000000000 --- a/.github/workflows/build-osal-documentation.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: "Build OSAL Documentation (API Guide)" - -on: - push: - pull_request: - -jobs: - - #Check for duplicate actions. Skips push actions if there is a matching or duplicate pull-request action. - check-for-duplicates: - runs-on: ubuntu-latest - # Map a step output to a job output - outputs: - should_skip: ${{ steps.skip_check.outputs.should_skip }} - steps: - - id: skip_check - uses: fkirc/skip-duplicate-actions@master - with: - concurrent_skipping: 'same_content' - skip_after_successful_duplicate: 'true' - do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]' - - build-osal-apiguide: - #Continue if check-for-duplicates found no duplicates. Always runs for pull-requests. - needs: check-for-duplicates - if: ${{ needs.check-for-duplicates.outputs.should_skip != 'true' }} - runs-on: ubuntu-20.04 - timeout-minutes: 15 - - steps: - - name: Install Dependencies - run: sudo apt-get install doxygen graphviz -y - - - name: Checkout submodule - uses: actions/checkout@v3 - - - name: Set up for build - run: | - cp Makefile.sample Makefile - make prep - - - name: Build OSAL API Guide - run: | - make osal-apiguide 2>&1 > make_osal-apiguide_stdout.txt | tee make_osal-apiguide_stderr.txt - mv build/docs/osal-apiguide-warnings.log osal-apiguide-warnings.log - - - name: Archive Osal Guide Build Logs - uses: actions/upload-artifact@v3 - with: - name: OSAL Guide Artifacts - path: | - make_osal-apiguide_stdout.txt - make_osal-apiguide_stderr.txt - osal-apiguide-warnings.log - - - name: Error Check - run: | - if [[ -s make_osal-apiguide_stderr.txt ]]; then - cat make_osal-apiguide_stderr.txt - exit -1 - fi - - - name: Warning Check - run: | - if [[ -s osal-apiguide-warnings.log ]]; then - cat osal-apiguide-warnings.log - exit -1 - fi diff --git a/.github/workflows/codeql-cfe-build.yml b/.github/workflows/codeql-cfe-build.yml deleted file mode 100644 index 9a4a5099e..000000000 --- a/.github/workflows/codeql-cfe-build.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: "CodeQL cFE Build Analysis" - -on: - push: - pull_request: - -jobs: - codeql: - name: CodeQl Analysis - uses: nasa/cFS/.github/workflows/codeql-reusable.yml@main - with: - component-path: osal - make: 'make -C build/native/default_cpu1/osal' diff --git a/.github/workflows/codeql-osal-default.yml b/.github/workflows/codeql-osal-default.yml deleted file mode 100644 index 748faa93c..000000000 --- a/.github/workflows/codeql-osal-default.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: "CodeQL Analysis OSAL Default Build" - -on: - push: - pull_request: - -jobs: - codeql: - name: CodeQl Analysis - uses: nasa/cFS/.github/workflows/codeql-reusable.yml@main - with: - component-path: cFS # Causes reusable workflow to not checkout bundle - setup: 'cp Makefile.sample Makefile' - prep: 'make prep' - make: 'make' diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml deleted file mode 100644 index bf12f09e2..000000000 --- a/.github/workflows/format-check.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: Format Check - -# Run on all push and pull requests -on: - push: - pull_request: - -jobs: - format-check: - name: Run format check - uses: nasa/cFS/.github/workflows/format-check.yml@main \ No newline at end of file diff --git a/.github/workflows/local_unit_test.yml b/.github/workflows/local_unit_test.yml deleted file mode 100644 index a14a4f1c2..000000000 --- a/.github/workflows/local_unit_test.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: "Local Unit Test" - -on: - push: - pull_request: - -jobs: - #Checks for duplicate actions. Skips push actions if there is a matching or duplicate pull-request action. - check-for-duplicates: - runs-on: ubuntu-latest - # Map a step output to a job output - outputs: - should_skip: ${{ steps.skip_check.outputs.should_skip }} - steps: - - id: skip_check - uses: fkirc/skip-duplicate-actions@master - with: - concurrent_skipping: 'same_content' - skip_after_successful_duplicate: 'true' - do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]' - - Local-Unit-Test: - needs: check-for-duplicates - if: ${{ needs.check-for-duplicates.outputs.should_skip != 'true' }} - runs-on: ubuntu-20.04 - timeout-minutes: 15 - - steps: - - name: Install coverage tools - run: sudo apt-get install lcov -y - - - name: Checkout submodule - uses: actions/checkout@v3 - - - name: Set up for build - run: | - cp Makefile.sample Makefile - make ENABLE_UNIT_TESTS=true PERMISSIVE_MODE=true prep - - - name: Build the code - run: make -j - - # Baseline lcov and run all tests - - name: Test - run: make test - - - name: Calculate coverage - run: make lcov | tee lcov_out.txt - - - name: Confirm 100% line coverage - run: | - if [[ `grep -A 3 "Overall coverage rate" lcov_out.txt | grep lines` != *"100.0%"* ]]; then - grep -A 3 "Overall coverage rate" lcov_out.txt - echo "Lacks 100.0% line unit test coverage" - exit -1 - fi - - - name: Confirm absolute line coverage - run: | - # Current best possible branch coverage is all but 4, with associated issues for each missing case - missed_branches=4 - coverage_nums=$(grep -A 3 "Overall coverage rate" lcov_out.txt | grep branches | grep -oP "[0-9]+[0-9]*") - - diff=$(echo $coverage_nums | awk '{ print $4 - $3 }') - if [ $diff -gt $missed_branches ] - then - grep -A 3 "Overall coverage rate" lcov_out.txt - echo "More than $missed_branches branches missed" - exit -1 - fi diff --git a/.github/workflows/standalone-build.yml b/.github/workflows/standalone-build.yml new file mode 100644 index 000000000..1f836bb7f --- /dev/null +++ b/.github/workflows/standalone-build.yml @@ -0,0 +1,92 @@ +name: Build and Test Standalone OSAL package + +on: + workflow_dispatch: + pull_request: + push: # for now + +# Force bash to apply pipefail option so pipeline failures aren't masked +defaults: + run: + shell: bash + +jobs: + + build-and-test: + name: Build and Execute Tests + + strategy: + fail-fast: false + matrix: + build-type: [Debug, Release] + base-os: [ubuntu-22.04, ubuntu-20.04] + + runs-on: ${{ matrix.base-os }} + + steps: + + - name: Checkout OSAL + uses: actions/checkout@v3 + with: + path: source + + - name: Install Coverage Analysis Tools + if: ${{ matrix.build-type == 'Debug' && matrix.base-os == 'ubuntu-20.04' }} + run: sudo apt-get install -y lcov xsltproc && echo "run_lcov=TRUE" >> $GITHUB_ENV + + - name: Set up debug environment + if: ${{ matrix.build-type == 'Debug' }} + run: | + echo "is_debug=TRUE" >> $GITHUB_ENV + echo "is_release=FALSE" >> $GITHUB_ENV + echo "build_tgt=all" >> $GITHUB_ENV + echo "DESTDIR=${{ github.workspace }}/staging-debug" >> $GITHUB_ENV + + - name: Set up release environment + if: ${{ matrix.build-type == 'Release' }} + run: | + echo "is_debug=FALSE" >> $GITHUB_ENV + echo "is_release=TRUE" >> $GITHUB_ENV + echo "build_tgt=install osal_apicheck" >> $GITHUB_ENV + echo "DESTDIR=${{ github.workspace }}/staging-release" >> $GITHUB_ENV + + - name: Set up build + run: cmake + -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} + -DENABLE_UNIT_TESTS=${{ env.is_debug }} + -DOSAL_OMIT_DEPRECATED=${{ env.is_debug }} + -DOSAL_VALIDATE_API=${{ env.is_release }} + -DOSAL_INSTALL_LIBRARIES=${{ env.is_release }} + -DOSAL_CONFIG_DEBUG_PERMISSIVE_MODE=${{ env.is_debug }} + -DOSAL_SYSTEM_BSPTYPE=generic-linux + -DCMAKE_PREFIX_PATH=/usr/lib/cmake + -DCMAKE_INSTALL_PREFIX=/usr + -S source + -B build + + - name: Build OSAL + working-directory: build + run: make ${{ env.build_tgt }} -j2 VERBOSE=1 + + - name: Run Tests + if: ${{ matrix.build-type == 'Debug' }} + working-directory: build + run: ctest --output-on-failure -j4 2>&1 | tee ../ctest.log + + - name: Check Coverage + if: ${{ env.run_lcov == 'TRUE' }} + uses: ./source/.github/actions/check-coverage + with: + binary-dir: build + + - name: Assemble Results + run: | + if [ -s ctest.log ]; then + echo '

CTest Execution

' >> $GITHUB_STEP_SUMMARY + echo '
' >> $GITHUB_STEP_SUMMARY
+            cat ctest.log >> $GITHUB_STEP_SUMMARY
+            echo '
' >> $GITHUB_STEP_SUMMARY + fi + if [ -s 'build/lcov-summary.xml' ]; then + cat 'build/lcov-summary.xml' >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml deleted file mode 100644 index ea2250b04..000000000 --- a/.github/workflows/static-analysis.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Static Analysis - -# Run on all push and pull requests -on: - push: - pull_request: - workflow_dispatch: - -jobs: - static-analysis: - name: Run Static Analysis - uses: nasa/cFS/.github/workflows/static-analysis.yml@main - with: - strict-dir-list: './src/bsp ./src/os' - cmake-project-options: -DENABLE_UNIT_TESTS=TRUE -DOSAL_OMIT_DEPRECATED=TRUE -DOSAL_SYSTEM_BSPTYPE=generic-linux diff --git a/CMakeLists.txt b/CMakeLists.txt index 38a39952f..a7d44eba2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,7 +97,14 @@ if (CMAKE_VERSION VERSION_GREATER 3.13) cmake_policy(SET CMP0079 NEW) endif() -project(OSAL C) +option(OSAL_VALIDATE_API "Validate the OSAL API headers as part of the build" OFF) +if (OSAL_VALIDATE_API) + set(OSAL_LANG C CXX) +else() + set(OSAL_LANG C CXX) +endif() + +project(OSAL ${OSAL_LANG}) # define a custom property to track relationship between BSP and OS # this should be set on BSP "impl" targets to indicate the correct OS impl to go with it @@ -178,7 +185,6 @@ if (OSAL_OMIT_DEPRECATED) target_compile_definitions(osal_public_api INTERFACE OSAL_OMIT_DEPRECATED) endif (OSAL_OMIT_DEPRECATED) - # OSAL_SYSTEM_BSPTYPE indicate which of the BSP packages # to build. If not defined then no OSAL implementation will be built, however # the api interface target (osal_public_api) is still usable. Therefore @@ -478,3 +484,41 @@ if (OSAL_INSTALL_LIBRARIES) ) endif() + +if (OSAL_VALIDATE_API) + + # Validate the API header files individually + file(GLOB OSAL_API_HEADERS ${OSAL_SOURCE_DIR}/src/os/inc/*.h) + set(OSAL_APICHECK_SOURCES) + set(OSAL_APICHECK_DIR "${CMAKE_CURRENT_BINARY_DIR}/apicheck") + + foreach(HDR_FILE ${OSAL_API_HEADERS}) + get_filename_component(HDR "${HDR_FILE}" NAME) + string(MAKE_C_IDENTIFIER "${HDR}" HDR_ID) + configure_file(${OSAL_SOURCE_DIR}/check_header.c.in ${OSAL_APICHECK_DIR}/check_${HDR_ID}.c) + list(APPEND OSAL_APICHECK_SOURCES_C ${OSAL_APICHECK_DIR}/check_${HDR_ID}.c) + configure_file(${OSAL_SOURCE_DIR}/check_header.cpp.in ${OSAL_APICHECK_DIR}/check_${HDR_ID}.cpp) + list(APPEND OSAL_APICHECK_SOURCES_CXX ${OSAL_APICHECK_DIR}/check_${HDR_ID}.cpp) + endforeach(HDR_FILE ${OSAL_API_HEADERS}) + add_library(osal_apicheck_C OBJECT ${OSAL_APICHECK_SOURCES_C}) + add_library(osal_apicheck_CXX OBJECT ${OSAL_APICHECK_SOURCES_CXX}) + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(osal_apicheck_C PUBLIC -std=c99 -pedantic -Wall -Werror) + endif() + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(osal_apicheck_CXX PUBLIC -std=c++03 -pedantic -Wall -Werror) + endif() + + # This causes the check to compile with the same set of defines and include dirs as specified + # in the "INTERFACE" properties of the actual module + target_link_libraries(osal_apicheck_C PUBLIC + osal_public_api + ) + target_link_libraries(osal_apicheck_CXX PUBLIC + osal_public_api + ) + + add_custom_target(osal_apicheck ALL) + add_dependencies(osal_apicheck osal_apicheck_C) + add_dependencies(osal_apicheck osal_apicheck_CXX) +endif() diff --git a/check_header.c.in b/check_header.c.in new file mode 100644 index 000000000..e452d823f --- /dev/null +++ b/check_header.c.in @@ -0,0 +1,4 @@ +#include "@HDR@" + +/* A no-op function so this compilation unit is not empty */ +void CheckHeader_@HDR_ID@(void) {} diff --git a/check_header.cpp.in b/check_header.cpp.in new file mode 100644 index 000000000..4be090c57 --- /dev/null +++ b/check_header.cpp.in @@ -0,0 +1,7 @@ +extern "C" +{ +#include "@HDR@" +} + +/* An empty class */ +class CheckHeader_@HDR_ID@ {};