diff --git a/CMakeLists.txt b/CMakeLists.txt index 97beef5f022..ee855739861 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/GRASSConfig.cmake.in b/GRASSConfig.cmake.in index d7fb0379d47..153f0c05b7d 100644 --- a/GRASSConfig.cmake.in +++ b/GRASSConfig.cmake.in @@ -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@) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt new file mode 100644 index 00000000000..87901fe5a05 --- /dev/null +++ b/cmake/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(find_scripts) +add_subdirectory(modules) diff --git a/cmake/find_scripts/CMakeLists.txt b/cmake/find_scripts/CMakeLists.txt new file mode 100644 index 00000000000..512583d4169 --- /dev/null +++ b/cmake/find_scripts/CMakeLists.txt @@ -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) diff --git a/cmake/modules/CMakeLists.txt b/cmake/modules/CMakeLists.txt new file mode 100644 index 00000000000..f75ac3e6140 --- /dev/null +++ b/cmake/modules/CMakeLists.txt @@ -0,0 +1,5 @@ +install( + FILES + build_addon.cmake + DESTINATION + ${GRASS_INSTALL_ETCDIR}/cmake) diff --git a/cmake/modules/GRASSInstallDirs.cmake b/cmake/modules/GRASSInstallDirs.cmake index c12d2d2b2c5..796cf0a904a 100644 --- a/cmake/modules/GRASSInstallDirs.cmake +++ b/cmake/modules/GRASSInstallDirs.cmake @@ -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}") @@ -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}) diff --git a/cmake/modules/build_addon.cmake b/cmake/modules/build_addon.cmake new file mode 100644 index 00000000000..61a7f5720bc --- /dev/null +++ b/cmake/modules/build_addon.cmake @@ -0,0 +1,319 @@ +#[[ +AUTHOR(S): Nicklas Larsson +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( + [SOURCES ...] + [DOCFILES ...] + [GRASSLIBS ...] + [DEPENDS ...] + [ADDONS ...] + [NO_DOCS]) + + Build an Addon named `` with the sourcefiles `` + and documentation files ``. 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 ] + [PYTHONPATH ] + [IS_SCRIPT] + + + add_files_to_etc_dir( FILES ...) + + Add `files` destined to the GRASS Addon's `etc/` directory. +#]=======================================================================] + +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) + +if(WIN32) + set(null_device nul) + set(search_command findstr /v) + set(html_search_str "\"\|\| \"") + set(sep "\;") + string(REPLACE ";" "${sep}" env_path "${env_path}") +else() + set(null_device /dev/null) + set(search_command grep -v) + set(html_search_str "\'\|\| \'") + 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() diff --git a/include/Make/Install.make b/include/Make/Install.make index 1dbd837597a..16f9b3d6123 100644 --- a/include/Make/Install.make +++ b/include/Make/Install.make @@ -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)#' \ diff --git a/python/grass/app/Makefile b/python/grass/app/Makefile index e63287710d2..ee259ace56b 100644 --- a/python/grass/app/Makefile +++ b/python/grass/app/Makefile @@ -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)#' \ diff --git a/python/grass/app/resource_paths.py b/python/grass/app/resource_paths.py index df4721d5724..ff80dea14dc 100644 --- a/python/grass/app/resource_paths.py +++ b/python/grass/app/resource_paths.py @@ -21,3 +21,5 @@ GRASS_PREFIX = "@GRASS_PREFIX@" GISBASE = "@GISBASE_INSTALL_PATH@" +GRASS_CMAKE_CONFIG = "@GRASS_INSTALL_CMAKECONFDIR@" +GRASS_CMAKE_MODULES = "@GRASS_INSTALL_CMAKEMODULEDIR@" diff --git a/python/grass/app/runtime.py b/python/grass/app/runtime.py index 2425aa1a9ce..4bb61b164dc 100644 --- a/python/grass/app/runtime.py +++ b/python/grass/app/runtime.py @@ -94,6 +94,26 @@ def grass_version_git(self): def config_projshare(self): return self.env.get("GRASS_PROJSHARE", resource_paths.CONFIG_PROJSHARE) + @property + def grass_cmake_config_dir(self): + return ( + Path(self.prefix, resource_paths.GRASS_CMAKE_CONFIG) + if self.is_cmake_build + else "" + ) + + @property + def grass_cmake_module_dir(self): + return ( + Path(self.prefix, resource_paths.GRASS_CMAKE_MODULES) + if self.is_cmake_build + else "" + ) + + @property + def is_cmake_build(self): + return resource_paths.GRASS_CMAKE_MODULES != "" + @property def prefix(self): if self._custom_prefix: diff --git a/scripts/g.extension/g.extension.py b/scripts/g.extension/g.extension.py old mode 100644 new mode 100755 index 3c5e75c6001..dc45642c50e --- a/scripts/g.extension/g.extension.py +++ b/scripts/g.extension/g.extension.py @@ -173,6 +173,7 @@ import grass.script as gs from grass.script import task as gtask from grass.script.utils import try_rmdir +from grass.app.runtime import RuntimePaths # temp dir REMOVE_TMPDIR = True @@ -1970,9 +1971,12 @@ def create_md_if_missing(root_dir): def install_extension_std_platforms(name, source, url, branch): """Install extension on standard platforms""" - gisbase = os.getenv("GISBASE") + runtime_paths = RuntimePaths() + gisbase = runtime_paths.gisbase path_to_src_code_message = _("Path to the source code:") + is_cmake = runtime_paths.is_cmake_build + # to hide non-error messages from subprocesses outdev = open(os.devnull, "w") if gs.verbosity() <= 2 else sys.stdout @@ -1995,22 +1999,42 @@ def install_extension_std_platforms(name, source, url, branch): ) # collect module names module_list = [] - for r, d, f in os.walk(srcdir): - for filename in f: - if filename == "Makefile": - # get the module name: PGM = - with open(os.path.join(r, "Makefile")) as fp: - for line in fp: - if re.match(r"PGM.*.=|PGM=", line): - try: - modulename = line.split("=")[1].strip() - if modulename: - if modulename not in module_list: - module_list.append(modulename) - else: + + if is_cmake: + for r, d, f in os.walk(srcdir): + for filename in f: + if filename == "CMakeLists.txt": + # get the module name: project() + with open(os.path.join(r, "CMakeLists.txt")) as fp: + for line in fp: + m = re.match(r"project\((.*)\)", line) + if m: + try: + modulename = m.group(1) + if modulename: + if modulename not in module_list: + module_list.append(modulename) + else: + gs.fatal(pgm_not_found_message) + except IndexError: + gs.fatal(pgm_not_found_message) + else: + for r, d, f in os.walk(srcdir): + for filename in f: + if filename == "Makefile": + # get the module name: PGM = + with open(os.path.join(r, "Makefile")) as fp: + for line in fp: + if re.match(r"PGM.*.=|PGM=", line): + try: + modulename = line.split("=")[1].strip() + if modulename: + if modulename not in module_list: + module_list.append(modulename) + else: + gs.fatal(pgm_not_found_message) + except IndexError: gs.fatal(pgm_not_found_message) - except IndexError: - gs.fatal(pgm_not_found_message) # change shebang from python to python3 pyfiles = [] @@ -2028,42 +2052,82 @@ def install_extension_std_platforms(name, source, url, branch): end="", ) - dirs = { - "bin": os.path.join(srcdir, "bin"), - "docs": os.path.join(srcdir, "docs"), - "html": os.path.join(srcdir, "docs", "html"), - "mkdocs": os.path.join(srcdir, "docs", "mkdocs"), - "rest": os.path.join(srcdir, "docs", "rest"), - "man": os.path.join(srcdir, "docs", "man"), - "script": os.path.join(srcdir, "scripts"), - # TODO: handle locales also for addons - # 'string' : os.path.join(srcdir, 'locale'), - "string": srcdir, - "etc": os.path.join(srcdir, "etc"), - } - - make_cmd = [ - MAKE, - "MODULE_TOPDIR=%s" % gisbase.replace(" ", r"\ "), - "RUN_GISRC=%s" % os.environ["GISRC"], - "BIN=%s" % dirs["bin"], - "HTMLDIR=%s" % dirs["html"], - "MDDIR=%s" % dirs["mkdocs"], - "RESTDIR=%s" % dirs["rest"], - "MANBASEDIR=%s" % dirs["man"], - "SCRIPTDIR=%s" % dirs["script"], - "STRINGDIR=%s" % dirs["string"], - "ETC=%s" % os.path.join(dirs["etc"]), - "SOURCE_URL=%s" % url, - ] - - install_cmd = [ - MAKE, - "MODULE_TOPDIR=%s" % gisbase, - "ARCH_DISTDIR=%s" % srcdir, - "INST_DIR=%s" % options["prefix"], - "install", - ] + if is_cmake: + grass_addon_base = os.getenv("GRASS_ADDON_BASE") + cmake_prefix_path = ( + ";" + os.getenv("CMAKE_PREFIX_PATH") + if os.getenv("CMAKE_PREFIX_PATH") + else "" + ) + cmake_module_path = ( + ";" + os.getenv("CMAKE_MODULE_PATH") + if os.getenv("CMAKE_MODULE_PATH") + else "" + ) + g_cmake_config_dir = runtime_paths.grass_cmake_config_dir + g_cmake_module_dir = runtime_paths.grass_cmake_module_dir + + c_prefix_path = f"{g_cmake_config_dir}{cmake_prefix_path}" + c_mod_path = f"{g_cmake_module_dir}{cmake_module_path}" + config_cmd = [ + "cmake", + "-B", + "build", + f"-DCMAKE_PREFIX_PATH={c_prefix_path}", + f"-DCMAKE_MODULE_PATH={c_mod_path}", + f"-DCMAKE_INSTALL_PREFIX={grass_addon_base}", + f"-DPYTHON_EXECUTABLE={sys.executable}", + ] + make_cmd = [ + "cmake", + "--build", + "build", + "-v", + ] + install_cmd = [ + "cmake", + "--install", + "build", + ] + try: + shutil.rmtree(os.path.join(srcdir, "build")) + except FileNotFoundError: + pass + else: + dirs = { + "bin": os.path.join(srcdir, "bin"), + "docs": os.path.join(srcdir, "docs"), + "html": os.path.join(srcdir, "docs", "html"), + "mkdocs": os.path.join(srcdir, "docs", "mkdocs"), + "rest": os.path.join(srcdir, "docs", "rest"), + "man": os.path.join(srcdir, "docs", "man"), + "script": os.path.join(srcdir, "scripts"), + # TODO: handle locales also for addons + # 'string' : os.path.join(srcdir, 'locale'), + "string": srcdir, + "etc": os.path.join(srcdir, "etc"), + } + make_cmd = [ + MAKE, + "MODULE_TOPDIR=%s" % gisbase.replace(" ", r"\ "), + "RUN_GISRC=%s" % os.environ["GISRC"], + "BIN=%s" % dirs["bin"], + "HTMLDIR=%s" % dirs["html"], + "MDDIR=%s" % dirs["mkdocs"], + "RESTDIR=%s" % dirs["rest"], + "MANBASEDIR=%s" % dirs["man"], + "SCRIPTDIR=%s" % dirs["script"], + "STRINGDIR=%s" % dirs["string"], + "ETC=%s" % os.path.join(dirs["etc"]), + "SOURCE_URL=%s" % url, + ] + install_cmd = [ + MAKE, + "MODULE_TOPDIR=%s" % gisbase, + "ARCH_DISTDIR=%s" % srcdir, + "INST_DIR=%s" % options["prefix"], + "install", + ] if flags["d"]: gs.message("\n%s\n" % _("To compile run:")) @@ -2077,9 +2141,12 @@ def install_extension_std_platforms(name, source, url, branch): os.chdir(srcdir) gs.message(_("Compiling...")) - if not Path(gisbase, "include", "Make", "Module.make").exists(): + if not is_cmake and not Path(gisbase, "include", "Make", "Module.make").exists(): gs.fatal(_("Please install GRASS development package")) + if is_cmake and gs.call(config_cmd, stdout=outdev) != 0: + gs.fatal(_("Compilation failed, sorry. Please check above error messages.")) + if gs.call(make_cmd, stdout=outdev) != 0: gs.fatal(_("Compilation failed, sorry. Please check above error messages."))