diff --git a/.github/workflows/build_test_cmake.yml b/.github/workflows/build_test_cmake.yml index 22610fb454..a9ec3c8a6d 100644 --- a/.github/workflows/build_test_cmake.yml +++ b/.github/workflows/build_test_cmake.yml @@ -1,7 +1,7 @@ name: Build Test on: push: - pull_request: + # pull_request: jobs: test: diff --git a/.github/workflows/build_test_makefile.yml b/.github/workflows/build_test_makefile.yml index 237d25ebf8..45ea727557 100644 --- a/.github/workflows/build_test_makefile.yml +++ b/.github/workflows/build_test_makefile.yml @@ -1,7 +1,7 @@ name: Build Test on: push: - pull_request: + # pull_request: jobs: test: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index c664bf796c..f57bb892ae 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,6 +1,9 @@ name: Coverage Analysis on: + pull_request: + branches: + - develop workflow_dispatch: push: tags: @@ -13,24 +16,97 @@ jobs: steps: - name: Checkout uses: actions/checkout@v5 - - name: Install Requirements for Coverage Testing + with: + submodules: recursive + + - name: Install Perl Dependencies and Coverage Tools run: | - apt update && apt install -y lcov gpg curl jq ca-certificates - - name: Building + apt update && apt install -y curl jq ca-certificates python3-pip + # 首先安装系统版本的 lcov 和 Perl 依赖 + apt install -y lcov perl-modules + # 安装必要的 Perl 模块 + apt install -y libcapture-tiny-perl libdatetime-perl libjson-perl libperlio-gzip-perl + # 验证系统 lcov 是否工作 + lcov --version + # 同时安装 gcovr 作为备用方案 + pip3 install gcovr + + - name: Building with Coverage run: | - cmake -B build -DENABLE_COVERAGE=ON -DBUILD_TESTING=ON -DENABLE_MLALGO=ON -DENABLE_LIBXC=ON -DENABLE_LIBRI=ON -DENABLE_GOOGLEBENCH=ON -DENABLE_RAPIDJSON=ON - cmake --build build -j`nproc` - cmake --install build + rm -rf build/ + rm -f CMakeCache.txt + # 确保构建目录权限 + mkdir -p build + chmod -R 755 build/ + # 使用明确的覆盖率标志 + cmake -B build \ + -DENABLE_COVERAGE=ON \ + -DBUILD_TESTING=ON \ + -DENABLE_MLALGO=ON \ + -DENABLE_LIBXC=ON \ + -DENABLE_LIBRI=ON \ + -DENABLE_GOOGLEBENCH=ON \ + -DENABLE_RAPIDJSON=ON \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_CXX_FLAGS="--coverage -fprofile-arcs -ftest-coverage" \ + -DCMAKE_EXE_LINKER_FLAGS="--coverage" + cmake --build build -j`nproc` || echo "Some tests failed but continuing for coverage" + cmake --install build || echo "Some tests failed but continuing for coverage" + - name: Testing env: OMP_NUM_THREADS: 1 run: | - cmake --build build --target test ARGS="-V --timeout 21600" || exit 0 + # 运行测试前确保权限 + chmod -R 755 build/ + cmake --build build --target test ARGS="-V --timeout 21600" || echo "Some tests failed but continuing for coverage" + + - name: Generate Coverage Data + run: | + cd build + # 检查 .gcno 和 .gcda 文件是否存在 + echo "Checking for coverage files:" + find . -name "*.gcno" | head -10 + find . -name "*.gcda" | head -10 + + # 方法1: 使用更新后的 lcov + echo "Generating coverage with lcov..." + lcov --directory . --capture --output-file coverage.info || echo "lcov failed, will try gcovr" + + # 过滤覆盖率数据 + lcov --remove coverage.info '/usr/*' '*/test/*' '*/external/*' '*/build/*' --output-file coverage.filtered.info || echo "lcov filtering failed" + + # 生成HTML报告用于检查 + genhtml coverage.filtered.info --output-directory coverage-report || echo "genhtml failed" + + # # 方法2: 使用 gcovr 作为备用 + # echo "Generating coverage with gcovr..." + # gcovr -r .. --html --html-details -o coverage-gcovr.html --print-summary || echo "gcovr failed" + + # # 同时生成 gcovr 的 XML 格式,Codecov 更喜欢这种格式 + # gcovr -r .. --xml -o coverage.xml || echo "gcovr xml failed" + + cd .. + - name: Upload Coverage to Codecov uses: codecov/codecov-action@v5 if: ${{ ! cancelled() }} with: - fail_ci_if_error: true + fail_ci_if_error: false # 设置为 false 避免因覆盖率收集失败而整个 CI 失败 token: ${{ secrets.CODECOV_TOKEN }} - skip_validation: true + files: ./build/coverage.xml,./build/coverage.info # 提供多个文件选项 + directory: ./build/ + flags: unittests + name: codecov-umbrella verbose: true + + - name: Upload Coverage Report Artifact + uses: actions/upload-artifact@v4 + if: always() # 即使前面的步骤失败也上传,便于调试 + with: + name: coverage-report + path: | + build/coverage-report/ + build/coverage.info + build/coverage.xml + retention-days: 30 \ No newline at end of file diff --git a/.github/workflows/cuda.yml b/.github/workflows/cuda.yml index 14e829e546..2400fa4775 100644 --- a/.github/workflows/cuda.yml +++ b/.github/workflows/cuda.yml @@ -2,7 +2,7 @@ name: CUDA Test on: workflow_dispatch: - pull_request: + # pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index ef9bbbfc84..762b34e16e 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -1,6 +1,9 @@ name: Container on: + #pull_request: # 当有 PR 指向 develop 分支时触发 + #branches: + # - develop push: branches: - develop @@ -14,7 +17,8 @@ jobs: if: github.repository_owner == 'deepmodeling' strategy: matrix: - dockerfile: ["gnu","intel","cuda"] + dockerfile: ["intel"] + # dockerfile: ["gnu","intel","cuda"] steps: - name: Checkout uses: actions/checkout@v5 @@ -41,10 +45,11 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Login to Aliyun Registry + # 只有在不是由 fork 的 PR 触发时才执行 + if: github.event.pull_request.head.repo.full_name == github.repository uses: docker/login-action@v3 with: registry: dp-harbor-registry.us-east-1.cr.aliyuncs.com - # AliCloud automatically mirrors to registry.dp.tech username: ${{ secrets.DP_HARBOR_USERNAME }} password: ${{ secrets.DP_HARBOR_PASSWORD }} @@ -54,6 +59,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} file: Dockerfile.${{ matrix.dockerfile }} - push: true + # 只有在 develop 分支或打标签时才推送,PR时不推送 + push: ${{ github.event_name != 'pull_request' }} cache-from: type=registry,ref=ghcr.io/deepmodeling/abacus-${{ matrix.dockerfile }}:latest cache-to: type=inline diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 1e78dfb0a1..b0790c92a1 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,7 +1,8 @@ name: Pyabacus Build and Test on: - pull_request: + push: + # pull_request: jobs: test: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index df1dfb29dc..b87792a8db 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,8 @@ name: Integration Test and Unit Test on: - pull_request: + push: + # pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ac95e45fd..235d44f7ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,16 +151,24 @@ endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) if(ENABLE_COVERAGE) - find_package(codecov) + find_package(codecov QUIET) if(NOT codecov_FOUND) include(FetchContent) FetchContent_Declare( cmakecodecov - URL https://github.com/baixiaokuang/CMake-codecov/archive/refs/heads/master.zip + GIT_REPOSITORY https://github.com/RWTH-HPC/CMake-codecov.git + GIT_TAG master + # URL https://github.com/baixiaokuang/CMake-codecov/archive/refs/heads/master.zip + # URL https://github.com/RWTH-HPC/CMake-codecov/archive/refs/heads/master.zip + # URL https://gitee.com/mirrors_rwth-elp/cmake-codecov/archive/refs/heads/master.zip + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE ) FetchContent_Populate(cmakecodecov) list(APPEND CMAKE_MODULE_PATH ${cmakecodecov_SOURCE_DIR}/cmake) + set(CMAKE_MODULE_PATH "${cmakecodecov_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) find_package(codecov REQUIRED) + ##set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/CMake-codecov/cmake" ${CMAKE_MODULE_PATH}) endif() endif() @@ -806,5 +814,18 @@ install(PROGRAMS ${ABACUS_BIN_PATH} ) if(ENABLE_COVERAGE) - coverage_evaluate() + ## coverage_evaluate() + add_custom_target(coverage + COMMAND ${CMAKE_COMMAND} -E echo "Capturing coverage data..." + COMMAND lcov --capture --directory ${CMAKE_BINARY_DIR} --output-file ${CMAKE_BINARY_DIR}/coverage.info + COMMAND ${CMAKE_COMMAND} -E echo "Filtering coverage data..." + + COMMAND lcov --remove ${CMAKE_BINARY_DIR}/coverage.info '/usr/*' '*/test/*' '*/gtest/*' '*/gmock/*' --output-file ${CMAKE_BINARY_DIR}/coverage.info + COMMAND ${CMAKE_COMMAND} -E echo "Generating HTML report..." + + COMMAND genhtml -o ${CMAKE_BINARY_DIR}/coverage_html ${CMAKE_BINARY_DIR}/coverage.info + COMMAND ${CMAKE_COMMAND} -E echo "Coverage report generated at: ${CMAKE_BINARY_DIR}/coverage_html/index.html" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Generating code coverage report with lcov" + ) endif() diff --git a/Dockerfile.intel b/Dockerfile.intel index 119cf4eb4b..a85a50f2a3 100644 --- a/Dockerfile.intel +++ b/Dockerfile.intel @@ -1,3 +1,4 @@ +# Base image FROM ubuntu:22.04 RUN apt-get update && apt-get install -y \ @@ -5,13 +6,13 @@ RUN apt-get update && apt-get install -y \ libcereal-dev libxc-dev libgtest-dev libgmock-dev libbenchmark-dev \ pkg-config build-essential autoconf automake libtool -# Following steps by https://software.intel.com/content/www/us/en/develop/documentation/installation-guide-for-intel-oneapi-toolkits-linux/top/installation/install-using-package-managers/apt.html . +# Add Intel apt repo RUN wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \ | gpg --dearmor | sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null && \ echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" \ | sudo tee /etc/apt/sources.list.d/oneAPI.list -# To save disk space, only install the essential components, but not the whole toolkit. +# 安装oneAPI组件(使用更灵活的版本处理) RUN apt-get update && \ apt-get install -y \ intel-oneapi-compiler-dpcpp-cpp \ @@ -44,18 +45,121 @@ ENV PKG_CONFIG_PATH=${MKLROOT}/lib/pkgconfig:${ONEAPI_ROOT}/mpi/latest/lib/pkgco ENV CMAKE_PREFIX_PATH=${ONEAPI_ROOT}/tbb/latest:${MKLROOT}/lib/cmake:${ONEAPI_ROOT}/dpl/latest/lib/cmake:${ONEAPI_ROOT}/dnnl/latest/lib/cmake:${ONEAPI_ROOT}/dal/latest:${ONEAPI_ROOT}/compiler/latest:${CMAKE_PREFIX_PATH} SHELL ["/bin/bash", "-c"] + +# 设置编译器 - 使用更兼容的配置 +# ENV CC=mpiicc CXX=mpiicpc FC=mpiifort +# 备用设置(如果上面的不行) ENV CC=mpiicx CXX=mpiicpx FC=mpiifx -# Verify oneAPI installation +# 验证oneAPI安装 RUN source ${ONEAPI_ROOT}/setvars.sh && \ - echo "=== Verify compiler ===" && \ + echo "=== 验证编译器 ===" && \ which mpiicx && mpiicx --version && \ - echo "=== Verify MKL ===" && \ + echo "=== 验证MKL ===" && \ ls ${MKLROOT}/lib/intel64/ && \ - echo "=== Verify MPI ===" && \ + echo "=== 验证MPI ===" && \ which mpirun && mpirun --version -# https://elpa.mpcdf.mpg.de/software/tarball-archive/ELPA_TARBALL_ARCHIVE.html +###### PEXSI PART: pexsi and gklib and metis and parmetis and superlu_dist +ENV GKLIB_VERSION="master" +ENV METIS_VERSION="master" +ENV PARMETIS_VERSION="main" +ENV SUPERLU_DIST_VERSION=7.2.0 +ENV PEXSI_VERSION=2.0.0 +ENV GKLIB_ROOT=/usr/local/gklib-${GKLIB_VERSION} +ENV METIS32_ROOT=/usr/local/metis32-${METIS_VERSION} +ENV PARMETIS32_ROOT=/usr/local/parmetis32-${PARMETIS_VERSION} +ENV PEXSI32_ROOT=/usr/local/pexsi32-${PEXSI_VERSION} +ENV SUPERLU_DIST32_ROOT=/usr/local/superlu_dist32-${SUPERLU_DIST_VERSION} + +# 2. install GKlib +RUN wget https://codeload.github.com/KarypisLab/GKlib/zip/refs/heads/${GKLIB_VERSION} -O GKlib-${GKLIB_VERSION}.zip && \ + unzip GKlib-${GKLIB_VERSION}.zip && \ + cd GKlib-${GKLIB_VERSION} && \ + make config shared=1 prefix=${GKLIB_ROOT} openmp=set && \ + make -j$(nproc) && \ + make install && \ + ls ${GKLIB_ROOT}/lib && \ + cp ${GKLIB_ROOT}/lib/libGKlib.so.0 ${GKLIB_ROOT}/lib/libGKlib.so && \ + cd / && rm -rf GKlib-${GKLIB_VERSION} GKlib-${GKLIB_VERSION}.zip +#.so file CANNOT be found otherwise. +# 2. install METIS +RUN export LD_LIBRARY_PATH=${GKLIB_ROOT}/lib:${LD_LIBRARY_PATH} && \ + wget https://codeload.github.com/KarypisLab/METIS/zip/refs/heads/${METIS_VERSION} -O METIS-${METIS_VERSION}.zip && \ + unzip METIS-${METIS_VERSION}.zip && \ + cd METIS-${METIS_VERSION} && \ + make config shared=1 prefix=${METIS32_ROOT} gklib_path=${GKLIB_ROOT} && \ + make -j$(nproc) && \ + make install && \ + cd / && rm -rf METIS-${METIS_VERSION} METIS-${METIS_VERSION}.zip + +# 3. install ParMETIS +RUN export LD_LIBRARY_PATH=${METIS32_ROOT}/lib:${GKLIB_ROOT}/lib:${LD_LIBRARY_PATH} && \ + wget https://codeload.github.com/KarypisLab/ParMETIS/zip/refs/heads/${PARMETIS_VERSION} -O ParMETIS-${PARMETIS_VERSION}.zip && \ + unzip ParMETIS-${PARMETIS_VERSION}.zip && \ + cd ParMETIS-${PARMETIS_VERSION} && \ + make config shared=1 prefix=${PARMETIS32_ROOT} gklib_path=${GKLIB_ROOT} metis_path=${METIS32_ROOT} && \ + make -j$(nproc) && \ + make install && \ + cd / && rm -rf ParMETIS-${PARMETIS_VERSION} ParMETIS-${PARMETIS_VERSION}.zip + +# 4. install SuperLU_DIST +RUN wget https://codeload.github.com/xiaoyeli/superlu_dist/tar.gz/refs/tags/v${SUPERLU_DIST_VERSION} -O v${SUPERLU_DIST_VERSION}.tar.gz && \ + tar -xzf v${SUPERLU_DIST_VERSION}.tar.gz && \ + cd superlu_dist-${SUPERLU_DIST_VERSION} && \ + mkdir build && cd build && \ + cmake .. \ + -DTPL_ENABLE_PARMETISLIB=ON \ + -DTPL_PARMETIS_LIBRARIES="${PARMETIS32_ROOT}/lib/libparmetis.so;${METIS32_ROOT}/lib/libmetis.so;${GKLIB_ROOT}/lib/libGKlib.so" \ + -DTPL_PARMETIS_INCLUDE_DIRS="${PARMETIS32_ROOT}/include;${METIS32_ROOT}/include;${GKLIB_ROOT}/include" \ + -DTPL_ENABLE_INTERNAL_BLASLIB=OFF \ + -DTPL_ENABLE_LAPACKLIB=ON \ + -DTPL_ENABLE_COMBBLASLIB=OFF \ + -DTPL_ENABLE_CUDALIB=OFF \ + -Denable_complex16=ON \ + -DXSDK_INDEX_SIZE=32 \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_INSTALL_PREFIX=${SUPERLU_DIST32_ROOT} \ + -DCMAKE_C_FLAGS="-O3 -fopenmp" \ + -DCMAKE_CXX_FLAGS="-O3 -fopenmp" \ + -DXSDK_ENABLE_Fortran=ON \ + -DCMAKE_Fortran_COMPILER=mpiifx && \ + make -j$(nproc) && \ + make install && \ + cd / && rm -rf superlu_dist-${SUPERLU_DIST_VERSION} v${SUPERLU_DIST_VERSION}.tar.gz + +### -DCMAKE_C_COMPILER=mpiicc \ +### -DCMAKE_CXX_COMPILER=mpiicpc \ +### -DCMAKE_CUDA_COMPILER=nvcc \ + +# 5. install PEXSI +RUN export LD_LIBRARY_PATH=${SUPERLU_DIST32_ROOT}/lib:${METIS32_ROOT}/lib:${PARMETIS32_ROOT}/lib:${GKLIB_ROOT}/lib:${LD_LIBRARY_PATH} && \ + export LIBRARY_PATH=${SUPERLU_DIST32_ROOT}/lib:${METIS32_ROOT}/lib:${PARMETIS32_ROOT}/lib:${GKLIB_ROOT}/lib:${LIBRARY_PATH} && \ + export PKG_CONFIG_PATH=${SUPERLU_DIST32_ROOT}/lib/pkgconfig:${METIS32_ROOT}/lib/pkgconfig:${PARMETIS32_ROOT}/lib/pkgconfig:${GKLIB_ROOT}/lib/pkgconfig:${PKG_CONFIG_PATH} && \ + export CPATH=${SUPERLU_DIST32_ROOT}/include:${PARMETIS32_ROOT}/include:${METIS32_ROOT}/include:${GKLIB_ROOT}/include:${CMAKE_PREFIX_PATH} && \ + export CMAKE_PREFIX_PATH=${SUPERLU_DIST32_ROOT}:${PARMETIS32_ROOT}:${METIS32_ROOT}:${GKLIB_ROOT}:${CMAKE_PREFIX_PATH} && \ + wget https://bitbucket.org/berkeleylab/pexsi/downloads/pexsi_v${PEXSI_VERSION}.tar.gz && \ + tar -xzf pexsi_v${PEXSI_VERSION}.tar.gz && \ + cd pexsi_v${PEXSI_VERSION} && \ + sed -i 's/^add_pexsi_f_example_exe/# add_pexsi_f_example_exe/g' fortran/CMakeLists.txt && \ + sed -i 's/^add_pexsi_example_exe/# add_pexsi_example_exe/g' examples/CMakeLists.txt && \ + sed -i 's/add_executable/# add_executable/g' fortran/CMakeLists.txt && \ + sed -i 's/add_executable/# add_executable/g' examples/CMakeLists.txt && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=${PEXSI32_ROOT} \ + -DPEXSI_ENABLE_OPENMP=ON \ + -DPEXSI_ENABLE_FORTRAN=OFF && \ + make pexsi -j$(nproc) && \ + make install && \ + cd / && rm -rf pexsi_v${PEXSI_VERSION} pexsi_v${PEXSI_VERSION}.tar.gz + +### -DCMAKE_C_COMPILER=mpiicc \ +### -DCMAKE_CXX_COMPILER=mpiicpc \ +###### END of PEXSI PART + + +# Build ELPA (example dependency) RUN source /opt/intel/oneapi/setvars.sh && \ cd /tmp && \ ELPA_VER=2022.11.001 && \ @@ -77,13 +181,16 @@ ENV CMAKE_PREFIX_PATH=/opt/libtorch/share/cmake:${CMAKE_PREFIX_PATH} # Clone and build abacus (optional during image build; keep for CI image) ADD https://api.github.com/repos/deepmodeling/abacus-develop/git/refs/heads/develop /dev/null RUN export LD_LIBRARY_PATH=${GKLIB_ROOT}/lib:${METIS32_ROOT}/lib:${PARMETIS32_ROOT}/lib:${SUPERLU32_DIST_ROOT}/lib:${PEXSI32_ROOT}/lib:${LD_LIBRARY_PATH} && \ + export PKG_CONFIG_PATH=${GKLIB_ROOT}/lib/pkgconfig:${METIS32_ROOT}/lib/pkgconfig:${PARMETIS32_ROOT}/lib/pkgconfig:${SUPERLU32_DIST_ROOT}/lib/pkgconfig:${PEXSI32_ROOT}/lib/pkgconfig:${PKG_CONFIG_PATH} && \ export CPATH=${GKLIB_ROOT}/include:${METIS32_ROOT}/include:${PARMETIS32_ROOT}/include:${SUPERLU32_DIST_ROOT}/include:${PEXSI32_ROOT}/include:${CPATH} && \ + export CMAKE_PREFIX_PATH=${PEXSI32_ROOT}:${SUPERLU_DIST32_ROOT}:${PARMETIS32_ROOT}:${METIS32_ROOT}:${GKLIB_ROOT}:${CMAKE_PREFIX_PATH} && \ cd /tmp && git clone https://github.com/deepmodeling/abacus-develop.git --depth 1 && \ cd abacus-develop && \ cmake -B build \ -DENABLE_MLALGO=ON \ -DENABLE_LIBXC=ON \ -DENABLE_LIBRI=ON \ + -DENABLE_PEXSI=ON \ -DENABLE_RAPIDJSON=ON \ -DCMAKE_BUILD_TYPE=Release && \ cmake --build build -j"$(nproc)" && \ diff --git a/cmake/FindGcov.cmake b/cmake/FindGcov.cmake new file mode 100644 index 0000000000..a6ef6fd5c8 --- /dev/null +++ b/cmake/FindGcov.cmake @@ -0,0 +1,160 @@ +# This file is part of CMake-codecov. +# +# SPDX-FileCopyrightText: RWTH Aachen University, Federal Republic of Germany +# SPDX-FileContributor: Alexander Haase, alexander.haase@rwth-aachen.de +# +# SPDX-License-Identifier: BSD-3-Clause + +# include required Modules +include(FindPackageHandleStandardArgs) + + +# Search for gcov binary. +set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET}) +set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY}) + +get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach (LANG ${ENABLED_LANGUAGES}) + # Gcov evaluation is dependent on the used compiler. Check gcov support for + # each compiler that is used. If gcov binary was already found for this + # compiler, do not try to find it again. + if (NOT GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN) + get_filename_component(COMPILER_PATH "${CMAKE_${LANG}_COMPILER}" PATH) + + if ("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "GNU") + # Some distributions like OSX (homebrew) ship gcov with the compiler + # version appended as gcov-x. To find this binary we'll build the + # suggested binary name with the compiler version. + string(REGEX MATCH "^[0-9]+" GCC_VERSION + "${CMAKE_${LANG}_COMPILER_VERSION}") + + find_program(GCOV_BIN NAMES gcov-${GCC_VERSION} gcov + HINTS ${COMPILER_PATH}) + + elseif ("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "^(Apple)?Clang$") + # Some distributions like Debian ship llvm-cov with the compiler + # version appended as llvm-cov-x.y or just llvm-cov-x. To find this binary we'll build + # the suggested binary name with the compiler version. + string(REGEX MATCH "^[0-9]+\.[0-9]+" LLVM_FULL_VERSION + "${CMAKE_${LANG}_COMPILER_VERSION}") + string(REGEX MATCH "^[0-9]+" LLVM_MAJOR_VERSION + "${CMAKE_${LANG}_COMPILER_VERSION}") + + # llvm-cov prior version 3.5 seems to be not working with coverage + # evaluation tools, but these versions are compatible with the gcc + # gcov tool. + if(LLVM_FULL_VERSION VERSION_GREATER 3.4) + find_program(LLVM_COV_BIN NAMES "llvm-cov-${LLVM_FULL_VERSION}" "llvm-cov-${LLVM_MAJOR_VERSION}" + "llvm-cov" HINTS ${COMPILER_PATH}) + mark_as_advanced(LLVM_COV_BIN) + + if (LLVM_COV_BIN) + find_program(LLVM_COV_WRAPPER "llvm-cov-wrapper" PATHS + ${CMAKE_MODULE_PATH}) + if (LLVM_COV_WRAPPER) + set(GCOV_BIN "${LLVM_COV_WRAPPER}" CACHE FILEPATH "") + + # set additional parameters + set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV + "LLVM_COV_BIN=${LLVM_COV_BIN}" CACHE STRING + "Environment variables for llvm-cov-wrapper.") + mark_as_advanced(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV) + endif () + endif () + endif () + + if (NOT GCOV_BIN) + # Fall back to gcov binary if llvm-cov was not found or is + # incompatible. This is the default on OSX, but may crash on + # recent Linux versions. + find_program(GCOV_BIN gcov HINTS ${COMPILER_PATH}) + endif () + endif () + + + if (GCOV_BIN) + set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN "${GCOV_BIN}" CACHE STRING + "${LANG} gcov binary.") + + if (NOT CMAKE_REQUIRED_QUIET) + message("-- Found gcov evaluation for " + "${CMAKE_${LANG}_COMPILER_ID}: ${GCOV_BIN}") + endif() + + unset(GCOV_BIN CACHE) + endif () + endif () +endforeach () + + + + +# Add a new global target for all gcov targets. This target could be used to +# generate the gcov files for the whole project instead of calling -gcov +# for each target. +if (NOT TARGET gcov) + add_custom_target(gcov) +endif (NOT TARGET gcov) + + + +# This function will add gcov evaluation for target . Only sources of +# this target will be evaluated and no dependencies will be added. It will call +# Gcov on any source file of once and store the gcov file in the same +# directory. +function (add_gcov_target TNAME) + get_target_property(TBIN_DIR ${TNAME} BINARY_DIR) + set(TDIR ${TBIN_DIR}/CMakeFiles/${TNAME}.dir) + + # We don't have to check, if the target has support for coverage, thus this + # will be checked by add_coverage_target in Findcoverage.cmake. Instead we + # have to determine which gcov binary to use. + get_target_property(TSOURCES ${TNAME} SOURCES) + set(SOURCES "") + set(TCOMPILER "") + foreach (FILE ${TSOURCES}) + codecov_path_of_source(${FILE} FILE) + if (NOT "${FILE}" STREQUAL "") + codecov_lang_of_source(${FILE} LANG) + if (NOT "${LANG}" STREQUAL "") + list(APPEND SOURCES "${FILE}") + set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) + endif () + endif () + endforeach () + + # If no gcov binary was found, coverage data can't be evaluated. + if (NOT GCOV_${TCOMPILER}_BIN) + message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") + return() + endif () + + set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") + set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") + + + set(BUFFER "") + set(NULL_DEVICE "/dev/null") + if(WIN32) + set(NULL_DEVICE "NUL") + endif() + foreach(FILE ${SOURCES}) + get_filename_component(FILE_PATH "${TDIR}/${FILE}" PATH) + + # call gcov + add_custom_command(OUTPUT ${TDIR}/${FILE}.gcov + COMMAND ${GCOV_ENV} ${GCOV_BIN} -p ${TDIR}/${FILE}.gcno > ${NULL_DEVICE} + DEPENDS ${TNAME} ${TDIR}/${FILE}.gcno + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + + list(APPEND BUFFER ${TDIR}/${FILE}.gcov) + endforeach() + + + # add target for gcov evaluation of + add_custom_target(${TNAME}-gcov DEPENDS ${BUFFER}) + + # add evaluation target to the global gcov target. + add_dependencies(gcov ${TNAME}-gcov) +endfunction (add_gcov_target) diff --git a/cmake/FindLcov.cmake b/cmake/FindLcov.cmake new file mode 100644 index 0000000000..1d48b55149 --- /dev/null +++ b/cmake/FindLcov.cmake @@ -0,0 +1,365 @@ +# This file is part of CMake-codecov. +# +# SPDX-FileCopyrightText: RWTH Aachen University, Federal Republic of Germany +# SPDX-FileContributor: Alexander Haase, alexander.haase@rwth-aachen.de +# +# SPDX-License-Identifier: BSD-3-Clause + + +# configuration +set(LCOV_DATA_PATH "${CMAKE_BINARY_DIR}/lcov/data") +set(LCOV_DATA_PATH_INIT "${LCOV_DATA_PATH}/init") +set(LCOV_DATA_PATH_CAPTURE "${LCOV_DATA_PATH}/capture") +set(LCOV_HTML_PATH "${CMAKE_BINARY_DIR}/lcov/html") + +set(GENINFO_EXTRA_FLAGS "" CACHE STRING "Additional flags to pass to geninfo.") +string(REPLACE " " ";" GENINFO_EXTRA_FLAGS "${GENINFO_EXTRA_FLAGS}") +set(LCOV_EXTRA_FLAGS "" CACHE STRING "Additional flags to pass to lcov.") +string(REPLACE " " ";" LCOV_EXTRA_FLAGS "${LCOV_EXTRA_FLAGS}") + + +# Search for Gcov which is used by Lcov. +find_package(Gcov) + + + + +# This function will add lcov evaluation for target . Only sources of +# this target will be evaluated and no dependencies will be added. It will call +# geninfo on any source file of once and store the info file in the same +# directory. +# +# Note: This function is only a wrapper to define this function always, even if +# coverage is not supported by the compiler or disabled. This function must +# be defined here, because the module will be exited, if there is no coverage +# support by the compiler or it is disabled by the user. +function (add_lcov_target TNAME) + if (LCOV_FOUND) + # capture initial coverage data + lcov_capture_initial_tgt(${TNAME}) + + # capture coverage data after execution + lcov_capture_tgt(${TNAME}) + endif () +endfunction (add_lcov_target) + + + + +# include required Modules +include(FindPackageHandleStandardArgs) + +# Search for required lcov binaries. +find_program(LCOV_BIN lcov) +find_program(GENINFO_BIN geninfo) +find_program(GENHTML_BIN genhtml) +find_package_handle_standard_args(Lcov + REQUIRED_VARS LCOV_BIN GENINFO_BIN GENHTML_BIN +) + +# enable genhtml C++ demangeling, if c++filt is found. +set(GENHTML_CPPFILT_FLAG "") +find_program(CPPFILT_BIN c++filt) +if (NOT CPPFILT_BIN STREQUAL "") + set(GENHTML_CPPFILT_FLAG "--demangle-cpp") +endif (NOT CPPFILT_BIN STREQUAL "") + +# enable no-external flag for lcov, if available. +if (GENINFO_BIN AND NOT DEFINED GENINFO_EXTERN_FLAG) + set(FLAG "") + execute_process(COMMAND ${GENINFO_BIN} --help OUTPUT_VARIABLE GENINFO_HELP) + string(REGEX MATCH "external" GENINFO_RES "${GENINFO_HELP}") + if (GENINFO_RES) + set(FLAG "--no-external") + endif () + + set(GENINFO_EXTERN_FLAG "${FLAG}" + CACHE STRING "Geninfo flag to exclude system sources.") +endif () + +# If Lcov was not found, exit module now. +if (NOT LCOV_FOUND) + return() +endif (NOT LCOV_FOUND) + + + + +# Create directories to be used. +file(MAKE_DIRECTORY ${LCOV_DATA_PATH_INIT}) +file(MAKE_DIRECTORY ${LCOV_DATA_PATH_CAPTURE}) + +set(LCOV_REMOVE_PATTERNS "") + +# This function will merge lcov files to a single target file. Additional lcov +# flags may be set with setting LCOV_EXTRA_FLAGS before calling this function. +function (lcov_merge_files OUTFILE ...) + # Remove ${OUTFILE} from ${ARGV} and generate lcov parameters with files. + list(REMOVE_AT ARGV 0) + + # Generate merged file. + string(REPLACE "${CMAKE_BINARY_DIR}/" "" FILE_REL "${OUTFILE}") + add_custom_command(OUTPUT "${OUTFILE}.raw" + COMMAND cat ${ARGV} > ${OUTFILE}.raw + DEPENDS ${ARGV} + COMMENT "Generating ${FILE_REL}" + ) + + add_custom_command(OUTPUT "${OUTFILE}" + COMMAND ${LCOV_BIN} --quiet -a ${OUTFILE}.raw --output-file ${OUTFILE} + --base-directory ${PROJECT_SOURCE_DIR} ${LCOV_EXTRA_FLAGS} + COMMAND ${LCOV_BIN} --quiet -r ${OUTFILE} ${LCOV_REMOVE_PATTERNS} + --output-file ${OUTFILE} ${LCOV_EXTRA_FLAGS} + DEPENDS ${OUTFILE}.raw + COMMENT "Post-processing ${FILE_REL}" + ) +endfunction () + + + + +# Add a new global target to generate initial coverage reports for all targets. +# This target will be used to generate the global initial info file, which is +# used to gather even empty report data. +if (NOT TARGET lcov-capture-init) + add_custom_target(lcov-capture-init) + set(LCOV_CAPTURE_INIT_FILES "" CACHE INTERNAL "") +endif (NOT TARGET lcov-capture-init) + + +# This function will add initial capture of coverage data for target , +# which is needed to get also data for objects, which were not loaded at +# execution time. It will call geninfo for every source file of once and +# store the info file in the same directory. +function (lcov_capture_initial_tgt TNAME) + # We don't have to check, if the target has support for coverage, thus this + # will be checked by add_coverage_target in Findcoverage.cmake. Instead we + # have to determine which gcov binary to use. + get_target_property(TSOURCES ${TNAME} SOURCES) + set(SOURCES "") + set(TCOMPILER "") + foreach (FILE ${TSOURCES}) + codecov_path_of_source(${FILE} FILE) + if (NOT "${FILE}" STREQUAL "") + codecov_lang_of_source(${FILE} LANG) + if (NOT "${LANG}" STREQUAL "") + list(APPEND SOURCES "${FILE}") + set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) + endif () + endif () + endforeach () + + # If no gcov binary was found, coverage data can't be evaluated. + if (NOT GCOV_${TCOMPILER}_BIN) + message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") + return() + endif () + + set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") + set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") + + + get_target_property(TBIN_DIR ${TNAME} BINARY_DIR) + set(TDIR ${TBIN_DIR}/CMakeFiles/${TNAME}.dir) + + set(GENINFO_FILES "") + foreach(FILE ${SOURCES}) + # generate empty coverage files + set(OUTFILE "${TDIR}/${FILE}.info.init") + list(APPEND GENINFO_FILES ${OUTFILE}) + + add_custom_command(OUTPUT ${OUTFILE} COMMAND ${GCOV_ENV} ${GENINFO_BIN} + --quiet --base-directory ${PROJECT_SOURCE_DIR} --initial + --gcov-tool ${GCOV_BIN} --output-filename ${OUTFILE} + ${GENINFO_EXTERN_FLAG} ${TDIR}/${FILE}.gcno + ${GENINFO_EXTRA_FLAGS} + DEPENDS ${TNAME} + COMMENT "Capturing initial coverage data for ${FILE}" + ) + endforeach() + + # Concatenate all files generated by geninfo to a single file per target. + set(OUTFILE "${LCOV_DATA_PATH_INIT}/${TNAME}.info") + list(APPEND LCOV_EXTRA_FLAGS "--initial") + lcov_merge_files("${OUTFILE}" ${GENINFO_FILES}) + add_custom_target(${TNAME}-capture-init ALL DEPENDS ${OUTFILE}) + + # add geninfo file generation to global lcov-geninfo target + add_dependencies(lcov-capture-init ${TNAME}-capture-init) + set(LCOV_CAPTURE_INIT_FILES "${LCOV_CAPTURE_INIT_FILES}" + "${OUTFILE}" CACHE INTERNAL "" + ) +endfunction (lcov_capture_initial_tgt) + + +# This function will generate the global info file for all targets. It has to be +# called after all other CMake functions in the root CMakeLists.txt file, to get +# a full list of all targets that generate coverage data. +function (lcov_capture_initial) + # Skip this function (and do not create the following targets), if there are + # no input files. + if ("${LCOV_CAPTURE_INIT_FILES}" STREQUAL "") + return() + endif () + + # Add a new target to merge the files of all targets. + set(OUTFILE "${LCOV_DATA_PATH_INIT}/all_targets.info") + lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_INIT_FILES}) + add_custom_target(lcov-geninfo-init ALL DEPENDS ${OUTFILE} + lcov-capture-init + ) +endfunction (lcov_capture_initial) + + + + +# Add a new global target to generate coverage reports for all targets. This +# target will be used to generate the global info file. +if (NOT TARGET lcov-capture) + add_custom_target(lcov-capture) + set(LCOV_CAPTURE_FILES "" CACHE INTERNAL "") +endif (NOT TARGET lcov-capture) + + +# This function will add capture of coverage data for target , which is +# needed to get also data for objects, which were not loaded at execution time. +# It will call geninfo for every source file of once and store the info +# file in the same directory. +function (lcov_capture_tgt TNAME) + # We don't have to check, if the target has support for coverage, thus this + # will be checked by add_coverage_target in Findcoverage.cmake. Instead we + # have to determine which gcov binary to use. + get_target_property(TSOURCES ${TNAME} SOURCES) + set(SOURCES "") + set(TCOMPILER "") + foreach (FILE ${TSOURCES}) + codecov_path_of_source(${FILE} FILE) + if (NOT "${FILE}" STREQUAL "") + codecov_lang_of_source(${FILE} LANG) + if (NOT "${LANG}" STREQUAL "") + list(APPEND SOURCES "${FILE}") + set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) + endif () + endif () + endforeach () + + # If no gcov binary was found, coverage data can't be evaluated. + if (NOT GCOV_${TCOMPILER}_BIN) + message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") + return() + endif () + + set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") + set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") + + get_target_property(TBIN_DIR ${TNAME} BINARY_DIR) + set(TDIR ${TBIN_DIR}/CMakeFiles/${TNAME}.dir) + + set(GENINFO_FILES "") + foreach(FILE ${SOURCES}) + # Generate coverage files. If no .gcda file was generated during + # execution, the empty coverage file will be used instead. + set(OUTFILE "${TDIR}/${FILE}.info") + list(APPEND GENINFO_FILES ${OUTFILE}) + + # Create an empty .gcda file, so the target capture file can have a dependency on it. + # The capture file will only use this .gcda if it has a non-zero size (test -s). + add_custom_command(OUTPUT "${TDIR}/${FILE}.gcda" + COMMAND "${CMAKE_COMMAND}" -E touch "${TDIR}/${FILE}.gcda" + ) + + add_custom_command(OUTPUT ${OUTFILE} + COMMAND test -s "${TDIR}/${FILE}.gcda" + && ${GCOV_ENV} ${GENINFO_BIN} --quiet --base-directory + ${PROJECT_SOURCE_DIR} --gcov-tool ${GCOV_BIN} + --output-filename ${OUTFILE} ${GENINFO_EXTERN_FLAG} + ${TDIR}/${FILE}.gcda ${GENINFO_EXTRA_FLAGS} + || cp ${OUTFILE}.init ${OUTFILE} + DEPENDS ${TNAME} ${TNAME}-capture-init "${TDIR}/${FILE}.gcda" + COMMENT "Capturing coverage data for ${FILE}" + ) + endforeach() + + # Concatenate all files generated by geninfo to a single file per target. + set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/${TNAME}.info") + lcov_merge_files("${OUTFILE}" ${GENINFO_FILES}) + add_custom_target(${TNAME}-geninfo DEPENDS ${OUTFILE}) + + # add geninfo file generation to global lcov-capture target + add_dependencies(lcov-capture ${TNAME}-geninfo) + set(LCOV_CAPTURE_FILES "${LCOV_CAPTURE_FILES}" "${OUTFILE}" CACHE INTERNAL + "" + ) + + # Add target for generating html output for this target only. + file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/${TNAME}) + add_custom_target(${TNAME}-genhtml + COMMAND ${GENHTML_BIN} --quiet --sort ${GENHTML_CPPFILT_FLAG} + --prefix ${PROJECT_SOURCE_DIR} + --baseline-file ${LCOV_DATA_PATH_INIT}/${TNAME}.info + --output-directory ${LCOV_HTML_PATH}/${TNAME} + --title "${CMAKE_PROJECT_NAME} - target ${TNAME}" + ${OUTFILE} + DEPENDS ${TNAME}-geninfo ${TNAME}-capture-init + ) +endfunction (lcov_capture_tgt) + + +# This function will generate the global info file for all targets. It has to be +# called after all other CMake functions in the root CMakeLists.txt file, to get +# a full list of all targets that generate coverage data. +function (lcov_capture) + # Skip this function (and do not create the following targets), if there are + # no input files. + if ("${LCOV_CAPTURE_FILES}" STREQUAL "") + return() + endif () + + # Add a new target to merge the files of all targets. + set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/all_targets.info") + lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_FILES}) + add_custom_target(lcov-geninfo DEPENDS ${OUTFILE} lcov-capture) + + # Add a new global target for all lcov targets. This target could be used to + # generate the lcov html output for the whole project instead of calling + # -geninfo and -genhtml for each target. It will also be + # used to generate a html site for all project data together instead of one + # for each target. + if (NOT TARGET lcov) + file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/all_targets) + add_custom_target(lcov + COMMAND ${GENHTML_BIN} --quiet --sort ${GENHTML_CPPFILT_FLAG} + --baseline-file ${LCOV_DATA_PATH_INIT}/all_targets.info + --output-directory ${LCOV_HTML_PATH}/all_targets + --title "${CMAKE_PROJECT_NAME}" --prefix "${PROJECT_SOURCE_DIR}" + ${OUTFILE} + DEPENDS lcov-geninfo-init lcov-geninfo + ) + endif () +endfunction (lcov_capture) + + + + +# Add a new global target to generate the lcov html report for the whole project +# instead of calling -genhtml for each target (to create an own report +# for each target). Instead of the lcov target it does not require geninfo for +# all targets, so you have to call -geninfo to generate the info files +# the targets you'd like to have in your report or lcov-geninfo for generating +# info files for all targets before calling lcov-genhtml. +file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/selected_targets) +if (NOT TARGET lcov-genhtml) + add_custom_target(lcov-genhtml + COMMAND ${GENHTML_BIN} + --quiet + --output-directory ${LCOV_HTML_PATH}/selected_targets + --title \"${CMAKE_PROJECT_NAME} - targets `find + ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name + \"all_targets.info\" -exec basename {} .info \\\;`\" + --prefix ${PROJECT_SOURCE_DIR} + --sort + ${GENHTML_CPPFILT_FLAG} + `find ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name + \"all_targets.info\"` + ) +endif (NOT TARGET lcov-genhtml) diff --git a/cmake/FindMKL.cmake b/cmake/FindMKL.cmake index 3680b8a92c..ca1147a432 100644 --- a/cmake/FindMKL.cmake +++ b/cmake/FindMKL.cmake @@ -102,3 +102,17 @@ if(NOT TARGET MKL::MKL_SCALAPACK) ) endif() endif() + + +if(NOT TARGET MKL::CORE) + find_library(MKL_CORE NAMES mkl_core HINTS ${MKLROOT}/lib ${MKLROOT}/lib/intel64) + message(STATUS "Found MKL_CORE: ${MKL_CORE}") + if(MKL_CORE) + # create an IMPORTED target that points to the discovered library file + add_library(MKL::CORE UNKNOWN IMPORTED) + set_target_properties(MKL::CORE PROPERTIES + IMPORTED_LOCATION "${MKL_CORE}" + INTERFACE_INCLUDE_DIRECTORIES "${MKL_INCLUDE}" + ) + endif() +endif() \ No newline at end of file diff --git a/cmake/Findcodecov.cmake b/cmake/Findcodecov.cmake new file mode 100644 index 0000000000..004b086e70 --- /dev/null +++ b/cmake/Findcodecov.cmake @@ -0,0 +1,275 @@ +# This file is part of CMake-codecov. +# +# SPDX-FileCopyrightText: RWTH Aachen University, Federal Republic of Germany +# SPDX-FileContributor: Alexander Haase, alexander.haase@rwth-aachen.de +# +# SPDX-License-Identifier: BSD-3-Clause + + +# Add an option to choose, if coverage should be enabled or not. If enabled +# marked targets will be build with coverage support and appropriate targets +# will be added. If disabled coverage will be ignored for *ALL* targets. +option(ENABLE_COVERAGE "Enable coverage build." OFF) + +set(COVERAGE_FLAG_CANDIDATES + # gcc and clang + "-O0 -g -fprofile-arcs -ftest-coverage" + + # gcc and clang fallback + "-O0 -g --coverage" +) + + +# Add coverage support for target ${TNAME} and register target for coverage +# evaluation. If coverage is disabled or not supported, this function will +# simply do nothing. +# +# Note: This function is only a wrapper to define this function always, even if +# coverage is not supported by the compiler or disabled. This function must +# be defined here, because the module will be exited, if there is no coverage +# support by the compiler or it is disabled by the user. +function (add_coverage TNAME) + # only add coverage for target, if coverage is support and enabled. + if (ENABLE_COVERAGE) + foreach (TNAME ${ARGV}) + add_coverage_target(${TNAME}) + endforeach () + endif () +endfunction (add_coverage) + + +# Add global target to gather coverage information after all targets have been +# added. Other evaluation functions could be added here, after checks for the +# specific module have been passed. +# +# Note: This function is only a wrapper to define this function always, even if +# coverage is not supported by the compiler or disabled. This function must +# be defined here, because the module will be exited, if there is no coverage +# support by the compiler or it is disabled by the user. +function (coverage_evaluate) + # add lcov evaluation + if (LCOV_FOUND) + lcov_capture_initial() + lcov_capture() + endif (LCOV_FOUND) +endfunction () + + +# Exit this module, if coverage is disabled. add_coverage is defined before this +# return, so this module can be exited now safely without breaking any build- +# scripts. +if (NOT ENABLE_COVERAGE) + return() +endif () + + + + +# Find the required flags foreach language. +set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET}) +set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY}) + +get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach (LANG ${ENABLED_LANGUAGES}) + if (NOT ${LANG} MATCHES "^(C|CXX|Fortran)$") + message(STATUS "Skipping coverage for unsupported language: ${LANG}") + continue() + endif () + + # Coverage flags are not dependent on language, but the used compiler. So + # instead of searching flags foreach language, search flags foreach compiler + # used. + set(COMPILER ${CMAKE_${LANG}_COMPILER_ID}) + if (NOT COVERAGE_${COMPILER}_FLAGS) + foreach (FLAG ${COVERAGE_FLAG_CANDIDATES}) + if(NOT CMAKE_REQUIRED_QUIET) + message(STATUS "Try ${COMPILER} code coverage flag = [${FLAG}]") + endif() + + set(CMAKE_REQUIRED_FLAGS "${FLAG}") + unset(COVERAGE_FLAG_DETECTED CACHE) + + if (${LANG} STREQUAL "C") + include(CheckCCompilerFlag) + check_c_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED) + + elseif (${LANG} STREQUAL "CXX") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED) + + elseif (${LANG} STREQUAL "Fortran") + # CheckFortranCompilerFlag was introduced in CMake 3.x. To be + # compatible with older Cmake versions, we will check if this + # module is present before we use it. Otherwise we will define + # Fortran coverage support as not available. + include(CheckFortranCompilerFlag OPTIONAL + RESULT_VARIABLE INCLUDED) + if (INCLUDED) + check_fortran_compiler_flag("${FLAG}" + COVERAGE_FLAG_DETECTED) + elseif (NOT CMAKE_REQUIRED_QUIET) + message("-- Performing Test COVERAGE_FLAG_DETECTED") + message("-- Performing Test COVERAGE_FLAG_DETECTED - Failed" + " (Check not supported)") + endif () + endif() + + unset(CMAKE_REQUIRED_FLAGS) + + if (COVERAGE_FLAG_DETECTED) + set(COVERAGE_${COMPILER}_FLAGS "${FLAG}" + CACHE STRING "${COMPILER} flags for code coverage.") + mark_as_advanced(COVERAGE_${COMPILER}_FLAGS) + break() + elseif (NOT CMAKE_REQUIRED_QUIET) + message(WARNING "Code coverage is not available for ${COMPILER}" + " compiler. Targets using this compiler will be " + "compiled without it.") + endif () + endforeach () + endif () +endforeach () + +set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE}) + + + + +# Helper function to get the language of a source file. +function (codecov_lang_of_source FILE RETURN_VAR) + # Usually, only the last extension of the file should be checked, to avoid + # template files (i.e. *.t.cpp) are checked with the full file extension. + # However, this feature requires CMake 3.14 or later. + set(EXT_COMP "LAST_EXT") + if(${CMAKE_VERSION} VERSION_LESS "3.14.0") + set(EXT_COMP "EXT") + endif() + + get_filename_component(FILE_EXT "${FILE}" ${EXT_COMP}) + string(TOLOWER "${FILE_EXT}" FILE_EXT) + string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT) + + get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + foreach (LANG ${ENABLED_LANGUAGES}) + list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP) + if (NOT ${TEMP} EQUAL -1) + set(${RETURN_VAR} "${LANG}" PARENT_SCOPE) + return() + endif () + endforeach() + + set(${RETURN_VAR} "" PARENT_SCOPE) +endfunction () + + +# Helper function to get the relative path of the source file destination path. +# This path is needed by FindGcov and FindLcov cmake files to locate the +# captured data. +function (codecov_path_of_source FILE RETURN_VAR) + string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _source ${FILE}) + + # If expression was found, SOURCEFILE is a generator-expression for an + # object library. Currently we found no way to call this function automatic + # for the referenced target, so it must be called in the directoryso of the + # object library definition. + if (NOT "${_source}" STREQUAL "") + set(${RETURN_VAR} "" PARENT_SCOPE) + return() + endif () + + + string(REPLACE "${CMAKE_CURRENT_BINARY_DIR}/" "" FILE "${FILE}") + if(IS_ABSOLUTE ${FILE}) + file(RELATIVE_PATH FILE ${CMAKE_CURRENT_SOURCE_DIR} ${FILE}) + endif() + + # get the right path for file + string(REPLACE ".." "__" PATH "${FILE}") + + set(${RETURN_VAR} "${PATH}" PARENT_SCOPE) +endfunction() + + + + +# Add coverage support for target ${TNAME} and register target for coverage +# evaluation. +function(add_coverage_target TNAME) + # Check if all sources for target use the same compiler. If a target uses + # e.g. C and Fortran mixed and uses different compilers (e.g. clang and + # gfortran) this can trigger huge problems, because different compilers may + # use different implementations for code coverage. + get_target_property(TSOURCES ${TNAME} SOURCES) + set(TARGET_COMPILER "") + set(ADDITIONAL_FILES "") + foreach (FILE ${TSOURCES}) + # If expression was found, FILE is a generator-expression for an object + # library. Object libraries will be ignored. + string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE}) + if ("${_file}" STREQUAL "") + codecov_lang_of_source(${FILE} LANG) + if (LANG) + list(APPEND TARGET_COMPILER ${CMAKE_${LANG}_COMPILER_ID}) + + list(APPEND ADDITIONAL_FILES "${FILE}.gcno") + list(APPEND ADDITIONAL_FILES "${FILE}.gcda") + endif () + endif () + endforeach () + + list(REMOVE_DUPLICATES TARGET_COMPILER) + list(LENGTH TARGET_COMPILER NUM_COMPILERS) + + if (NUM_COMPILERS GREATER 1) + message(WARNING "Can't use code coverage for target ${TNAME}, because " + "it will be compiled by incompatible compilers. Target will be " + "compiled without code coverage.") + return() + + elseif (NUM_COMPILERS EQUAL 0) + message(WARNING "Can't use code coverage for target ${TNAME}, because " + "it uses an unknown compiler. Target will be compiled without " + "code coverage.") + return() + + elseif (NOT DEFINED "COVERAGE_${TARGET_COMPILER}_FLAGS") + # A warning has been printed before, so just return if flags for this + # compiler aren't available. + return() + endif() + + + # enable coverage for target + set_property(TARGET ${TNAME} APPEND_STRING + PROPERTY COMPILE_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}") + set_property(TARGET ${TNAME} APPEND_STRING + PROPERTY LINK_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}") + + + # Add gcov files generated by compiler to clean target. + set(CLEAN_FILES "") + foreach (FILE ${ADDITIONAL_FILES}) + codecov_path_of_source(${FILE} FILE) + list(APPEND CLEAN_FILES "CMakeFiles/${TNAME}.dir/${FILE}") + endforeach() + + if(${CMAKE_VERSION} VERSION_LESS "3.15.0") + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES + "${CLEAN_FILES}") + else() + set_directory_properties(PROPERTIES ADDITIONAL_CLEAN_FILES + "${CLEAN_FILES}") + endif() + + + add_gcov_target(${TNAME}) + add_lcov_target(${TNAME}) +endfunction(add_coverage_target) + + + + +# Include modules for parsing the collected data and output it in a readable +# format (like gcov and lcov). +find_package(Gcov) +find_package(Lcov) diff --git a/source/source_cell/CMakeLists.txt b/source/source_cell/CMakeLists.txt index f9d4f0c535..ad3aefba42 100644 --- a/source/source_cell/CMakeLists.txt +++ b/source/source_cell/CMakeLists.txt @@ -3,7 +3,7 @@ add_subdirectory(module_neighbor) add_library( cell - OBJECT + STATIC atom_pseudo.cpp atom_spec.cpp pseudo.cpp diff --git a/source/source_lcao/module_hcontainer/CMakeLists.txt b/source/source_lcao/module_hcontainer/CMakeLists.txt index 65db746a84..e0a5efe23b 100644 --- a/source/source_lcao/module_hcontainer/CMakeLists.txt +++ b/source/source_lcao/module_hcontainer/CMakeLists.txt @@ -12,7 +12,7 @@ list(APPEND objects add_library( hcontainer - OBJECT + STATIC ${objects} ) @@ -26,4 +26,4 @@ IF (BUILD_TESTING) endif() endif() -endif() \ No newline at end of file +endif() diff --git a/source/source_lcao/module_rt/CMakeLists.txt b/source/source_lcao/module_rt/CMakeLists.txt index 58bc834a5f..4e794487db 100644 --- a/source/source_lcao/module_rt/CMakeLists.txt +++ b/source/source_lcao/module_rt/CMakeLists.txt @@ -19,7 +19,7 @@ if(ENABLE_LCAO) add_library( tddft - OBJECT + STATIC ${objects} ) diff --git a/tests/integrate/CMakeLists.txt b/tests/integrate/CMakeLists.txt index 8da79197aa..106c179f90 100644 --- a/tests/integrate/CMakeLists.txt +++ b/tests/integrate/CMakeLists.txt @@ -1,5 +1,14 @@ find_program(BASH bash) find_package(Python3 REQUIRED) + +add_executable(sum_cube ${ABACUS_TEST_DIR}/integrate/tools/sum_cube.cpp) +install(TARGETS sum_cube DESTINATION ${ABACUS_TEST_DIR}/integrate/tools/) +message(STATUS "Target sum_cube has been defined in ${CMAKE_CURRENT_SOURCE_DIR}") # 添加此行 + +if(ENABLE_COVERAGE) + add_coverage(sum_cube) +endif() + if(ENABLE_ASAN) add_test( NAME integrated_test_with_asan @@ -13,10 +22,4 @@ else() COMMAND ${BASH} Autotest.sh -a ${ABACUS_BIN_PATH} -n 4 WORKING_DIRECTORY ${ABACUS_TEST_DIR}/integrate ) - add_executable(sum_cube.exe ${ABACUS_TEST_DIR}/integrate/tools/sum_cube.cpp) - install(TARGETS sum_cube.exe DESTINATION ${ABACUS_TEST_DIR}/integrate/tools/) - - if(ENABLE_COVERAGE) - add_coverage(sum_cube.exe) - endif() endif()