diff --git a/.gitignore b/.gitignore index dda7dae4..b74f51e4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ Makefile gmon.out cmake-build-debug +coverage-output build pvt diff --git a/etc/Build.sh b/etc/Build.sh new file mode 100755 index 00000000..2fa954bc --- /dev/null +++ b/etc/Build.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash + +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2025, The OpenROAD Authors + +set -euo pipefail + +DIR="$(dirname $(readlink -f $0))" +cd "$DIR/../" + +# default values, can be overwritten by cmdline args +buildDir="build" +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + numThreads=$(nproc --all) +elif [[ "$OSTYPE" == "darwin"* ]]; then + numThreads=$(sysctl -n hw.ncpu) +else + cat << EOF +WARNING: Unsupported OSTYPE: cannot determine number of host CPUs" + Defaulting to 2 threads. Use -threads=N to use N threads" +EOF + numThreads=2 +fi +cmakeOptions="" +cleanBefore=no +compiler=gcc + +_help() { + cat < has multiple + e.g.: -cmake='-DFLAGS="-a -b"' + -compiler=COMPILER_NAME Compiler name: gcc or clang + Default: gcc + -dir=PATH Path to store build files. + Default: ./build + -coverage Enable cmake coverage options + -clean Remove build dir before compile + -threads=NUM_THREADS Number of threads to use during + compile. Default: \`nproc\` on linux + or \`sysctl -n hw.ncpu\` on macOS + -O0 Disable optimizations for accurate + coverage measurement + -help Shows this message + +EOF + exit "${1:-1}" +} + +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|-help) + _help 0 + ;; + -coverage ) + cmakeOptions+=" -DCMAKE_BUILD_TYPE=Debug" + cmakeOptions+=" -DCMAKE_CXX_FLAGS='--coverage'" + cmakeOptions+=" -DCMAKE_EXE_LINKER_FLAGS='--coverage'" + ;; + -O0 ) + cmakeOptions+=" -DCMAKE_CXX_FLAGS_DEBUG='-g -O0'" + ;; + -cmake=*) + cmakeOptions+=" ${1#*=}" + ;; + -clean ) + cleanBefore=yes + ;; + -compiler=*) + compiler="${1#*=}" + ;; + -dir=* ) + buildDir="${1#*=}" + ;; + -threads=* ) + numThreads="${1#*=}" + ;; + -compiler | -cmake | -dir | -threads ) + echo "${1} requires an argument" >&2 + _help + ;; + *) + echo "unknown option: ${1}" >&2 + _help + ;; + esac + shift 1 +done + +case "${compiler}" in + "gcc" ) + export CC="$(command -v gcc)" + export CXX="$(command -v g++)" + ;; + "clang" ) + export CC="$(command -v clang)" + export CXX="$(command -v clang++)" + ;; + *) + echo "Compiler $compiler not supported. Use gcc or clang." >&2 + _help 1 +esac + +if [[ -z "${CC}" || -z "${CXX}" ]]; then + echo "Compiler $compiler not installed." >&2 + _help 1 +fi + +if [[ "${cleanBefore}" == "yes" ]]; then + rm -rf "${buildDir}" +fi + +mkdir -p "${buildDir}" + +echo "[INFO] Compiler: ${compiler} (${CC})" +echo "[INFO] Build dir: ${buildDir}" +echo "[INFO] Using ${numThreads} threads." + +eval cmake "${cmakeOptions}" -B "${buildDir}" . +eval time cmake --build "${buildDir}" -j "${numThreads}" diff --git a/etc/CodeCoverage.sh b/etc/CodeCoverage.sh new file mode 100755 index 00000000..6b65ca29 --- /dev/null +++ b/etc/CodeCoverage.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash + +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2025, The OpenROAD Authors + +# Generate an lcov/genhtml code coverage report for OpenSTA. +# Requires a coverage build: ./etc/Build.sh -coverage +# +# Usage: +# ./etc/CodeCoverage.sh Run all tests, generate report +# ./etc/CodeCoverage.sh -tcl-only Run only Tcl tests (skip C++ unit tests) + +set -euo pipefail + +cd "$(dirname $(readlink -f $0))/../" + +buildDir="build" +reportDir="coverage-output" +tclOnly=no + +_help() { + cat <&2 + _help + ;; + *) + echo "unknown option: ${1}" >&2 + _help + ;; + esac + shift 1 +done + +if [[ ! -d "${buildDir}" ]]; then + echo "Build directory '${buildDir}' not found." >&2 + echo "Run ./etc/Build.sh -coverage first." >&2 + exit 1 +fi + +# Common lcov flags to suppress all known warnings: +# mismatch - GCC/GoogleTest macro function range mismatches +# gcov - unexecuted blocks on non-branch lines (GCC optimizer artifacts) +# source - source file newer than .gcno notes file (stale build artifacts) +# Double-specification (X,X) suppresses the warning output entirely. +LCOV_IGNORE="--ignore-errors mismatch,mismatch,gcov,gcov,source,source,unused,unused,negative,negative" + +# Clear stale coverage data before test execution. +# Old .gcda files from previous runs can cause gcov checksum mismatch noise. +find "${buildDir}" -name '*.gcda' -delete + +# Step 1: Run tests +if [[ "${tclOnly}" == "yes" ]]; then + echo "[INFO] Running Tcl tests only..." + ctest --test-dir "${buildDir}" -L tcl -j $(nproc) --output-on-failure || true +else + echo "[INFO] Running all tests..." + ctest --test-dir "${buildDir}" -j $(nproc) --output-on-failure || true +fi + +# Step 2: Capture coverage +echo "[INFO] Capturing coverage data..." +lcov --capture \ + -d "${buildDir}" \ + -o "${buildDir}/coverage.info" \ + --quiet \ + ${LCOV_IGNORE} + +# Step 3: Filter system/build/test dirs +lcov --remove "${buildDir}/coverage.info" \ + '/usr/*' \ + '*/build/*' \ + '*/test/*' \ + -o "${buildDir}/filtered.info" \ + --quiet \ + ${LCOV_IGNORE} + +# Step 4: Generate HTML report +GENHTML_IGNORE="--ignore-errors unmapped,unmapped,inconsistent,inconsistent,corrupt,corrupt,negative,negative,empty,empty,format,format,mismatch,mismatch,source,source" + +mkdir -p "${reportDir}" +genhtml "${buildDir}/filtered.info" \ + --output-directory "${reportDir}" \ + --quiet \ + ${GENHTML_IGNORE} || true + +echo "" +echo "=== Coverage report generated ===" +echo "Open $(pwd)/${reportDir}/index.html in a browser to view." +echo "" +lcov --summary "${buildDir}/filtered.info" ${LCOV_IGNORE} 2>&1 || true diff --git a/make_coverage_report.sh b/make_coverage_report.sh deleted file mode 100755 index 13110115..00000000 --- a/make_coverage_report.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/bin/bash -# Usage: ./make_coverage_report.sh [-B] [--tcl_only] [--O0] -# Generates an lcov/genhtml coverage report for OpenSTA tests. -# --O0 Disable optimizations for accurate coverage (no inlining) -# --tcl_only Run only Tcl tests (skip C++ unit tests) - -set -euo pipefail - -usage() { - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Build OpenSTA with coverage instrumentation, run tests, and generate" - echo "an lcov/genhtml coverage report." - echo "" - echo "Options:" - echo " -B Clean rebuild (remove build directory before building)" - echo " --O0 Disable optimizations (-O0) for accurate coverage measurement" - echo " --tcl_only Run only Tcl tests (skip C++ unit tests)" - echo " -h, --help Show this help message" - echo "" - echo "Output:" - echo " build/ Build directory (sta executable at build/sta)" - echo " build/coverage_report/ HTML coverage report" - exit 0 -} - -CLEAN_BUILD=0 -TCL_ONLY=0 -OPT_LEVEL="" -for arg in "$@"; do - case "$arg" in - -h|--help) usage ;; - -B) CLEAN_BUILD=1 ;; - --tcl_only) TCL_ONLY=1 ;; - --O0) OPT_LEVEL="-O0" ;; - *) echo "Unknown argument: $arg"; echo "Run '$0 --help' for usage."; exit 1 ;; - esac -done - -BUILD_DIR="build" -REPORT_DIR="build/coverage_report" - -# Common lcov flags to suppress all known warnings: -# mismatch - GCC/GoogleTest macro function range mismatches -# gcov - unexecuted blocks on non-branch lines (GCC optimizer artifacts) -# source - source file newer than .gcno notes file (stale build artifacts) -# Double-specification (X,X) suppresses the warning output entirely. -LCOV_IGNORE="--ignore-errors mismatch,mismatch,gcov,gcov,source,source" - -echo "=== OpenSTA Coverage Report ===" -echo "Build directory: $BUILD_DIR" -echo "Report directory: $REPORT_DIR" -echo "Clean build: $CLEAN_BUILD" -echo "TCL only: $TCL_ONLY" -echo "Optimization: ${OPT_LEVEL:-default}" - -# Step 1: Configure -if [ "$CLEAN_BUILD" -eq 1 ] && [ -d "$BUILD_DIR" ]; then - echo "Removing $BUILD_DIR for clean rebuild..." - rm -rf "$BUILD_DIR" -fi -mkdir -p "$BUILD_DIR" -cd "$BUILD_DIR" - -cmake .. \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DCMAKE_CXX_FLAGS="--coverage $OPT_LEVEL" \ - -DCMAKE_EXE_LINKER_FLAGS="--coverage" \ - -DBUILD_TESTS=ON - -# Step 2: Build -make -j$(nproc) - -# Step 2.5: Clear stale coverage data before test execution. -# Old .gcda files from previous builds can cause gcov checksum mismatch noise -# that pollutes test logs and leads to false regression diffs. -find . -name '*.gcda' -delete - -# Step 3: Run tests -if [ "$TCL_ONLY" -eq 1 ]; then - echo "Running Tcl tests only..." - ctest -L tcl -j$(nproc) --output-on-failure || true -else - echo "Running all tests..." - ctest -j$(nproc) --output-on-failure || true -fi - -# Step 4: Capture coverage -lcov --capture -d . -o coverage.info --quiet $LCOV_IGNORE - -# Step 5: Filter system/build/test dirs -lcov --remove coverage.info '/usr/*' '*/build/*' '*/test/*' -o filtered.info --quiet $LCOV_IGNORE - -# Step 6: Generate HTML report -ABS_REPORT_DIR="$(cd .. && pwd)/$REPORT_DIR" -genhtml filtered.info --output-directory "$ABS_REPORT_DIR" --quiet \ - --ignore-errors unmapped,unmapped,inconsistent,inconsistent,corrupt,corrupt,negative,negative,empty,empty,format,format,mismatch,mismatch,source,source || true - -echo "" -echo "=== Coverage report generated ===" -echo "Open $ABS_REPORT_DIR/index.html in a browser to view." -echo "sta executable: $(pwd)/sta" -echo "" -lcov --summary filtered.info $LCOV_IGNORE 2>&1 || true