Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ if(WITH_X11)
build_program_in_subdir(visualization/ximgview DEPENDS grass_gis X11::X11)
endif()

add_subdirectory(cmake)
include(CMakePackageConfigHelpers)
configure_package_config_file(GRASSConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/GRASSConfig.cmake
INSTALL_DESTINATION ${GRASS_INSTALL_CMAKECONFDIR}/GRASS)
Expand Down
6 changes: 6 additions & 0 deletions GRASSConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,9 @@ set(GRASS_VERSION_GIT "@GRASS_VERSION_GIT@")
set(GRASS_VERSION_STRING "@GRASS_VERSION_NUMBER@")
set(GRASS_CMAKE_DIR "@CMAKE_INSTALL_PREFIX@/@GRASS_INSTALL_ETCDIR@/cmake")
set(GRASS_UTILS_DIR "@CMAKE_INSTALL_PREFIX@/@GRASS_INSTALL_UTILSDIR@")

set(GRASS_GDAL_INCLUDE_DIR "@GDAL_INCLUDE_DIR@")
set(GRASS_PROJ_INCLUDE_DIRS "@PROJ_INCLUDE_DIRS@")
set(GRASS_ZLIB_INCLUDE_DIR "@ZLIB_INCLUDE_DIR@")
set(GRASS_PostgreSQL_INCLUDE_DIR "@PostgreSQL_INCLUDE_DIR@")
set(GRASS_WITH_POSTGRES @WITH_POSTGRES@)
2 changes: 2 additions & 0 deletions cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
add_subdirectory(find_scripts)
add_subdirectory(modules)
16 changes: 16 additions & 0 deletions cmake/find_scripts/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
install(
FILES
FindCairo.cmake
FindCBLAS.cmake
FindFFTW.cmake
FindGEOS.cmake
FindLAPACKE.cmake
FindLibLAS.cmake
FindMySQL.cmake
FindNetCDF.cmake
FindPCRE.cmake
FindPROJ.cmake
FindReadline.cmake
Findzstd.cmake
DESTINATION
${GRASS_INSTALL_ETCDIR}/cmake)
5 changes: 5 additions & 0 deletions cmake/modules/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
install(
FILES
build_addon.cmake
DESTINATION
${GRASS_INSTALL_ETCDIR}/cmake)
2 changes: 2 additions & 0 deletions cmake/modules/GRASSInstallDirs.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ else()
endif()

set(GRASS_INSTALL_CMAKECONFDIR "${CMAKE_INSTALL_LIBDIR}/cmake")
set(GRASS_INSTALL_CMAKEMODULEDIR "${GRASS_INSTALL_ETCDIR}/cmake")

message(STATUS "GISBASE_DIR ${GISBASE_DIR}")
message(STATUS "GRASS_INSTALL_BINDIR ${GRASS_INSTALL_BINDIR}")
Expand All @@ -79,6 +80,7 @@ message(STATUS "GRASS_INSTALL_MISCDIR ${GRASS_INSTALL_MISCDIR}")
message(STATUS "GRASS_INSTALL_MAKEFILEDIR ${GRASS_INSTALL_MAKEFILEDIR}")
message(STATUS "GRASS_INSTALL_LOCALEDIR ${GRASS_INSTALL_LOCALEDIR}")
message(STATUS "GRASS_INSTALL_CMAKECONFDIR ${GRASS_INSTALL_CMAKECONFDIR}")
message(STATUS "GRASS_INSTALL_CMAKEMODULEDIR ${GRASS_INSTALL_CMAKEMODULEDIR}")

set(OUTDIR "${CMAKE_BINARY_DIR}/output")
set(GISBASE ${CMAKE_INSTALL_PREFIX}/${GISBASE_DIR})
Expand Down
319 changes: 319 additions & 0 deletions cmake/modules/build_addon.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
#[[
AUTHOR(S): Nicklas Larsson <n_larsson yahoo.com>
PURPOSE: CMake module to build GRASS Addons
COPYRIGHT: (C) 2025 by the GRASS Development Team
SPDX-License-Identifier: GPL-2.0-or-later
]]

#[=======================================================================[.rst:

build_addon
-----------

Build GRASS Addon

build_addon(<modulename>
[SOURCES <sources>...]
[DOCFILES <docfiles>...]
[GRASSLIBS <grass-libraries>...]
[DEPENDS <dependency-targets>...]
[ADDONS <sub-addons>...]
[NO_DOCS])

Build an Addon named `<modulename>` with the sourcefiles `<sources>`
and documentation files `<docfiles>`. For compiled sources GRASS
library dependencies are added to `GRASSLIBS`, e.g. `gis raster datetime`
(see the file `GRASSConfig.cmake.in` for supported components). Other
external dependencies can be added to `DEPENDS` in form of a list of
targets.

Wrapper addons, which installs a group of sub-addons, includes each by
adding a list to `ADDONS`. `NO_DOCS` is an optional for, often, wrapper
addons, which do not have separate documentation.

Internally used arguments during recursive calls, listed here for reference:
[SRC_DIR <source-dir>]
[PYTHONPATH <python-path>]
[IS_SCRIPT]


add_files_to_etc_dir(<dir> FILES <files>...)

Add `files` destined to the GRASS Addon's `etc/<dir>` directory.
#]=======================================================================]

set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)

if(WIN32)
set(null_device nul)
set(search_command findstr /v)
set(html_search_str "\"</body>\|</html>\|</div> <!-- end container -->\"")
set(sep "\;")
string(REPLACE ";" "${sep}" env_path "${env_path}")
else()
set(null_device /dev/null)
set(search_command grep -v)
set(html_search_str "\'</body>\|</html>\|</div> <!-- end container -->\'")
set(sep ":")
endif()

set(GRASSAddon_BinDIR bin)
set(GRASSAddon_DocDIR docs/html)
set(GRASSAddon_ETCDIR etc)
set(GRASSAddon_LibDIR lib)
set(GRASSAddon_ScriptDIR scripts)

set(OUTDIR "${CMAKE_BINARY_DIR}/output")
file(MAKE_DIRECTORY "${OUTDIR}/${GRASSAddon_BinDIR}")
file(MAKE_DIRECTORY "${OUTDIR}/${GRASSAddon_DocDIR}")
file(MAKE_DIRECTORY "${OUTDIR}/${GRASSAddon_ETCDIR}")
file(MAKE_DIRECTORY "${OUTDIR}/${GRASSAddon_LibDIR}")
file(MAKE_DIRECTORY "${OUTDIR}/${GRASSAddon_ScriptDIR}")

function(add_files_to_etc_dir dir)
cmake_parse_arguments(PARSE_ARGV 1 G "" "" "FILES")
file(MAKE_DIRECTORY ${OUTDIR}/${GRASSAddon_ETCDIR}/${dir})
file(COPY ${G_FILES} DESTINATION ${OUTDIR}/${GRASSAddon_ETCDIR}/${dir})
install(FILES ${G_FILES} DESTINATION ${GRASSAddon_ETCDIR}/${dir})
endfunction()

function(build_addon name)
cmake_parse_arguments(PARSE_ARGV 1 G "NO_DOCS;IS_SCRIPT" "SRC_DIR;PYTHONPATH"
"SOURCES;ADDONS;DEPENDS;DOCFILES;GRASSLIBS")
set(_src_dir ${CMAKE_CURRENT_SOURCE_DIR})
set(G_NAME ${name})
find_package(GRASS REQUIRED)

set(MKHTML_PY ${GRASS_UTILS_DIR}/mkhtml.py)
if(NOT EXISTS ${MKHTML_PY})
message(FATAL_ERROR "NO MKHTML_PY FOUND")
endif()

if(G_ADDONS)
set(_pythonpath
"PYTHONPATH=${OUTDIR}/${GRASSAddon_ETCDIR}/${G_NAME}:$ENV{PYTHONPATH}")

if(NOT G_NO_DOCS)
set(_tmp_html_file ${CMAKE_CURRENT_BINARY_DIR}/${G_NAME}.tmp.html)
set(_out_html_file ${OUTDIR}/${GRASSAddon_DocDIR}/${G_NAME}.html)
add_custom_command(
OUTPUT ${_out_html_file}
COMMAND
${_pythonpath} ${CMAKE_COMMAND} -E copy ${_src_dir}/${G_NAME}.html
${CMAKE_CURRENT_BINARY_DIR}/${G_NAME}.html
COMMAND ${_pythonpath} MODULE_TOPDIR=$ENV{GISBASE} ${PYTHON_EXECUTABLE}
${MKHTML_PY} ${G_NAME} > ${_out_html_file}
COMMAND ${CMAKE_COMMAND} -E remove ${_tmp_html_file}
${CMAKE_CURRENT_BINARY_DIR}/${G_NAME}.html
COMMENT
"Creating ${OUT_HTML_FILE} ${OUTDIR}/${GRASSAddon_ETCDIR}/${G_NAME} ${CMAKE_COMMAND}"
)

add_custom_target(${G_NAME}-docs ALL DEPENDS ${_out_html_file})

install(FILES ${_out_html_file} DESTINATION ${GRASSAddon_DocDIR})
endif()
foreach(addon IN ITEMS ${G_ADDONS})
add_subdirectory(${addon})
endforeach()
return()
endif()

set(_py_files ${G_SOURCES})
list(FILTER _py_files INCLUDE REGEX ".*\.py$")
list(LENGTH _py_files _n_py_files)
if(_n_py_files)
set(_is_script ON)
else()
set(_is_script OFF)
endif()
unset(_py_files)
unset(_n_py_files)

if(_is_script)
set(install_dest ${GRASSAddon_ScriptDIR})
set(PYTHON_EXECUTABLE $ENV{PYTHON_EXECUTABLE})
_build_addon(
NAME
${G_NAME}
${ARGN}
SRC_DIR
${_src_dir}
IS_SCRIPT
PYTHONPATH
${_pythonpath})
else()
_set_thirdparty_include_paths()
set(install_dest "${GRASSAddon_BinDIR}")
_build_addon(NAME ${G_NAME} ${ARGN} SRC_DIR ${_src_dir})
endif()
endfunction()

function(_build_addon)
cmake_parse_arguments(G "NO_DOCS;IS_SCRIPT" "NAME;SRC_DIR;PYTHONPATH"
"SOURCES;DEPENDS;GRASSLIBS;DOCFILES" ${ARGN})

find_package(GRASS REQUIRED)

if(G_IS_SCRIPT)
set(SRC_SCRIPT_FILE ${G_NAME}.py)
set(_execute ${PYTHON_EXECUTABLE})
list(FIND G_SOURCES "${SRC_SCRIPT_FILE}" has_script_file)
if(has_script_file LESS "0" OR NOT EXISTS ${G_SRC_DIR}/${SRC_SCRIPT_FILE})
message(FATAL_ERROR "${SRC_SCRIPT_FILE} does not exists")
endif()

list(TRANSFORM G_SOURCES PREPEND "${G_SRC_DIR}/")

set(SCRIPT_EXT "")
if(WIN32)
set(SCRIPT_EXT ".py")
endif()

configure_file(
${G_SRC_DIR}/${G_NAME}.py
${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${G_NAME}${SCRIPT_EXT} COPYONLY)
file(
COPY ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${G_NAME}${SCRIPT_EXT}
DESTINATION ${OUTDIR}/${GRASSAddon_ScriptDIR}
FILE_PERMISSIONS
OWNER_READ
OWNER_WRITE
OWNER_EXECUTE
GROUP_READ
GROUP_EXECUTE
WORLD_READ
WORLD_EXECUTE)

add_custom_target(${G_NAME} ALL)

if(WIN32)
install(PROGRAMS ${OUTDIR}/${GRASSAddon_ScriptDIR}/${G_NAME}.bat
DESTINATION ${GRASSAddon_ScriptDIR})
endif()

install(PROGRAMS ${OUTDIR}/${GRASSAddon_ScriptDIR}/${G_NAME}${SCRIPT_EXT}
DESTINATION ${GRASSAddon_ScriptDIR})

else()
find_package(GRASS REQUIRED COMPONENTS ${G_GRASSLIBS})

set(CMAKE_INSTALL_RPATH $ENV{GISBASE}/lib)

list(TRANSFORM G_GRASSLIBS PREPEND "GRASS::")

set(G_RUNTIME_OUTPUT_DIR "${OUTDIR}/${GRASSAddon_BinDIR}")
if(MSVC)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${G_RUNTIME_OUTPUT_DIR}>)
elseif(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${G_RUNTIME_OUTPUT_DIR})
endif()

add_executable(${G_NAME} ${G_SOURCES})
add_dependencies(${G_NAME} ${G_GRASSLIBS} ${G_DEPENDS})

foreach(G_DEPEND ${G_DEPENDS})
if(NOT TARGET ${G_DEPEND})
message(FATAL_ERROR "${G_DEPEND} not a target")
endif()
add_dependencies(${G_NAME} ${G_DEPEND})
endforeach()

target_link_libraries(${G_NAME} ${G_GRASSLIBS} ${G_DEPENDS})

install(TARGETS ${G_NAME} DESTINATION ${install_dest})
endif()

if(NOT G_NO_DOCS)
list(TRANSFORM G_DOCFILES PREPEND "${G_SRC_DIR}/")

set(html_doc ${G_DOCFILES})
set(md_doc ${G_DOCFILES})
set(img_files ${G_DOCFILES})

list(FILTER html_doc INCLUDE REGEX "${G_NAME}.html$")
list(FILTER md_doc INCLUDE REGEX "${G_NAME}.md$")
list(FILTER img_files INCLUDE REGEX ".*\.(jpg|png)$")

if(img_files)
set(copy_images_command ${CMAKE_COMMAND} -E copy ${img_files}
${OUTDIR}/${GRASSAddon_DocDIR})
install(FILES ${img_files} DESTINATION ${GRASSAddon_DocDIR})
endif()

set(_tmp_html_file ${CMAKE_CURRENT_BINARY_DIR}/${G_NAME}.tmp.html)
set(_out_html_file ${OUTDIR}/${GRASSAddon_DocDIR}/${G_NAME}.html)

if(EXISTS ${html_doc})
install(FILES ${_out_html_file} DESTINATION ${GRASSAddon_DocDIR})
else()
message(
FATAL_ERROR
"${html_doc} does not exists. ${G_SRC_DIR} \n ${OUTDIR}/${GRASSAddon_DocDIR}| ${G_NAME}"
)
endif()

add_custom_command(
OUTPUT ${_out_html_file}
COMMAND ${CMAKE_COMMAND} -E copy ${G_SRC_DIR}/${G_NAME}.html
${CMAKE_CURRENT_BINARY_DIR}/${G_NAME}.html
COMMAND
${G_PYTHONPATH} GISBASE=$ENV{GISBASE} GISRC=$ENV{GISRC}
VERSION_NUMBER=${GRASS_VERSION_STRING}
VERSION_DATE=${GRASS_VERSION_DATE} SOURCE_URL=$ENV{SOURCE_URL}
${_execute} ${OUTDIR}/${install_dest}/${G_NAME}${SCRIPT_EXT}
--html-description < ${null_device} | ${search_command}
${html_search_str} > ${_tmp_html_file}
COMMAND MODULE_TOPDIR=$ENV{GISBASE} ${PYTHON_EXECUTABLE} ${MKHTML_PY}
${G_NAME} > ${_out_html_file}
COMMAND ${copy_images_command}
COMMAND ${CMAKE_COMMAND} -E remove ${_tmp_html_file}
${CMAKE_CURRENT_BINARY_DIR}/${G_NAME}.html
COMMENT "Creating ${OUT_HTML_FILE}")

add_custom_target(${G_NAME}-docs ALL DEPENDS ${G_NAME} ${_out_html_file})

install(FILES ${_out_html_file} DESTINATION ${GRASSAddon_DocDIR})
endif()
endfunction()

# Set Third-party API include paths exposed by GRASS API
macro(_set_thirdparty_include_paths)
if(NOT TARGET GDAL::GDAL)
find_package(GDAL)
if(NOT GDAL_FOUND)
add_library(GDAL::GDAL INTERFACE)
set_target_properties(GDAL::GDAL PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${GRASS_GDAL_INCLUDE_DIR})
endif()
endif()
if(NOT TARGET PROJ::proj)
find_package(PROJ QUIET)
if(NOT PROJ_FOUND)
add_library(PROJ::proj INTERFACE)
set_target_properties(PROJ::proj PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${GRASS_PROJ_INCLUDE_DIRS})
endif()
endif()
if(NOT TARGET ZLIB::ZLIB)
find_package(ZLIB)
if(NOT ZLIB_FOUND)
add_library(ZLIB::ZLIB INTERFACE)
set_target_properties(ZLIB::ZLIB PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${GRASS_ZLIB_INCLUDE_DIR})
endif()
endif()
if(GRASS_WITH_POSTGRES)
if(NOT TARGET PostgreSQL::PostgreSQL)
if(NOT PostgreSQL_ADDITIONAL_VERSIONS)
set(PostgreSQL_ADDITIONAL_VERSIONS "18" "17" "16" "15" "14" "13")
endif()
find_package(PostgreSQL)
if(NOT PostgreSQL_FOUND)
add_library(PostgreSQL::PostgreSQL INTERFACE)
set_target_properties(PostgreSQL::PostgreSQL PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${GRASS_PostgreSQL_INCLUDE_DIR})
endif()
endif()
endif()
endmacro()
2 changes: 2 additions & 0 deletions include/Make/Install.make
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ $(DESTDIR)$(INST_DIR)/$(RESOURCE_PATHS): $(ARCH_DISTDIR)/resource_paths.py
sed \
-e 's#'@CONFIG_PROJSHARE@'#$(PROJSHARE)#' \
-e 's#'@GISBASE_INSTALL_PATH@'##' \
-e 's#'@GRASS_INSTALL_CMAKECONFDIR@'##' \
-e 's#'@GRASS_INSTALL_CMAKEMODULEDIR@'##' \
-e 's#'@GRASS_PREFIX@'#$(INST_DIR)#' \
-e 's#'@GRASS_VERSION_GIT@'#$(GRASS_VERSION_GIT)#' \
-e 's#'@GRASS_VERSION_MAJOR@'#$(GRASS_VERSION_MAJOR)#' \
Expand Down
2 changes: 2 additions & 0 deletions python/grass/app/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ $(DSTDIR)/resource_paths.py: resource_paths.py
sed \
-e 's#@CONFIG_PROJSHARE@#$(PROJSHARE)#' \
-e 's#@GISBASE_INSTALL_PATH@##' \
-e 's#@GRASS_INSTALL_CMAKECONFDIR@##' \
-e 's#@GRASS_INSTALL_CMAKEMODULEDIR@##' \
-e 's#@GRASS_PREFIX@#$(RUN_GISBASE)#' \
-e 's#@GRASS_VERSION_GIT@#$(GRASS_VERSION_GIT)#' \
-e 's#@GRASS_VERSION_MAJOR@#$(GRASS_VERSION_MAJOR)#' \
Expand Down
Loading
Loading