Skip to content

doc: cmake compilation guide - #7744

Merged
petrasovaa merged 2 commits into
OSGeo:mainfrom
petrasovaa:cmake-compilation-doc
Aug 6, 2026
Merged

petrasovaa merged 2 commits into
OSGeo:mainfrom
petrasovaa:cmake-compilation-doc

Conversation

@petrasovaa

Copy link
Copy Markdown
Contributor

This is an attempt to help myself and other transition to cmake compilation. I wanted to have an overview of cmake commands and workflows. I also tried to link it in relevant parts of the documentation and CONTRIBUTING, I ended up removing a lot of stuff from there. There is probably more cleanup needed there and in INSTALL.md and testing README, but that was somewhat out of scope. In the guide, I specifically am not sure about parts:

  • Running parts from the build directory
  • Building Addons

@petrasovaa
petrasovaa requested review from nilason and wenzeslaus July 17, 2026 09:50
@github-actions github-actions Bot added docs markdown Related to markdown, markdown files labels Jul 17, 2026
@cwhite911

Copy link
Copy Markdown
Contributor

I've been using 'ninja' locally with CMakePresets.json

{
    "version": 3,
    "cmakeMinimumRequired": {
        "major": 3,
        "minor": 22,
        "patch": 0
    },
    "configurePresets": [
        {
            "name": "ninja-release-local",
            "displayName": "Ninja Release (local install)",
            "description": "Matches mycmake.sh options but installs into ./_install to avoid sudo.",
            "generator": "Ninja",
            "binaryDir": "${sourceDir}/build",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Release",
                "CMAKE_INSTALL_PREFIX": "${sourceDir}/_install",
                "PKG_CONFIG_EXECUTABLE": "/usr/bin/pkg-config",
                "CBLAS_PKGCONFIG": "openblas",
                "LAPACKE_PKGCONFIG": "lapacke",
                "WITH_NETCDF": "ON",
                "WITH_READLINE": "ON",
                "WITH_FREETYPE": "ON"
            }
        },
        {
            "name": "unix-makefiles-release-local",
            "displayName": "Unix Makefiles Release (local install)",
            "description": "Fallback preset when Ninja is not available.",
            "generator": "Unix Makefiles",
            "binaryDir": "${sourceDir}/build",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Release",
                "CMAKE_INSTALL_PREFIX": "${sourceDir}/_install",
                "PKG_CONFIG_EXECUTABLE": "/usr/bin/pkg-config",
                "CBLAS_PKGCONFIG": "openblas",
                "LAPACKE_PKGCONFIG": "lapacke",
                "WITH_NETCDF": "ON",
                "WITH_READLINE": "ON",
                "WITH_FREETYPE": "ON"
            }
        },
        {
            "name": "ninja-release-system",
            "displayName": "Ninja Release (/usr/local)",
            "description": "Closest to mycmake.sh (may require sudo for install).",
            "generator": "Ninja",
            "binaryDir": "${sourceDir}/build",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Release",
                "CMAKE_INSTALL_PREFIX": "/usr/local",
                "PKG_CONFIG_EXECUTABLE": "/usr/bin/pkg-config",
                "CBLAS_PKGCONFIG": "openblas",
                "LAPACKE_PKGCONFIG": "lapacke",
                "WITH_NETCDF": "ON",
                "WITH_READLINE": "ON",
                "WITH_FREETYPE": "ON"
            }
        }
    ],
    "buildPresets": [
        {
            "name": "build",
            "displayName": "Build",
            "configurePreset": "ninja-release-local",
            "jobs": 0
        },
        {
            "name": "install",
            "displayName": "Install",
            "configurePreset": "ninja-release-local",
            "targets": [
                "install"
            ],
            "jobs": 0
        }
    ]
}

I also have 'mycmake.sh`

#!/bin/bash
# Build, install, and optionally test GRASS on Pop!_OS 22.04.
#
# Usage:
#   ./mycmake.sh                 Build and install (default).
#   ./mycmake.sh -t              Build, install, then run the whole test suite.
#   ./mycmake.sh -t PATH         Build, install, then test only PATH (a tool or
#                                lib dir, e.g. lib/octree or raster/r.slope.aspect).
#   ./mycmake.sh -T [PATH]       Test only, against the existing build/install
#                                (skip configure, build, and install).
#
# Test layers, run in order; a failure in one is reported but the rest still run:
#   1. C unit-test modules (test.*.lib) via grass --tmp-project --exec ... -u
#   2. ctest registered suite (the gunittest testsuite/ tests)
#   3. pytest (the tests/ pytest-style tests)
# The NC SPM "thorough" gunittest run is intentionally not invoked here: it is
# assumed any sample data a test needs is already downloaded.

# This script uses bash-only features (pipefail, arrays). If invoked as `sh`,
# re-exec with bash.
if [ -z "${BASH_VERSION:-}" ]; then
    exec bash "$0" "$@"
fi

set -euo pipefail

build_dir="build"
install_prefix="/usr/local"

# Preserve the caller's PATH (and thus any active venv) for the test phase; the
# build phase deliberately runs with a minimal PATH.
orig_path="${PATH}"

run_tests=0
test_only=0
test_path=""

while [ $# -gt 0 ]; do
    case "$1" in
        -t | --test) run_tests=1 ;;
        -T | --test-only)
            run_tests=1
            test_only=1
            ;;
        -h | --help)
            sed -n '2,21p' "$0"
            exit 0
            ;;
        -*)
            echo "Unknown option: $1" >&2
            exit 2
            ;;
        *) test_path="$1" ;;
    esac
    shift
done

build_and_install() {
    local cmake_generator_args=()
    if command -v ninja >/dev/null 2>&1; then
        cmake_generator_args=(-G Ninja)
    fi

    rm -rf "${build_dir}"

    # Configure and build with a minimal PATH, scoped to this subshell so the
    # caller's PATH (and venv) survive for the test phase.
    (
        PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
        cmake -S . -B "${build_dir}" "${cmake_generator_args[@]}" \
            -DCMAKE_BUILD_TYPE=Release \
            -DCMAKE_INSTALL_PREFIX="${install_prefix}" \
            -DPKG_CONFIG_EXECUTABLE=/usr/bin/pkg-config \
            -DCBLAS_PKGCONFIG=openblas \
            -DLAPACKE_PKGCONFIG=lapacke \
            -DWITH_NETCDF=ON \
            -DWITH_READLINE=ON \
            -DWITH_FREETYPE=ON
        cmake --build "${build_dir}" --parallel
    )

    # Use sudo only for the install step.
    sudo cmake --install "${build_dir}"
}

# Populate the environment pytest and the grass session runner need, using the
# freshly installed grass.
setup_test_env() {
    export PATH="${install_prefix}/bin:${orig_path}"
    if ! command -v grass >/dev/null 2>&1; then
        echo "grass not found on PATH; build and install first (drop -T)." >&2
        exit 1
    fi
    gisbase_path="$(grass --config path)"
    export PYTHONPATH="$(grass --config python_path):${PYTHONPATH:-}"
    export LD_LIBRARY_PATH="${gisbase_path}/lib:${LD_LIBRARY_PATH:-}"
    # Used by .coveragerc path mapping; harmless otherwise.
    export INITIAL_GISBASE="${gisbase_path}"
    export INITIAL_PWD="${PWD}"
}

# Run installed C unit-test modules (test.*.lib). With a filter, run only the
# module for that lib (lib/octree -> test.octree.lib).
run_c_unit_tests() {
    local filter="$1" found=0
    local bin name
    for bin in "${gisbase_path}"/bin/test.*.lib; do
        [ -e "${bin}" ] || continue
        name="$(basename "${bin}")"
        if [ -n "${filter}" ] && [ "${name}" != "test.${filter}.lib" ]; then
            continue
        fi
        found=1
        echo ">>> C unit test: ${name}"
        if ! grass --tmp-project XY --exec "${name}" -u; then
            tests_failed=1
        fi
    done
    if [ "${found}" -eq 0 ] && [ -n "${filter}" ]; then
        echo ">>> no C unit-test module test.${filter}.lib (skipping)"
    fi
}

# Run the ctest-registered suite (gunittest testsuite/ tests). With a filter,
# restrict to tests whose name matches it.
run_ctest_suite() {
    local filter="$1"
    echo ">>> ctest suite${filter:+ (filter: ${filter})}"
    if [ -n "${filter}" ]; then
        ctest --test-dir "${build_dir}" --output-on-failure -R "${filter}" \
            || tests_failed=1
    else
        ctest --test-dir "${build_dir}" --output-on-failure || tests_failed=1
    fi
}

# Run pytest. With a path, scope to it; otherwise collect from the repo root.
# pytest exit code 5 means "no tests collected", which we treat as a skip.
run_pytest() {
    local path="${1:-.}" rc=0
    echo ">>> pytest: ${path}"
    pytest "${path}" || rc=$?
    if [ "${rc}" -eq 5 ]; then
        echo ">>> no pytest tests under ${path} (skipping)"
    elif [ "${rc}" -ne 0 ]; then
        tests_failed=1
    fi
}

run_test_suite() {
    setup_test_env

    tests_failed=0
    local filter=""
    if [ -n "${test_path}" ]; then
        filter="$(basename "${test_path}")"
    fi

    run_c_unit_tests "${filter}"
    run_ctest_suite "${filter}"
    run_pytest "${test_path}"

    if [ "${tests_failed}" -ne 0 ]; then
        echo "!!! one or more test layers failed" >&2
        exit 1
    fi
    echo "=== all test layers passed ==="
}

if [ "${test_only}" -eq 0 ]; then
    build_and_install
fi

if [ "${run_tests}" -eq 1 ]; then
    run_test_suite
fi

@nilason nilason left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. The install instruction cmake --install build is only mentioned under Compiling Addons, should probably deserve to be added before.

(I'm not too happy with ccache being ON by default, but that is the current behaviour so this description is correct).

@petrasovaa

Copy link
Copy Markdown
Contributor Author

Looks good to me. The install instruction cmake --install build is only mentioned under Compiling Addons, should probably deserve to be added before.

(I'm not too happy with ccache being ON by default, but that is the current behaviour so this description is correct).

I added a missing install section.

@petrasovaa

Copy link
Copy Markdown
Contributor Author

I've been using 'ninja' locally with CMakePresets.json

I added a mention of ninja in the documentation. But I am not sure what to do with this, do you want to suggest an advanced section? Or is this something that should be in AGENTS.md?

@nilason nilason left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

(There is a wiki page https://grasswiki.osgeo.org/wiki/Compile_and_Install_With_CMake, which could be added as a link. It needs however an update.)

@petrasovaa
petrasovaa merged commit 4229ab8 into OSGeo:main Aug 6, 2026
22 checks passed
@petrasovaa
petrasovaa deleted the cmake-compilation-doc branch August 6, 2026 07:41
@github-actions github-actions Bot added this to the 8.6.0 milestone Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs markdown Related to markdown, markdown files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants