From b8e081fc0a2d90bf4c65d077a4087e4de6c20c2a Mon Sep 17 00:00:00 2001 From: Nicklas Larsson Date: Wed, 26 Mar 2025 11:43:47 +0100 Subject: [PATCH 01/11] build: new specific env vars for FHS, replacing GISBASE --- CMakeLists.txt | 49 ++++++++++----- cmake/modules/GRASSInstallDirs.cmake | 41 ++++++++++-- display/CMakeLists.txt | 26 +++++--- display/d.linegraph/main.c | 4 +- display/d.mon/start.c | 12 ++-- display/d.vect.thematic/main.c | 4 +- display/d.vect/main.c | 4 +- doc/examples/gui/wxpython/frame.py | 2 +- doc/examples/gui/wxpython/g.gui.example.py | 2 +- general/g.gui/main.c | 8 ++- general/g.list/main.c | 2 +- general/g.mapset/main.c | 4 +- general/g.mapsets/main.c | 10 ++- general/g.mkfontcap/Makefile | 3 +- general/g.mkfontcap/main.c | 3 +- general/g.mkfontcap/stroke_fonts.c | 5 +- general/g.setproj/get_stp.c | 4 +- general/g.setproj/local_proto.h | 4 +- general/g.setproj/proj.c | 6 +- gui/scripts/d.wms.py | 2 +- gui/wxpython/core/globalvar.py | 11 ++-- gui/wxpython/core/settings.py | 2 +- gui/wxpython/core/toolboxes.py | 2 +- gui/wxpython/gui_core/ghelp.py | 20 +++--- gui/wxpython/gui_core/widgets.py | 8 +-- gui/wxpython/image2target/ii2t_gis_set.py | 6 +- gui/wxpython/lmgr/frame.py | 5 +- gui/wxpython/main_window/frame.py | 5 +- gui/wxpython/psmap/dialogs.py | 10 +-- gui/wxpython/web_services/cap_interface.py | 2 +- gui/wxpython/web_services/widgets.py | 2 +- include/Make/Install.make | 17 ++++- include/Make/Rules.make | 11 ++++ include/grass/defs/gis.h | 7 +++ include/grass/gprojects.h | 8 +-- lib/driver/font2.c | 4 +- lib/driver/parse_ftcap.c | 2 +- lib/gis/color_rules.c | 7 +-- lib/gis/datum.c | 4 +- lib/gis/find_etc.c | 2 +- lib/gis/get_ellipse.c | 2 +- lib/gis/is.c | 2 +- lib/gis/locale.c | 9 +-- lib/gis/parser.c | 10 ++- lib/gis/resource_dirs.c | 53 ++++++++++++++++ lib/init/Makefile | 13 +--- lib/init/grass.py | 6 +- lib/manage/read_list.c | 2 +- lib/proj/convert.c | 13 ++-- lib/proj/datum.c | 4 +- lib/proj/ellipse.c | 2 +- lib/psdriver/graph_set.c | 3 +- lib/raster/color_rules.c | 2 +- lib/symbol/read.c | 2 +- locale/Makefile | 2 +- locale/grass_po_stats.py | 2 +- man/Makefile | 21 +++++-- man/build_html.py | 2 +- man/build_md.py | 2 +- man/build_rest.py | 4 +- ps/ps.map/makeprocs.c | 2 +- python/grass/__init__.py | 3 +- python/grass/app/Makefile | 12 ++++ python/grass/app/data.py | 11 ++-- python/grass/app/resource_paths.py | 13 ++++ python/grass/app/runtime.py | 63 +++++++++++++++++++ python/grass/docs/conf.py | 40 ++++-------- python/grass/script/setup.py | 22 ++++--- python/grass/script/task.py | 4 +- python/grass/script/utils.py | 4 +- python/grass/semantic_label/reader.py | 2 +- python/grass/temporal/core.py | 4 +- .../CMakeLists.txt | 1 - raster/r.colors/edit_colors.c | 4 +- raster/r.watershed/front/main.c | 2 +- raster/r.watershed/shed/com_line.c | 10 +-- scripts/d.polar/d.polar.py | 2 +- scripts/db.test/db.test.py | 2 +- scripts/g.extension/g.extension.py | 2 +- scripts/g.search.modules/g.search.modules.py | 6 +- scripts/wxpyimgview/wxpyimgview.py | 3 +- utils/thumbnails.py | 2 +- vector/v.label.sa/font.c | 2 +- 83 files changed, 454 insertions(+), 248 deletions(-) mode change 100644 => 100755 doc/examples/gui/wxpython/g.gui.example.py mode change 100644 => 100755 gui/scripts/d.wms.py create mode 100644 lib/gis/resource_dirs.c mode change 100644 => 100755 locale/grass_po_stats.py mode change 100644 => 100755 man/build_rest.py mode change 100644 => 100755 scripts/g.extension/g.extension.py mode change 100644 => 100755 scripts/wxpyimgview/wxpyimgview.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 1327577f0f6..d4d454a6551 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,18 +184,9 @@ set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) if(WITH_FHS) file(CREATE_LINK ${OUTDIR}/${GRASS_INSTALL_DEMODIR} ${RUNTIME_GISBASE}/demolocation SYMBOLIC) - file(CREATE_LINK ${OUTDIR}/${GRASS_INSTALL_FONTSDIR} ${RUNTIME_GISBASE}/fonts - SYMBOLIC) - file(CREATE_LINK ${OUTDIR}/${GRASS_INSTALL_ETCDIR}/colors - ${RUNTIME_GISBASE}/etc/colors SYMBOLIC) - file(CREATE_LINK ${OUTDIR}/${GRASS_INSTALL_ETCDIR}/colors.desc - ${RUNTIME_GISBASE}/etc/colors.desc SYMBOLIC) - file(CREATE_LINK ${OUTDIR}/${GRASS_INSTALL_ETCDIR}/element_list - ${RUNTIME_GISBASE}/etc/element_list SYMBOLIC) - file(CREATE_LINK ${OUTDIR}/${GRASS_INSTALL_ETCDIR}/renamed_options - ${RUNTIME_GISBASE}/etc/renamed_options SYMBOLIC) - file(CREATE_LINK ${OUTDIR}/${GRASS_INSTALL_ETCDIR}/VERSIONNUMBER - ${RUNTIME_GISBASE}/etc/VERSIONNUMBER SYMBOLIC) + file(MAKE_DIRECTORY "${RUNTIME_GISBASE}/gui/wxpython") + file(CREATE_LINK ${OUTDIR}/${GRASS_INSTALL_GUIDIR}/wxpython/xml + ${RUNTIME_GISBASE}/gui/wxpython/xml SYMBOLIC) endif() include(set_compiler_flags) @@ -233,6 +224,16 @@ if(WIN32) ${CMAKE_COMMAND} -E env "PATH=${BIN_DIR}${sep}${SCRIPTS_DIR}${sep}${env_path}${sep}${LIB_DIR}" "PYTHONPATH=${ETC_PYTHON_DIR}${sep}${GUI_WXPYTHON_DIR}${sep}$ENV{PYTHONPATH}" "GRASS_PYTHON=${PYTHON_EXECUTABLE}" "GISBASE=${RUN_GISBASE_NATIVE}" "GISRC=${GISRC}" "LC_ALL=C" "LANG=C" + "GRASS_COLORSDIR=${RUNTIME_GRASS_COLORSDIR}" + "GRASS_DOCDIR=${RUNTIME_GRASS_DOCDIR}" + "GRASS_ETCDIR=${RUNTIME_GRASS_ETCDIR}" + "GRASS_FONTSDIR=${RUNTIME_GRASS_FONTSDIR}" + "GRASS_GRAPHICSDIR=${RUNTIME_GRASS_GRAPHICSDIR}" + "GRASS_GUIRESDIR=${RUNTIME_GRASS_GUIRESDIR}" + "GRASS_GUISCRIPTDIR=${RUNTIME_GRASS_GUISCRIPTDIR}" + "GRASS_GUIWXDIR=${GUI_WXPYTHON_DIR}" + "GRASS_LOCALEDIR=${RUNTIME_GRASS_LOCALEDIR}" + "GRASS_MKDOCSDIR=${RUNTIME_GRASS_MKDOCSDIR}" "ARCH=${BUILD_ARCH}" "ARCH_DISTDIR=${RUN_GISBASE_NATIVE}" "LANGUAGE=C" "MODULE_TOPDIR=${MODULE_TOPDIR}" "HTMLDIR=${DOC_DIR}" "LC_ALL=C" "LANG=C" @@ -249,6 +250,16 @@ else() "PYTHONPATH=${ETC_PYTHON_DIR}${sep}${GUI_WXPYTHON_DIR}${sep}$ENV{PYTHONPATH}" "LD_LIBRARY_PATH=${LIB_DIR}${sep}$ENV{LD_LIBRARY_PATH}" "GISBASE=${RUN_GISBASE_NATIVE}" "GISRC=${GISRC}" "LC_ALL=C" "LANG=C" + "GRASS_COLORSDIR=${RUNTIME_GRASS_COLORSDIR}" + "GRASS_DOCDIR=${RUNTIME_GRASS_DOCDIR}" + "GRASS_ETCDIR=${RUNTIME_GRASS_ETCDIR}" + "GRASS_FONTSDIR=${RUNTIME_GRASS_FONTSDIR}" + "GRASS_GRAPHICSDIR=${RUNTIME_GRASS_GRAPHICSDIR}" + "GRASS_GUIRESDIR=${RUNTIME_GRASS_GUIRESDIR}" + "GRASS_GUISCRIPTDIR=${RUNTIME_GRASS_GUISCRIPTDIR}" + "GRASS_GUIWXDIR=${GUI_WXPYTHON_DIR}" + "GRASS_LOCALEDIR=${RUNTIME_GRASS_LOCALEDIR}" + "GRASS_MKDOCSDIR=${RUNTIME_GRASS_MKDOCSDIR}" "ARCH=${BUILD_ARCH}" "ARCH_DISTDIR=${RUN_GISBASE_NATIVE}" "LANGUAGE=C" "MODULE_TOPDIR=${MODULE_TOPDIR}" "HTMLDIR=${DOC_DIR}" "VERSION_NUMBER=\"${GRASS_VERSION_NUMBER}\"" @@ -304,14 +315,18 @@ endif() add_custom_target( r_colors_thumbnails ALL COMMAND ${CMAKE_COMMAND} -E make_directory - ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/colortables + ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/colortables COMMAND ${grass_env_command} ${PYTHON_EXECUTABLE} ${THUMBNAILS_PY} - ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/colortables - BYPRODUCTS ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/colortables + ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/colortables + BYPRODUCTS ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/colortables COMMENT "Creating thumbnails" DEPENDS ALL_MODULES) -install(DIRECTORY ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/colortables - DESTINATION ${GRASS_INSTALL_DOCDIR}) +install(DIRECTORY ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/colortables + DESTINATION ${GRASS_INSTALL_GRAPHICSDIR}) +if(WITH_FHS AND WITH_DOCS) + install(DIRECTORY ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/colortables + DESTINATION ${GRASS_INSTALL_DOCDIR}) +endif() set(misc_files AUTHORS diff --git a/cmake/modules/GRASSInstallDirs.cmake b/cmake/modules/GRASSInstallDirs.cmake index ce40fc03c35..fafbca66626 100644 --- a/cmake/modules/GRASSInstallDirs.cmake +++ b/cmake/modules/GRASSInstallDirs.cmake @@ -22,12 +22,14 @@ if(WITH_FHS) set(GRASS_INSTALL_DEVDOCDIR "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME_LOWER}-dev-doc") set(GRASS_INSTALL_MANDIR "${CMAKE_INSTALL_MANDIR}") - set(GRASS_INSTALL_MKDOCSDIR - "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME_LOWER}-mkdocs") - set(GRASS_INSTALL_DEMODIR "${GRASS_INSTALL_SHAREDIR}/demolocation") + set(GRASS_INSTALL_MKDOCSDIR "${GRASS_INSTALL_SHAREDIR}/mkdocs") set(GRASS_INSTALL_MISCDIR "${GRASS_INSTALL_SHAREDIR}") + set(GRASS_INSTALL_DEMODIR "${GRASS_INSTALL_MISCDIR}/demolocation") set(GRASS_INSTALL_MAKEFILEDIR "${GISBASE_DIR}/Make") set(GRASS_INSTALL_LOCALEDIR "${CMAKE_INSTALL_LOCALEDIR}") + set(GRASS_INSTALL_COLORSDIR "${GRASS_INSTALL_ETCDIR}/colors") + set(GRASS_INSTALL_GRAPHICSDIR "${GRASS_INSTALL_ETCDIR}/graphics") + set(GRASS_INSTALL_GRASS_GUIWXDIR "${GRASS_INSTALL_GUIDIR}/wxpython") else() message("Legacy file structure") set(GISBASE_DIR "${CMAKE_INSTALL_LIBDIR}/grass${GRASS_VERSION_MAJOR}${GRASS_VERSION_MINOR}") @@ -48,10 +50,13 @@ else() set(GRASS_INSTALL_DEVDOCDIR "${GISBASE_DIR}/html") set(GRASS_INSTALL_MANDIR "${GISBASE_DIR}/docs/man") set(GRASS_INSTALL_MKDOCSDIR "${GISBASE_DIR}/docs/mkdocs") - set(GRASS_INSTALL_DEMODIR "${GISBASE_DIR}/demolocation") set(GRASS_INSTALL_MISCDIR "${GISBASE_DIR}") + set(GRASS_INSTALL_DEMODIR "${GRASS_INSTALL_MISCDIR}/demolocation") set(GRASS_INSTALL_MAKEFILEDIR "${GISBASE_DIR}/Make") set(GRASS_INSTALL_LOCALEDIR "${GISBASE_DIR}/locale") + set(GRASS_INSTALL_COLORSDIR "${GRASS_INSTALL_ETCDIR}/colors") + set(GRASS_INSTALL_GRAPHICSDIR "${GRASS_INSTALL_DOCDIR}") + set(GRASS_INSTALL_GRASS_GUIWXDIR "${GRASS_INSTALL_GUIDIR}/wxpython") endif() message(STATUS "GISBASE_DIR ${GISBASE_DIR}") @@ -76,10 +81,38 @@ message(STATUS "GRASS_INSTALL_DEMODIR ${GRASS_INSTALL_DEMODIR}") 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_COLORSDIR ${GRASS_INSTALL_COLORSDIR}") +message(STATUS "GRASS_INSTALL_GRAPHICSDIR ${GRASS_INSTALL_GRAPHICSDIR}") +message(STATUS "GRASS_INSTALL_GRASS_GUIWXDIR ${GRASS_INSTALL_GRASS_GUIWXDIR}") set(OUTDIR "${CMAKE_BINARY_DIR}/output") set(GISBASE ${CMAKE_INSTALL_PREFIX}/${GISBASE_DIR}) + +set(GRASS_LOCALEDIR "${GRASS_INSTALL_LOCALEDIR}") +set(GRASS_GUIRESDIR "${GRASS_INSTALL_GUIDIR}") +set(GRASS_GUISCRIPTDIR "${GRASS_INSTALL_GUISCRIPTDIR}") +set(GRASS_DOCDIR "${GRASS_INSTALL_DOCDIR}") +set(GRASS_MKDOCSDIR "${GRASS_INSTALL_MKDOCSDIR}") +set(GRASS_FONTSDIR "${GRASS_INSTALL_FONTSDIR}") +set(GRASS_COLORSDIR "${GRASS_INSTALL_COLORSDIR}") +set(GRASS_GRAPHICSDIR "${GRASS_INSTALL_GRAPHICSDIR}") +set(GRASS_ETCDIR "${GRASS_INSTALL_ETCDIR}") +set(GRASS_ETCBINDIR "${GRASS_INSTALL_ETCBINDIR}") +set(GRASS_PYDIR "${GRASS_INSTALL_PYDIR}") +set(GRASS_GUIWXDIR "${GRASS_INSTALL_GRASS_GUIWXDIR}") +set(GRASS_MISCDIR "${GRASS_INSTALL_MISCDIR}") + set(RUNTIME_GISBASE "${OUTDIR}/${GISBASE_DIR}") +set(RUNTIME_GRASS_LOCALEDIR "${OUTDIR}/${GRASS_INSTALL_LOCALEDIR}") +set(RUNTIME_GRASS_GUIRESDIR "${OUTDIR}/${GRASS_INSTALL_GUIDIR}") +set(RUNTIME_GRASS_GUISCRIPTDIR "${OUTDIR}/${GRASS_INSTALL_GUISCRIPTDIR}") +set(RUNTIME_GRASS_DOCDIR "${OUTDIR}/${GRASS_INSTALL_DOCDIR}") +set(RUNTIME_GRASS_MKDOCSDIR "${OUTDIR}/${GRASS_INSTALL_MKDOCSDIR}") +set(RUNTIME_GRASS_FONTSDIR "${OUTDIR}/${GRASS_INSTALL_FONTSDIR}") +set(RUNTIME_GRASS_COLORSDIR "${OUTDIR}/${GRASS_INSTALL_COLORSDIR}") +set(RUNTIME_GRASS_GRAPHICSDIR "${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}") +set(RUNTIME_GRASS_ETCDIR "${OUTDIR}/${GRASS_INSTALL_ETCDIR}") + set(GISRC_NAME ".grassrc${GRASS_VERSION_MAJOR}${GRASS_VERSION_MINOR}") if(WITH_FHS) diff --git a/display/CMakeLists.txt b/display/CMakeLists.txt index 9ddb3faa214..5be9432a33e 100644 --- a/display/CMakeLists.txt +++ b/display/CMakeLists.txt @@ -52,12 +52,16 @@ add_custom_command( TARGET d.barscale POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory - ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/barscales + ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/barscales COMMAND ${CMAKE_COMMAND} -E copy ${d_barscale_png} - ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/barscales - BYPRODUCTS ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/barscales) -install(DIRECTORY ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/barscales - DESTINATION ${GRASS_INSTALL_DOCDIR}) + ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/barscales + BYPRODUCTS ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/barscales) +install(DIRECTORY ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/barscales + DESTINATION ${GRASS_INSTALL_GRAPHICSDIR}) +if(WITH_FHS AND WITH_DOCS) + install(DIRECTORY ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/barscales + DESTINATION ${GRASS_INSTALL_DOCDIR}) +endif() build_program_in_subdir(d.colorlist DEPENDS grass_gis grass_display grass_raster) @@ -108,13 +112,17 @@ build_program_in_subdir(d.linegraph DEPENDS grass_gis grass_display grass_symb build_program_in_subdir(d.northarrow DEPENDS grass_gis grass_display grass_symb ${LIBM}) if(WITH_DOCS) - file(MAKE_DIRECTORY ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/northarrows) + file(MAKE_DIRECTORY ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/northarrows) file(GLOB d_northarrow_png ${CMAKE_CURRENT_SOURCE_DIR}/d.northarrow/thumbnails/*.png) file(COPY ${d_northarrow_png} - DESTINATION ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/northarrows) - install(DIRECTORY ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/northarrows - DESTINATION ${GRASS_INSTALL_DOCDIR}) + DESTINATION ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/northarrows) + install(DIRECTORY ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/northarrows + DESTINATION ${GRASS_INSTALL_GRAPHICSDIR}) + if(WITH_FHS) + install(DIRECTORY ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/northarrows + DESTINATION ${GRASS_INSTALL_DOCDIR}) + endif() endif() build_program_in_subdir(d.path DEPENDS grass_gis grass_display grass_vector) build_program_in_subdir(d.profile DEPENDS grass_gis grass_display grass_raster diff --git a/display/d.linegraph/main.c b/display/d.linegraph/main.c index 3aa493f78ac..1fb7886733f 100644 --- a/display/d.linegraph/main.c +++ b/display/d.linegraph/main.c @@ -89,7 +89,7 @@ static char *icon_files(void) list = NULL; len = 0; - snprintf(path, sizeof(path), "%s/etc/symbol", G_gisbase()); + snprintf(path, sizeof(path), "%s/symbol", G_etc_dir()); dir = opendir(path); if (!dir) @@ -102,7 +102,7 @@ static char *icon_files(void) if (d->d_name[0] == '.') continue; - snprintf(path_i, sizeof(path_i), "%s/etc/symbol/%s", G_gisbase(), + snprintf(path_i, sizeof(path_i), "%s/symbol/%s", G_etc_dir(), d->d_name); dir_i = opendir(path_i); diff --git a/display/d.mon/start.c b/display/d.mon/start.c index 2d741db0bf7..0af93eaa308 100644 --- a/display/d.mon/start.c +++ b/display/d.mon/start.c @@ -115,8 +115,12 @@ char *start_wx(const char *name, const char *element, int width, int height, mapfile = (char *)G_malloc(GPATH_MAX); mapfile[0] = '\0'; - snprintf(progname, sizeof(progname), "%s/gui/wxpython/mapdisp/main.py", - G_gisbase()); + const char *wxdir = getenv("GRASS_GUIWXDIR"); + if (!wxdir) + G_fatal_error(_("Incomplete GRASS session: Variable '%s' not set"), + "GRASS_GUIWXDIR"); + + snprintf(progname, sizeof(progname), "%s/mapdisp/main.py", wxdir); snprintf(str_width, sizeof(str_width), "%d", width); snprintf(str_height, sizeof(str_height), "%d", height); @@ -168,8 +172,8 @@ int start_mon(const char *name, const char *output, int select, int width, leg_file = G_store(file_path); /* create py file (renderer) */ - snprintf(render_cmd_path, sizeof(render_cmd_path), - "%s/etc/d.mon/render_cmd.py", getenv("GISBASE")); + snprintf(render_cmd_path, sizeof(render_cmd_path), "%s/d.mon/render_cmd.py", + G_etcbin_dir()); G_file_name(file_path, mon_path, "render.py", G_mapset()); G_debug(1, "Monitor name=%s, pyfile = %s", name, file_path); if (1 != G_copy_file(render_cmd_path, file_path)) diff --git a/display/d.vect.thematic/main.c b/display/d.vect.thematic/main.c index 7fd51defe21..6be1267183d 100644 --- a/display/d.vect.thematic/main.c +++ b/display/d.vect.thematic/main.c @@ -645,7 +645,7 @@ static char *icon_files(void) list = NULL; len = 0; - snprintf(path, sizeof(path), "%s/etc/symbol", G_gisbase()); + snprintf(path, sizeof(path), "%s/symbol", G_etc_dir()); dir = opendir(path); if (!dir) @@ -658,7 +658,7 @@ static char *icon_files(void) if (d->d_name[0] == '.') continue; - snprintf(path_i, sizeof(path_i), "%s/etc/symbol/%s", G_gisbase(), + snprintf(path_i, sizeof(path_i), "%s/symbol/%s", G_etc_dir(), d->d_name); dir_i = opendir(path_i); diff --git a/display/d.vect/main.c b/display/d.vect/main.c index 265357a1ee7..0c653c67bab 100644 --- a/display/d.vect/main.c +++ b/display/d.vect/main.c @@ -531,7 +531,7 @@ char *icon_files(void) list = NULL; len = 0; - snprintf(path, sizeof(path), "%s/etc/symbol", G_gisbase()); + snprintf(path, sizeof(path), "%s/symbol", G_etc_dir()); dir = opendir(path); if (!dir) @@ -544,7 +544,7 @@ char *icon_files(void) if (d->d_name[0] == '.') continue; - snprintf(path_i, sizeof(path_i), "%s/etc/symbol/%s", G_gisbase(), + snprintf(path_i, sizeof(path_i), "%s/symbol/%s", G_etc_dir(), d->d_name); dir_i = opendir(path_i); diff --git a/doc/examples/gui/wxpython/frame.py b/doc/examples/gui/wxpython/frame.py index 9bb95b17094..e9d6c01e431 100644 --- a/doc/examples/gui/wxpython/frame.py +++ b/doc/examples/gui/wxpython/frame.py @@ -22,7 +22,7 @@ # this enables to run application standalone (> python example/frame.py ) if __name__ == "__main__": - sys.path.append(os.path.join(os.environ["GISBASE"], "etc", "gui", "wxpython")) + sys.path.append(os.environ["GRASS_GUIWXDIR"]) # i18n is taken care of in the grass library code. # So we need to import it before any of the GUI code. diff --git a/doc/examples/gui/wxpython/g.gui.example.py b/doc/examples/gui/wxpython/g.gui.example.py old mode 100644 new mode 100755 index 9c04d659458..fb2e94f4c36 --- a/doc/examples/gui/wxpython/g.gui.example.py +++ b/doc/examples/gui/wxpython/g.gui.example.py @@ -38,7 +38,7 @@ import grass.script.core as gcore if __name__ == "__main__": - wxbase = os.path.join(os.getenv("GISBASE"), "etc", "gui", "wxpython") + wxbase = os.environ["GRASS_GUIWXDIR"] if wxbase not in sys.path: sys.path.append(wxbase) diff --git a/general/g.gui/main.c b/general/g.gui/main.c index 91e7de7e128..a34782d3faf 100644 --- a/general/g.gui/main.c +++ b/general/g.gui/main.c @@ -104,8 +104,12 @@ int main(int argc, char *argv[]) exit(EXIT_SUCCESS); } - snprintf(progname, sizeof(progname), "%s/gui/wxpython/wxgui.py", - G_gisbase()); + const char *wxdir = getenv("GRASS_GUIWXDIR"); + if (!wxdir) + G_fatal_error(_("Incomplete GRASS session: Variable '%s' not set"), + "GRASS_GUIWXDIR"); + + snprintf(progname, sizeof(progname), "%s/wxgui.py", wxdir); if (access(progname, F_OK) == -1) G_fatal_error(_("Your installation doesn't include GUI, exiting.")); diff --git a/general/g.list/main.c b/general/g.list/main.c index dabfec64bb7..ecfe05f5be3 100644 --- a/general/g.list/main.c +++ b/general/g.list/main.c @@ -366,7 +366,7 @@ int main(int argc, char *argv[]) if (flag.full->answer) { char lister[GPATH_MAX]; - snprintf(lister, sizeof(lister), "%s/etc/lister/%s", G_gisbase(), + snprintf(lister, sizeof(lister), "%s/lister/%s", G_etcbin_dir(), elem->element[0]); G_debug(3, "lister CMD: %s", lister); diff --git a/general/g.mapset/main.c b/general/g.mapset/main.c index 07eddafb061..e86ee40fc37 100644 --- a/general/g.mapset/main.c +++ b/general/g.mapset/main.c @@ -245,7 +245,7 @@ int main(int argc, char *argv[]) if (!gis_lock) G_fatal_error(_("Unable to read GIS_LOCK environment variable")); - G_asprintf(&lock_prog, "%s/etc/lock", G_gisbase()); + G_asprintf(&lock_prog, "%s/lock", G_etc_dir()); snprintf(path, sizeof(path), "%s/.gislock", mapset_new_path); G_debug(2, "%s", path); @@ -268,7 +268,7 @@ int main(int argc, char *argv[]) } /* Clean temporary directory */ - snprintf(path, sizeof(path), "%s/etc/clean_temp", G_gisbase()); + snprintf(path, sizeof(path), "%s/clean_temp", G_etcbin_dir()); G_verbose_message(_("Cleaning up temporary files...")); G_spawn(path, "clean_temp", NULL); diff --git a/general/g.mapsets/main.c b/general/g.mapsets/main.c index 54068dd71b7..d4e4f86c3aa 100644 --- a/general/g.mapsets/main.c +++ b/general/g.mapsets/main.c @@ -217,8 +217,14 @@ int main(int argc, char *argv[]) if (opt.dialog->answer) { if (opt.mapset->answer) G_warning(_("Option <%s> ignored"), opt.mapset->key); - snprintf(path_buf, sizeof(path_buf), - "%s/gui/wxpython/modules/mapsets_picker.py", G_gisbase()); + + const char *wxdir = getenv("GRASS_GUIWXDIR"); + if (!wxdir) + G_fatal_error(_("Incomplete GRASS session: Variable '%s' not set"), + "GRASS_GUIWXDIR"); + + snprintf(path_buf, sizeof(path_buf), "%s/modules/mapsets_picker.py", + wxdir); G_spawn(getenv("GRASS_PYTHON"), "mapsets_picker.py", path_buf, NULL); exit(EXIT_SUCCESS); } diff --git a/general/g.mkfontcap/Makefile b/general/g.mkfontcap/Makefile index acf9cb184a3..1adf01758ae 100644 --- a/general/g.mkfontcap/Makefile +++ b/general/g.mkfontcap/Makefile @@ -17,6 +17,7 @@ default: cmd $(CAPFILE) endif $(CAPFILE): $(BIN)/$(PGM)$(EXE) - GISRC=junk GISBASE=$(RUN_GISBASE) \ + GISRC=junk GISBASE=$(RUN_GISBASE) GRASS_ETCDIR=$(RUN_GISBASE)/etc \ + GRASS_LOCALEDIR=$(RUN_GISBASE)/locale GRASS_FONTSDIR=$(RUN_GISBASE)/fonts \ $(LD_LIBRARY_PATH_VAR)="$(ARCH_LIBDIR):$(BASE_LIBDIR):$($(LD_LIBRARY_PATH_VAR))" \ $< -s > $@ diff --git a/general/g.mkfontcap/main.c b/general/g.mkfontcap/main.c index 0280e966103..4297292050a 100644 --- a/general/g.mkfontcap/main.c +++ b/general/g.mkfontcap/main.c @@ -86,13 +86,12 @@ int main(int argc, char *argv[]) exit(EXIT_FAILURE); if (!tostdout->answer) { - const char *gisbase = G_gisbase(); const char *alt_file = getenv("GRASS_FONT_CAP"); if (alt_file) fontcapfile = G_store(alt_file); else - G_asprintf(&fontcapfile, "%s/etc/fontcap", gisbase); + G_asprintf(&fontcapfile, "%s/fontcap", G_etc_dir()); if (!access(fontcapfile, F_OK)) { /* File exists? */ if (!G_get_overwrite()) diff --git a/general/g.mkfontcap/stroke_fonts.c b/general/g.mkfontcap/stroke_fonts.c index d94b0f441c0..afbfa87d135 100644 --- a/general/g.mkfontcap/stroke_fonts.c +++ b/general/g.mkfontcap/stroke_fonts.c @@ -48,12 +48,11 @@ static const char *get_desc(const char *); void find_stroke_fonts(void) { - char *dirpath, *fonttable; + char *fonttable; char **dirlisting; int numfiles, i; - G_asprintf(&dirpath, "%s/fonts", G_gisbase()); - + const char *dirpath = G_fonts_dir(); dirlisting = G_ls2(dirpath, &numfiles); G_asprintf(&fonttable, "%s/fonts.table", dirpath); diff --git a/general/g.setproj/get_stp.c b/general/g.setproj/get_stp.c index 3969a735f38..cfe906bddd7 100644 --- a/general/g.setproj/get_stp.c +++ b/general/g.setproj/get_stp.c @@ -51,7 +51,7 @@ int get_stp_code(int code, char *string, char *paramfile) int gotit = 0, stp; FILE *fp; - snprintf(nad27, sizeof(nad27), "%s%s", G_gisbase(), paramfile); + snprintf(nad27, sizeof(nad27), "%s/%s", G_etc_dir(), paramfile); fp = fopen(nad27, "r"); if (fp == NULL) { snprintf(buff, sizeof(buff), "Can not open NAD27 file %s", nad27); @@ -84,7 +84,7 @@ int get_stp_num(void) int record, icode, reccnt = 0, special_case; char STabbr[50], COname[50]; - snprintf(FIPSfile, sizeof(FIPSfile), "%s/etc/proj/FIPS.code", G_gisbase()); + snprintf(FIPSfile, sizeof(FIPSfile), "%s/proj/FIPS.code", G_etc_dir()); for (;;) { diff --git a/general/g.setproj/local_proto.h b/general/g.setproj/local_proto.h index 03f67725748..31f7d64b32a 100644 --- a/general/g.setproj/local_proto.h +++ b/general/g.setproj/local_proto.h @@ -4,8 +4,8 @@ #define SP_UNKNOWN 3 /* $GISBASE-relative locations of parameter files */ -#define STP1927PARAMS "/etc/proj/state27" -#define STP1983PARAMS "/etc/proj/state83" +#define STP1927PARAMS "proj/state27" +#define STP1983PARAMS "proj/state83" #define RADIUS_DEF 6370997. diff --git a/general/g.setproj/proj.c b/general/g.setproj/proj.c index eb6de8a7bd7..553e0049ddd 100644 --- a/general/g.setproj/proj.c +++ b/general/g.setproj/proj.c @@ -13,7 +13,7 @@ struct proj_unit *get_proj_unit(const char *arg) struct proj_unit *unit; FILE *fp; - snprintf(buf, sizeof(buf), "%s/etc/proj/units.table", G_gisbase()); + snprintf(buf, sizeof(buf), "%s/proj/units.table", G_etc_dir()); fp = fopen(buf, "r"); if (!fp) @@ -51,7 +51,7 @@ struct proj_desc *get_proj_desc(const char *arg) struct proj_desc *res; FILE *fp; - snprintf(buf, sizeof(buf), "%s/etc/proj/desc.table", G_gisbase()); + snprintf(buf, sizeof(buf), "%s/proj/desc.table", G_etc_dir()); fp = fopen(buf, "r"); if (!fp) @@ -92,7 +92,7 @@ struct proj_parm *get_proj_parms(const char *arg) int done; FILE *fp; - snprintf(buf, sizeof(buf), "%s/etc/proj/parms.table", G_gisbase()); + snprintf(buf, sizeof(buf), "%s/proj/parms.table", G_etc_dir()); fp = fopen(buf, "r"); if (!fp) diff --git a/gui/scripts/d.wms.py b/gui/scripts/d.wms.py old mode 100644 new mode 100755 index e7da3717f3a..becd35fdd96 --- a/gui/scripts/d.wms.py +++ b/gui/scripts/d.wms.py @@ -161,7 +161,7 @@ from grass.script import core as grass -sys.path.append(os.path.join(os.getenv("GISBASE"), "etc", "r.in.wms")) +sys.path.append(os.path.join(os.getenv("GRASS_ETCBINDIR"), "r.in.wms")) def GetRegion(): diff --git a/gui/wxpython/core/globalvar.py b/gui/wxpython/core/globalvar.py index a3c8b50de79..ade5238844b 100644 --- a/gui/wxpython/core/globalvar.py +++ b/gui/wxpython/core/globalvar.py @@ -26,11 +26,10 @@ from pathlib import Path # path to python scripts -ETCDIR = os.path.join(os.getenv("GISBASE"), "etc") -GUIDIR = os.path.join(os.getenv("GISBASE"), "gui") -WXGUIDIR = os.path.join(GUIDIR, "wxpython") -ICONDIR = os.path.join(GUIDIR, "icons") -IMGDIR = os.path.join(GUIDIR, "images") +ETCDIR = os.getenv("GRASS_ETCDIR") +WXGUIDIR = os.getenv("GRASS_GUIWXDIR") +ICONDIR = os.path.join(os.getenv("GRASS_GUIRESDIR"), "icons") +IMGDIR = os.path.join(os.getenv("GRASS_GUIRESDIR"), "images") SYMBDIR = os.path.join(IMGDIR, "symbols") WXPY3_MIN_VERSION = [4, 0, 0, 0] @@ -249,7 +248,7 @@ def UpdateGRASSAddOnCommands(eList=None): gtk3 = "gtk3" in wx.PlatformInfo # Add GUIDIR/scripts into path -os.environ["PATH"] = os.path.join(GUIDIR, "scripts") + os.pathsep + os.environ["PATH"] +os.environ["PATH"] = os.environ["GRASS_GUISCRIPTDIR"] + os.pathsep + os.environ["PATH"] ignoredCmdPattern = ( r"^d\..*|^r[3]?\.mapcalc$|^i.group$|^r.import$|" diff --git a/gui/wxpython/core/settings.py b/gui/wxpython/core/settings.py index e0758a482d6..10a126d10ba 100644 --- a/gui/wxpython/core/settings.py +++ b/gui/wxpython/core/settings.py @@ -105,7 +105,7 @@ def __init__(self): def _generateLocale(self): """Generate locales""" try: - locale_path = Path(os.environ["GISBASE"]) / "locale" + locale_path = Path(os.environ["GRASS_LOCALEDIR"]) self.locs = [p.name for p in locale_path.iterdir() if p.is_dir()] self.locs.append("en") # GRASS doesn't ship EN po files self.locs.sort() diff --git a/gui/wxpython/core/toolboxes.py b/gui/wxpython/core/toolboxes.py index beff6291f3e..495e06d9d4e 100644 --- a/gui/wxpython/core/toolboxes.py +++ b/gui/wxpython/core/toolboxes.py @@ -31,7 +31,7 @@ # if this will become part of grass Python library or module, this should be # parametrized, so we will get rid of the definition here # (GUI will use its definition and build also its own) -WXGUIDIR = os.path.join(os.getenv("GISBASE"), "gui", "wxpython") +WXGUIDIR = os.environ["GRASS_GUIWXDIR"] # this could be placed to functions diff --git a/gui/wxpython/gui_core/ghelp.py b/gui/wxpython/gui_core/ghelp.py index bc4924ecb80..3d866d4de4d 100644 --- a/gui/wxpython/gui_core/ghelp.py +++ b/gui/wxpython/gui_core/ghelp.py @@ -275,7 +275,7 @@ def _pageInfo(self): def _pageCopyright(self): """Copyright information""" - copyfile = os.path.join(os.getenv("GISBASE"), "COPYING") + copyfile = os.path.join(os.getenv("GRASS_MISCDIR"), "COPYING") if os.path.exists(copyfile): copytext = Path(copyfile).read_text() else: @@ -302,7 +302,7 @@ def _pageCopyright(self): def _pageLicense(self): """Licence about""" - licfile = os.path.join(os.getenv("GISBASE"), "GPL.TXT") + licfile = os.path.join(os.getenv("GRASS_MISCDIR"), "GPL.TXT") if os.path.exists(licfile): with open(licfile) as licenceFile: license = "".join(licenceFile.readlines()) @@ -355,7 +355,7 @@ def _pageCitation(self): def _pageCredit(self): """Credit about""" # credits - authfile = os.path.join(os.getenv("GISBASE"), "AUTHORS") + authfile = os.path.join(os.getenv("GRASS_MISCDIR"), "AUTHORS") if os.path.exists(authfile): with codecs.open(authfile, encoding="utf-8", mode="r") as authorsFile: authors = "".join(authorsFile.readlines()) @@ -380,9 +380,11 @@ def _pageCredit(self): def _pageContributors(self, extra=False): """Contributors info""" if extra: - contribfile = os.path.join(os.getenv("GISBASE"), "contributors_extra.csv") + contribfile = os.path.join( + os.getenv("GRASS_MISCDIR"), "contributors_extra.csv" + ) else: - contribfile = os.path.join(os.getenv("GISBASE"), "contributors.csv") + contribfile = os.path.join(os.getenv("GRASS_MISCDIR"), "contributors.csv") if os.path.exists(contribfile): contribs = [] errLines = [] @@ -465,7 +467,7 @@ def _pageContributors(self, extra=False): def _pageTranslators(self): """Translators info""" - translatorsfile = os.path.join(os.getenv("GISBASE"), "translators.csv") + translatorsfile = os.path.join(os.getenv("GRASS_MISCDIR"), "translators.csv") if os.path.exists(translatorsfile): translators = {} errLines = [] @@ -645,7 +647,7 @@ def OnPaneChanged(self, evt): def _pageStats(self): """Translation statistics info""" fname = "translation_status.json" - statsfile = os.path.join(os.getenv("GISBASE"), fname) + statsfile = os.path.join(os.getenv("GRASS_MISCDIR"), fname) if os.path.exists(statsfile): import json @@ -761,10 +763,10 @@ def __init__(self, parent, command, text, **kwargs): self.historyIdx = 0 self.markdown = True # check if mkdocs is used (add slash at the end) - self.fspath = os.path.join(os.getenv("GISBASE"), "docs", "mkdocs", "site", "") + self.fspath = os.path.join(os.getenv("GRASS_MKDOCSDIR"), "site", "") if not os.path.exists(self.fspath): self.markdown = False - self.fspath = os.path.join(os.getenv("GISBASE"), "docs", "html", "") + self.fspath = os.path.join(os.getenv("GRASS_DOCDIR"), "") self._setFont() self.SetBorders(10) diff --git a/gui/wxpython/gui_core/widgets.py b/gui/wxpython/gui_core/widgets.py index ffbbed02e02..3a44b8b0dc9 100644 --- a/gui/wxpython/gui_core/widgets.py +++ b/gui/wxpython/gui_core/widgets.py @@ -1727,7 +1727,7 @@ class ColorTablesComboBox(PictureComboBox): def _getPath(self, name): return os.path.join( - os.getenv("GISBASE"), "docs", "html", "colortables", "%s.png" % name + os.getenv("GRASS_GRAPHICSDIR"), "colortables", "%s.png" % name ) @@ -1735,9 +1735,7 @@ class BarscalesComboBox(PictureComboBox): """ComboBox with barscales for d.barscale.""" def _getPath(self, name): - return os.path.join( - os.getenv("GISBASE"), "docs", "html", "barscales", name + ".png" - ) + return os.path.join(os.getenv("GRASS_GRAPHICSDIR"), "barscales", name + ".png") class NArrowsComboBox(PictureComboBox): @@ -1745,7 +1743,7 @@ class NArrowsComboBox(PictureComboBox): def _getPath(self, name): return os.path.join( - os.getenv("GISBASE"), "docs", "html", "northarrows", "%s.png" % name + os.getenv("GRASS_GRAPHICSDIR"), "northarrows", "%s.png" % name ) diff --git a/gui/wxpython/image2target/ii2t_gis_set.py b/gui/wxpython/image2target/ii2t_gis_set.py index e2b3da9e60d..c9952ed6160 100644 --- a/gui/wxpython/image2target/ii2t_gis_set.py +++ b/gui/wxpython/image2target/ii2t_gis_set.py @@ -88,11 +88,9 @@ def __init__(self, parent=None, id=wx.ID_ANY, style=wx.DEFAULT_FRAME_STYLE): # image try: if os.getenv("ISISROOT"): - name = os.path.join( - globalvar.GUIDIR, "images", "startup_banner_isis.png" - ) + name = os.path.join(globalvar.IMGDIR, "startup_banner_isis.png") else: - name = os.path.join(globalvar.GUIDIR, "images", "startup_banner.png") + name = os.path.join(globalvar.IMGDIR, "startup_banner.png") self.hbitmap = wx.StaticBitmap( self.panel, wx.ID_ANY, wx.Bitmap(name=name, type=wx.BITMAP_TYPE_PNG) ) diff --git a/gui/wxpython/lmgr/frame.py b/gui/wxpython/lmgr/frame.py index 9524c3a8b5d..b3ab6730993 100644 --- a/gui/wxpython/lmgr/frame.py +++ b/gui/wxpython/lmgr/frame.py @@ -35,9 +35,6 @@ except ImportError: import wx.lib.flatnotebook as FN -if os.path.join(globalvar.ETCDIR, "python") not in sys.path: - sys.path.append(os.path.join(globalvar.ETCDIR, "python")) - from grass.script import core as grass from grass.script.utils import decode @@ -1463,7 +1460,7 @@ def OnSystemInfo(self, event): # check also OSGeo4W on MS Windows if sys.platform == "win32" and not os.path.exists( - os.path.join(os.getenv("GISBASE"), "WinGRASS-README.url") + os.path.join(os.getenv("GRASS_MISCDIR"), "WinGRASS-README.url") ): osgeo4w = " (OSGeo4W)" else: diff --git a/gui/wxpython/main_window/frame.py b/gui/wxpython/main_window/frame.py index f6ab655d6c0..1239b10a400 100644 --- a/gui/wxpython/main_window/frame.py +++ b/gui/wxpython/main_window/frame.py @@ -41,9 +41,6 @@ except ImportError: import wx.lib.flatnotebook as FN -if os.path.join(globalvar.ETCDIR, "python") not in sys.path: - sys.path.append(os.path.join(globalvar.ETCDIR, "python")) - from grass.script import core as grass from grass.script.utils import decode @@ -1619,7 +1616,7 @@ def OnSystemInfo(self, event): # check also OSGeo4W on MS Windows if sys.platform == "win32" and not os.path.exists( - os.path.join(os.getenv("GISBASE"), "WinGRASS-README.url") + os.path.join(os.getenv("GRASS_MISCDIR"), "WinGRASS-README.url") ): osgeo4w = " (OSGeo4W)" else: diff --git a/gui/wxpython/psmap/dialogs.py b/gui/wxpython/psmap/dialogs.py index 2561e3c388e..fa76cc15e1f 100644 --- a/gui/wxpython/psmap/dialogs.py +++ b/gui/wxpython/psmap/dialogs.py @@ -2203,13 +2203,13 @@ def __init__(self, parent, id, vectors, tmpSettings): self.currLayer = self.vPropertiesDict["layer"] # path to symbols, patterns - gisbase = os.getenv("GISBASE") - self.symbolPath = os.path.join(gisbase, "etc", "symbol") + g_etc_dir = os.getenv("GRASS_ETCDIR") + self.symbolPath = os.path.join(g_etc_dir, "symbol") self.symbols = [] for dir in Path(self.symbolPath).iterdir(): for symbol in Path(self.symbolPath).joinpath(dir.name).iterdir(): self.symbols.append(os.path.join(dir.name, symbol.name)) - self.patternPath = os.path.join(gisbase, "etc", "paint", "patterns") + self.patternPath = os.path.join(g_etc_dir, "paint", "patterns") # notebook notebook = Notebook(parent=self, id=wx.ID_ANY, style=wx.BK_DEFAULT) @@ -6193,8 +6193,8 @@ def _newObject(self): return NorthArrow(self.id, self.instruction, env=self.env) def _getImageDirectory(self): - gisbase = os.getenv("GISBASE") - return os.path.join(gisbase, "etc", "paint", "decorations") + g_etc_dir = os.getenv("GRASS_ETCDIR") + return os.path.join(g_etc_dir, "paint", "decorations") def _addConvergence(self, panel, gridBagSizer): convergence = Button(parent=panel, id=wx.ID_ANY, label=_("Compute convergence")) diff --git a/gui/wxpython/web_services/cap_interface.py b/gui/wxpython/web_services/cap_interface.py index 91b2b396dfd..eacea483b29 100644 --- a/gui/wxpython/web_services/cap_interface.py +++ b/gui/wxpython/web_services/cap_interface.py @@ -25,7 +25,7 @@ import os import sys -WMSLibPath = os.path.join(os.getenv("GISBASE"), "etc", "r.in.wms") +WMSLibPath = os.path.join(os.getenv("GRASS_ETCBINDIR"), "r.in.wms") if WMSLibPath not in sys.path: sys.path.append(WMSLibPath) diff --git a/gui/wxpython/web_services/widgets.py b/gui/wxpython/web_services/widgets.py index 29a1c99014a..ff7746f358f 100644 --- a/gui/wxpython/web_services/widgets.py +++ b/gui/wxpython/web_services/widgets.py @@ -62,7 +62,7 @@ import grass.script as gs from grass.pydispatch.signal import Signal -rinwms_path = os.path.join(os.getenv("GISBASE"), "etc", "r.in.wms") +rinwms_path = os.path.join(os.getenv("GRASS_ETCBINDIR"), "r.in.wms") if rinwms_path not in sys.path: sys.path.append(rinwms_path) diff --git a/include/Make/Install.make b/include/Make/Install.make index 635ce194190..8e67ca6e869 100644 --- a/include/Make/Install.make +++ b/include/Make/Install.make @@ -128,8 +128,9 @@ $(DESTDIR)$(INST_DIR) $(DESTDIR)$(UNIX_BIN): $(MAKE_DIR_CMD) $@ $(STARTUP): $(ARCH_DISTDIR)/$(GRASS_NAME).tmp - sed -e 's#'@GRASS_PYDIR@'#'$(INST_DIR)/etc/python'#g' \ - $< > $@ + sed \ + -e 's#'@GRASS_PYDIR@'#'$(INST_DIR)/etc/python'#g' \ + $< > $@ -$(CHMOD) a+x $@ $(DESTDIR)$(INST_DIR)/$(RESOURCE_PATHS): $(ARCH_DISTDIR)/resource_paths.py @@ -143,6 +144,18 @@ $(DESTDIR)$(INST_DIR)/$(RESOURCE_PATHS): $(ARCH_DISTDIR)/resource_paths.py -e 's#'@GRASS_VERSION_NUMBER@'#$(GRASS_VERSION_NUMBER)#' \ -e 's#'@LD_LIBRARY_PATH_VAR@'#$(LD_LIBRARY_PATH_VAR)#' \ -e 's#'@START_UP@'#$(GRASS_NAME)#' \ + -e 's#'@GRASS_COLORSDIR@'#etc/colors#' \ + -e 's#'@GRASS_DOCDIR@'#docs/html#' \ + -e 's#'@GRASS_ETCBINDIR@'#etc#' \ + -e 's#'@GRASS_ETCDIR@'#etc#' \ + -e 's#'@GRASS_FONTSDIR@'#fonts#' \ + -e 's#'@GRASS_GRAPHICSDIR@'#docs/html#' \ + -e 's#'@GRASS_GUIRESDIR@'#gui#' \ + -e 's#'@GRASS_GUISCRIPTDIR@'#gui/scripts#' \ + -e 's#'@GRASS_GUIWXDIR@'#gui/wxpython#' \ + -e 's#'@GRASS_LOCALEDIR@'#locale#' \ + -e 's#'@GRASS_MISCDIR@'##' \ + -e 's#'@GRASS_MKDOCSDIR@'#docs/mkdocs#' \ $< > $@ define fix_gisbase diff --git a/include/Make/Rules.make b/include/Make/Rules.make index 9e765fab7a4..7220ad7f466 100644 --- a/include/Make/Rules.make +++ b/include/Make/Rules.make @@ -45,6 +45,17 @@ GRASS_PYTHONPATH := $(call mkpath,$(GISBASE)/etc/python,$(GRASS_PYTHONPATH)) run_grass = \ GISRC=$(RUN_GISRC) \ GISBASE=$(RUN_GISBASE) \ + GRASS_COLORSDIR=$(RUN_GISBASE)/etc/colors \ + GRASS_ETCDIR=$(RUN_GISBASE)/etc \ + GRASS_FONTSDIR=$(RUN_GISBASE)/fonts \ + GRASS_GRAPHICSDIR=$(RUN_GISBASE)/docs/html \ + GRASS_GUIRESDIR=$(RUN_GISBASE)/gui \ + GRASS_GUISCRIPTDIR=$(RUN_GISBASE)/gui/scripts \ + GRASS_GUIWXDIR=$(RUN_GISBASE)/gui/wxpython \ + GRASS_LOCALEDIR=$(RUN_GISBASE)/locale \ + GRASS_MISCDIR=$(RUN_GISBASE) \ + GRASS_MKDOCSDIR=$(RUN_GISBASE)/docs/mkdocs \ + GRASS_PYDIR=$(RUN_GISBASE)/etc/python \ PATH="$(ARCH_DISTDIR)/bin:$(GISBASE)/bin:$(GISBASE)/scripts:$$PATH" \ PYTHONPATH="$(GRASS_PYTHONPATH)" \ $(LD_LIBRARY_PATH_VAR)="$(BIN):$(GISBASE)/bin:$(GISBASE)/scripts:$(ARCH_LIBDIR):$(BASE_LIBDIR):$($(LD_LIBRARY_PATH_VAR))" \ diff --git a/include/grass/defs/gis.h b/include/grass/defs/gis.h index 8bacb260cc2..10a793c6ed5 100644 --- a/include/grass/defs/gis.h +++ b/include/grass/defs/gis.h @@ -394,6 +394,13 @@ void G__gisinit(const char *, const char *); void G__no_gisinit(const char *); void G_init_all(void); +/* resource_dirs.c */ +const char *G_colors_dir(void); +const char *G_etcbin_dir(void); +const char *G_etc_dir(void); +const char *G_fonts_dir(void); +const char *G_locale_dir(void); + /* handler.c */ void G_add_error_handler(void (*)(void *), void *); void G_remove_error_handler(void (*)(void *), void *); diff --git a/include/grass/gprojects.h b/include/grass/gprojects.h index 35f795d87ad..cf1e1236307 100644 --- a/include/grass/gprojects.h +++ b/include/grass/gprojects.h @@ -61,11 +61,11 @@ #endif /* Data Files */ -#define ELLIPSOIDTABLE "/etc/proj/ellipse.table" -#define DATUMTABLE "/etc/proj/datum.table" -#define DATUMTRANSFORMTABLE "/etc/proj/datumtransform.table" +#define ELLIPSOIDTABLE "/proj/ellipse.table" +#define DATUMTABLE "/proj/datum.table" +#define DATUMTRANSFORMTABLE "/proj/datumtransform.table" /* GRASS relative location of datum conversion lookup tables */ -#define GRIDDIR "/etc/proj/nad" +#define GRIDDIR "/proj/nad" /* TODO: rename pj_ to gpj_ to avoid symbol clash with PROJ lib */ struct pj_info { diff --git a/lib/driver/font2.c b/lib/driver/font2.c index eaa91e069d6..272a66e4e2f 100644 --- a/lib/driver/font2.c +++ b/lib/driver/font2.c @@ -128,7 +128,7 @@ static void load_glyphs(void) for (i = 1; i <= 4; i++) { char buf[GPATH_MAX]; - snprintf(buf, sizeof(buf), "%s/fonts/hersh.oc%d", G_gisbase(), i); + snprintf(buf, sizeof(buf), "%s/hersh.oc%d", G_fonts_dir(), i); read_hersh(buf); } } @@ -141,7 +141,7 @@ static void read_fontmap(const char *name) num_chars = 0; memset(fontmap, 0, sizeof(fontmap)); - snprintf(buf, sizeof(buf), "%s/fonts/%s.hmp", G_gisbase(), name); + snprintf(buf, sizeof(buf), "%s/%s.hmp", G_fonts_dir(), name); fp = fopen(buf, "r"); if (!fp) { diff --git a/lib/driver/parse_ftcap.c b/lib/driver/parse_ftcap.c index e8be0572c25..4c03d83fba2 100644 --- a/lib/driver/parse_ftcap.c +++ b/lib/driver/parse_ftcap.c @@ -86,7 +86,7 @@ struct GFONT_CAP *parse_fontcap(void) capfile); } if (fp == NULL) { - snprintf(file, sizeof(file), "%s/etc/fontcap", G_gisbase()); + snprintf(file, sizeof(file), "%s/fontcap", G_etc_dir()); if ((fp = fopen(file, "r")) == NULL) G_warning(_("%s: No font definition file"), file); } diff --git a/lib/gis/color_rules.c b/lib/gis/color_rules.c index bb3ace277f8..8c998b223df 100644 --- a/lib/gis/color_rules.c +++ b/lib/gis/color_rules.c @@ -268,7 +268,7 @@ struct colorinfo *get_colorinfo(int *nrules) char **cnames; /* load color rules */ - snprintf(path, GPATH_MAX, "%s/etc/colors", G_gisbase()); + snprintf(path, GPATH_MAX, "%s", G_colors_dir()); *nrules = 0; cnames = G_ls2(path, nrules); @@ -284,8 +284,7 @@ struct colorinfo *get_colorinfo(int *nrules) colorinfo[i].desc = NULL; /* open color rule file */ - snprintf(path, GPATH_MAX, "%s/etc/colors/%s", G_gisbase(), - colorinfo[i].name); + snprintf(path, GPATH_MAX, "%s/%s", G_colors_dir(), colorinfo[i].name); fp = fopen(path, "r"); if (!fp) G_fatal_error(_("Unable to open color rule")); @@ -361,7 +360,7 @@ struct colorinfo *get_colorinfo(int *nrules) qsort(colorinfo, *nrules, sizeof(struct colorinfo), cmp_clrname); /* load color descriptions */ - snprintf(path, GPATH_MAX, "%s/etc/colors.desc", G_gisbase()); + snprintf(path, GPATH_MAX, "%s/colors.desc", G_etc_dir()); fp = fopen(path, "r"); if (!fp) G_fatal_error(_("Unable to open color descriptions")); diff --git a/lib/gis/datum.c b/lib/gis/datum.c index 8931dd328de..7dbeb25a5ff 100644 --- a/lib/gis/datum.c +++ b/lib/gis/datum.c @@ -14,7 +14,7 @@ * *****************************************************************************/ -#define DATUMTABLE "/etc/proj/datum.table" +#define DATUMTABLE "/proj/datum.table" #include #include @@ -148,7 +148,7 @@ void G_read_datum_table(void) if (G_is_initialized(&table.initialized)) return; - snprintf(file, sizeof(file), "%s%s", G_gisbase(), DATUMTABLE); + snprintf(file, sizeof(file), "%s%s", G_etc_dir(), DATUMTABLE); fd = fopen(file, "r"); if (!fd) { diff --git a/lib/gis/find_etc.c b/lib/gis/find_etc.c index 59e8579689e..6e43ce437ac 100644 --- a/lib/gis/find_etc.c +++ b/lib/gis/find_etc.c @@ -40,7 +40,7 @@ static char *G__find_etc(const char *name) /* * check application etc dir */ - snprintf(path, sizeof(path), "%s/etc/%s", G_gisbase(), name); + snprintf(path, sizeof(path), "%s/%s", G_etc_dir(), name); if (access(path, 0) == 0) return G_store(path); diff --git a/lib/gis/get_ellipse.c b/lib/gis/get_ellipse.c index 2a208c47bed..439b16e8068 100644 --- a/lib/gis/get_ellipse.c +++ b/lib/gis/get_ellipse.c @@ -255,7 +255,7 @@ int G_read_ellipsoid_table(int fatal) if (G_is_initialized(&table.initialized)) return 1; - snprintf(file, sizeof(file), "%s/etc/proj/ellipse.table", G_gisbase()); + snprintf(file, sizeof(file), "%s/proj/ellipse.table", G_etc_dir()); fd = fopen(file, "r"); if (fd == NULL) { diff --git a/lib/gis/is.c b/lib/gis/is.c index 68298671306..1fbcd95a64c 100644 --- a/lib/gis/is.c +++ b/lib/gis/is.c @@ -48,7 +48,7 @@ static int test_path_file(const char *path, const char *file) */ int G_is_gisbase(const char *path) { - return test_path_file(path, "etc/element_list"); + return test_path_file(path, "etc/echo"); } /** diff --git a/lib/gis/locale.c b/lib/gis/locale.c index 18316f7c7f0..9b8fcd77215 100644 --- a/lib/gis/locale.c +++ b/lib/gis/locale.c @@ -39,14 +39,9 @@ void G_init_locale(void) #ifdef LC_MESSAGES setlocale(LC_MESSAGES, ""); #endif - const char *gisbase = getenv("GISBASE"); - - if (gisbase && *gisbase) { - char localedir[GPATH_MAX]; - - strcpy(localedir, gisbase); - strcat(localedir, "/locale"); + const char *localedir = G_locale_dir(); + if (localedir && *localedir) { bindtextdomain("grasslibs", localedir); bindtextdomain("grassmods", localedir); } diff --git a/lib/gis/parser.c b/lib/gis/parser.c index 62d905c4173..d906d898dbc 100644 --- a/lib/gis/parser.c +++ b/lib/gis/parser.c @@ -987,8 +987,12 @@ int module_gui_wx(void) if (!st->pgm_path) G_fatal_error(_("Unable to determine program name")); - snprintf(script, GPATH_MAX, "%s/gui/wxpython/gui_core/forms.py", - getenv("GISBASE")); + const char *wxdir = getenv("GRASS_GUIWXDIR"); + if (!wxdir) + G_fatal_error(_("Incomplete GRASS session: Variable '%s' not set"), + "GRASS_GUIWXDIR"); + + snprintf(script, GPATH_MAX, "%s/gui_core/forms.py", wxdir); if (access(script, F_OK) != -1) G_spawn(getenv("GRASS_PYTHON"), getenv("GRASS_PYTHON"), script, G_recreate_command_original_path(), NULL); @@ -1815,7 +1819,7 @@ const char *get_renamed_option(const char *key) /* read renamed options from file (renamed_options) */ char path[GPATH_MAX]; - snprintf(path, GPATH_MAX, "%s/etc/renamed_options", G_gisbase()); + snprintf(path, GPATH_MAX, "%s/renamed_options", G_etc_dir()); st->renamed_options = G_read_key_value_file(path); } diff --git a/lib/gis/resource_dirs.c b/lib/gis/resource_dirs.c new file mode 100644 index 00000000000..8602ceefe49 --- /dev/null +++ b/lib/gis/resource_dirs.c @@ -0,0 +1,53 @@ +/*! + \file lib/gis/resource_dirs.c + + \brief GIS Library - Get paths to resource directories. + + \author Nicklas Larsson + + (c) 2025 by the GRASS Development Team + + SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include + +#include +#include + +static const char *get_g_env(const char *); + +const char *G_colors_dir(void) +{ + return get_g_env("GRASS_COLORSDIR"); +} + +const char *G_etcbin_dir(void) +{ + return get_g_env("GRASS_ETCBINDIR"); +} + +const char *G_etc_dir(void) +{ + return get_g_env("GRASS_ETCDIR"); +} + +const char *G_fonts_dir(void) +{ + return get_g_env("GRASS_FONTSDIR"); +} + +const char *G_locale_dir(void) +{ + return get_g_env("GRASS_LOCALEDIR"); +} + +static const char *get_g_env(const char *env_var) +{ + const char *value = getenv(env_var); + if (value) + return value; + + G_fatal_error(_("Incomplete GRASS session: Variable '%s' not set"), + env_var); +} diff --git a/lib/init/Makefile b/lib/init/Makefile index 8cf12836bad..e7e24ba6ca7 100644 --- a/lib/init/Makefile +++ b/lib/init/Makefile @@ -69,22 +69,13 @@ $(ARCH_BINDIR)/$(START_UP): grass.py endif rm -f $@ sed \ - -e 's#@GRASS_PYDIR@#$(RUN_GISBASE)/etc/python#' \ + -e 's#@GRASS_PYDIR@#$(RUN_GISBASE)/etc/python#g' \ $< > $@ chmod +x $@ $(ARCH_DISTDIR)/$(START_UP).tmp: grass.py rm -f $@ - sed \ - -e 's#@GRASS_VERSION_NUMBER@#$(GRASS_VERSION_NUMBER)#' \ - -e 's#@GRASS_VERSION_MAJOR@#$(GRASS_VERSION_MAJOR)#' \ - -e 's#@GRASS_VERSION_MINOR@#$(GRASS_VERSION_MINOR)#' \ - -e 's#@GRASS_VERSION_GIT@#$(GRASS_VERSION_GIT)#' \ - -e 's#@START_UP@#$(START_UP)#' \ - -e 's#@GRASS_CONFIG_DIR@#$(GRASS_CONFIG_DIR)#' \ - -e 's#@LD_LIBRARY_PATH_VAR@#$(LD_LIBRARY_PATH_VAR)#' \ - -e 's#@CONFIG_PROJSHARE@#$(PROJSHARE)#' \ - $< > $@ + cp $< $@ $(ETC)/echo$(EXE) $(ETC)/run$(EXE): $(ETC)/%$(EXE): $(OBJDIR)/%.o $(call linker_base,$(LINK),$(LDFLAGS),$(MANIFEST_OBJ)) diff --git a/lib/init/grass.py b/lib/init/grass.py index 729720ada3c..fae0b5a2640 100755 --- a/lib/init/grass.py +++ b/lib/init/grass.py @@ -1167,7 +1167,7 @@ def set_language(grass_config_dir: StrPath) -> None: os.environ["LC_MESSAGES"] = "C" os.environ["LC_NUMERIC"] = "C" os.environ["LC_TIME"] = "C" - gettext.install("grasslibs", gpath("locale")) + gettext.install("grasslibs", os.environ["GRASS_LOCALEDIR"]) sys.stderr.write( "All attempts to enable English language have" " failed. GRASS running with C locale.\n" @@ -1248,7 +1248,7 @@ def set_language(grass_config_dir: StrPath) -> None: del os.environ["LC_ALL"] # Remove LC_ALL to not override LC_NUMERIC # From now on enforce the new language - gettext.install("grasslibs", gpath("locale")) + gettext.install("grasslibs", os.environ["GRASS_LOCALEDIR"]) # TODO: the gisrcrc here does not make sense, remove it from load_gisrc @@ -2129,7 +2129,7 @@ def main() -> None: GISBASE, \ CONFIG_PROJSHARE - runtime_paths = RuntimePaths() + runtime_paths = RuntimePaths(init_env_vars=True) CMD_NAME = runtime_paths.grass_exe_name GRASS_VERSION = runtime_paths.version GRASS_VERSION_MAJOR = runtime_paths.version_major diff --git a/lib/manage/read_list.c b/lib/manage/read_list.c index 3c9d5fece4d..793b55a0ab8 100644 --- a/lib/manage/read_list.c +++ b/lib/manage/read_list.c @@ -66,7 +66,7 @@ int M_read_list(int check_if_empty, int *num) if (env) strcpy(element_list, env); else - snprintf(element_list, GPATH_MAX, "%s/etc/element_list", G_gisbase()); + snprintf(element_list, GPATH_MAX, "%s/element_list", G_etc_dir()); fd = fopen(element_list, "r"); if (!fd) diff --git a/lib/proj/convert.c b/lib/proj/convert.c index b293706a745..0b8ea6d2a82 100644 --- a/lib/proj/convert.c +++ b/lib/proj/convert.c @@ -27,7 +27,7 @@ #include "local_proto.h" /* GRASS relative location of OGR co-ordinate system lookup tables */ -#define CSVDIR "/etc/proj/ogr_csv" +#define CSVDIR "/proj/ogr_csv" static void DatumNameMassage(char **); #endif @@ -504,8 +504,8 @@ int GPJ_osr_to_grass(struct Cell_head *cellhd, struct Key_Value **projinfo, /* use name of the projection as name for the coordinate * system */ - snprintf(path, sizeof(path), "%s/etc/proj/projections", - G_gisbase()); + snprintf(path, sizeof(path), "%s/proj/projections", + G_etc_dir()); if (G_lookup_key_value_from_file(path, pszProj, name, sizeof(name)) > 0) G_set_key_value("name", name, *projinfo); @@ -640,8 +640,7 @@ int GPJ_osr_to_grass(struct Cell_head *cellhd, struct Key_Value **projinfo, /* use name of the projection as name for the coordinate system */ - snprintf(path, sizeof(path), "%s/etc/proj/projections", - G_gisbase()); + snprintf(path, sizeof(path), "%s/proj/projections", G_etc_dir()); if (G_lookup_key_value_from_file(path, pszProj, name, sizeof(name)) > 0) G_set_key_value("name", name, *projinfo); @@ -1015,13 +1014,13 @@ int GPJ_wkt_to_grass(struct Cell_head *cellhd, struct Key_Value **projinfo, const char *GPJ_set_csv_loc(const char *name) { - const char *gisbase = G_gisbase(); + const char *g_etc_dir = G_etc_dir(); static char *buf = NULL; if (buf != NULL) G_free(buf); - G_asprintf(&buf, "%s%s/%s", gisbase, CSVDIR, name); + G_asprintf(&buf, "%s%s/%s", g_etc_dir, CSVDIR, name); return buf; } diff --git a/lib/proj/datum.c b/lib/proj/datum.c index 1680b554f22..28bff7f2763 100644 --- a/lib/proj/datum.c +++ b/lib/proj/datum.c @@ -270,7 +270,7 @@ GPJ_get_datum_transform_by_name(const char *inputname) /* Now check for additional parameters in datumtransform.table */ - snprintf(file, sizeof(file), "%s%s", G_gisbase(), DATUMTRANSFORMTABLE); + snprintf(file, sizeof(file), "%s%s", G_etc_dir(), DATUMTRANSFORMTABLE); fd = fopen(file, "r"); if (!fd) { @@ -347,7 +347,7 @@ struct datum_list *read_datum_table(void) int line; struct datum_list *current = NULL, *outputlist = NULL; - snprintf(file, sizeof(file), "%s%s", G_gisbase(), DATUMTABLE); + snprintf(file, sizeof(file), "%s%s", G_etc_dir(), DATUMTABLE); fd = fopen(file, "r"); if (!fd) { diff --git a/lib/proj/ellipse.c b/lib/proj/ellipse.c index 0498cc7c566..19d2e52a2be 100644 --- a/lib/proj/ellipse.c +++ b/lib/proj/ellipse.c @@ -232,7 +232,7 @@ struct ellps_list *read_ellipsoid_table(int fatal) struct ellps_list *current = NULL, *outputlist = NULL; double a, e2, rf; - snprintf(file, sizeof(file), "%s%s", G_gisbase(), ELLIPSOIDTABLE); + snprintf(file, sizeof(file), "%s%s", G_etc_dir(), ELLIPSOIDTABLE); fd = fopen(file, "r"); if (!fd) { diff --git a/lib/psdriver/graph_set.c b/lib/psdriver/graph_set.c index b33db00f307..1e4f87244d9 100644 --- a/lib/psdriver/graph_set.c +++ b/lib/psdriver/graph_set.c @@ -56,8 +56,7 @@ static void write_prolog(void) strftime(date_str, sizeof(date_str), DATE_FORMAT, tm); - snprintf(prolog_file, sizeof(prolog_file), "%s/etc/psdriver.ps", - G_gisbase()); + snprintf(prolog_file, sizeof(prolog_file), "%s/psdriver.ps", G_etc_dir()); prolog_fp = fopen(prolog_file, "r"); if (!prolog_fp) diff --git a/lib/raster/color_rules.c b/lib/raster/color_rules.c index 1d9ddd6f1eb..910bc050103 100644 --- a/lib/raster/color_rules.c +++ b/lib/raster/color_rules.c @@ -318,7 +318,7 @@ static void load_rules_name(struct Colors *colors, const char *name, DCELL min, { char path[GPATH_MAX]; - snprintf(path, sizeof(path), "%s/etc/colors/%s", G_gisbase(), name); + snprintf(path, sizeof(path), "%s/%s", G_colors_dir(), name); if (!load_rules_file(colors, path, min, max)) G_fatal_error(_("Unable to load color rules <%s>"), name); diff --git a/lib/symbol/read.c b/lib/symbol/read.c index 5aacb78b32f..19cb89cbed5 100644 --- a/lib/symbol/read.c +++ b/lib/symbol/read.c @@ -273,7 +273,7 @@ SYMBOL *S_read(const char *sname) fp = G_fopen_old(buf, name, ms); } else { /* Search in GISBASE */ - snprintf(buf, sizeof(buf), "%s/etc/symbol/%s", G_gisbase(), sname); + snprintf(buf, sizeof(buf), "%s/symbol/%s", G_etc_dir(), sname); fp = fopen(buf, "r"); } diff --git a/locale/Makefile b/locale/Makefile index 6c5df6a7567..bca151dec68 100644 --- a/locale/Makefile +++ b/locale/Makefile @@ -42,7 +42,7 @@ MSGMERGE_ARGS = --no-wrap --sort-by-file --previous MSGUNIQ_ARGS = --no-wrap --sort-by-file define po_stats -GISBASE="$(RUN_GISBASE)" $(PYTHON) ./grass_po_stats.py +GISBASE="$(RUN_GISBASE)" GRASS_MISCDIR="$(RUN_GISBASE)" $(PYTHON) ./grass_po_stats.py endef #The xgettext utility is used to automate the creation of diff --git a/locale/grass_po_stats.py b/locale/grass_po_stats.py old mode 100644 new mode 100755 index bfae49629d3..e552032f28e --- a/locale/grass_po_stats.py +++ b/locale/grass_po_stats.py @@ -160,5 +160,5 @@ def main(in_dirpath, out_josonpath): if __name__ == "__main__": directory = "po/" - outfile = os.path.join(os.environ["GISBASE"], "translation_status.json") + outfile = os.path.join(os.environ["GRASS_MISCDIR"], "translation_status.json") sys.exit(main(directory, outfile)) diff --git a/man/Makefile b/man/Makefile index 820b8a9f830..0f1b7321f94 100644 --- a/man/Makefile +++ b/man/Makefile @@ -81,35 +81,40 @@ manpages: define build GISBASE="$(RUN_GISBASE)" ARCH="$(ARCH)" ARCH_DISTDIR="$(ARCH_DISTDIR)" \ - MDDIR="${MDDIR}" \ + MDDIR="${MDDIR}" GRASS_DOCDIR="$(ARCH_DISTDIR)/docs/html" \ + GRASS_MKDOCSDIR="$(ARCH_DISTDIR)/docs/mkdocs" \ VERSION_NUMBER=$(GRASS_VERSION_NUMBER) VERSION_DATE=$(GRASS_VERSION_DATE) \ $(PYTHON) ./build_$(1).py $(2) endef define build_topics GISBASE="$(RUN_GISBASE)" ARCH="$(ARCH)" ARCH_DISTDIR="$(ARCH_DISTDIR)" \ - MDDIR="${MDDIR}" \ + MDDIR="${MDDIR}" GRASS_DOCDIR="$(ARCH_DISTDIR)/docs/html" \ + GRASS_MKDOCSDIR="$(ARCH_DISTDIR)/docs/mkdocs" \ VERSION_NUMBER=$(GRASS_VERSION_NUMBER) VERSION_DATE=$(GRASS_VERSION_DATE) \ $(PYTHON) ./build_topics.py endef define build_keywords GISBASE="$(RUN_GISBASE)" ARCH="$(ARCH)" ARCH_DISTDIR="$(ARCH_DISTDIR)" \ - MDDIR="${MDDIR}" \ + MDDIR="${MDDIR}" GRASS_DOCDIR="$(ARCH_DISTDIR)/docs/html" \ + GRASS_MKDOCSDIR="$(ARCH_DISTDIR)/docs/mkdocs" \ VERSION_NUMBER=$(GRASS_VERSION_NUMBER) VERSION_DATE=$(GRASS_VERSION_DATE) \ $(PYTHON) ./build_keywords.py endef define build_graphical_index GISBASE="$(RUN_GISBASE)" ARCH="$(ARCH)" ARCH_DISTDIR="$(ARCH_DISTDIR)" \ - MDDIR="${MDDIR}" \ + MDDIR="${MDDIR}" GRASS_DOCDIR="$(ARCH_DISTDIR)/docs/html" \ + GRASS_MKDOCSDIR="$(ARCH_DISTDIR)/docs/mkdocs" \ VERSION_NUMBER=$(GRASS_VERSION_NUMBER) VERSION_DATE=$(GRASS_VERSION_DATE) \ $(PYTHON) ./build_graphical_index.py endef define build_manual_gallery GISBASE="$(RUN_GISBASE)" ARCH="$(ARCH)" ARCH_DISTDIR="$(ARCH_DISTDIR)" \ - MDDIR="${MDDIR}" \ + MDDIR="${MDDIR}" GRASS_DOCDIR="$(ARCH_DISTDIR)/docs/html" \ + GRASS_MKDOCSDIR="$(ARCH_DISTDIR)/docs/mkdocs" \ VERSION_NUMBER=$(GRASS_VERSION_NUMBER) VERSION_DATE=$(GRASS_VERSION_DATE) \ $(PYTHON) ./build_manual_gallery.py endef @@ -117,13 +122,15 @@ endef define build_pso GISBASE="$(RUN_GISBASE)" ARCH="$(ARCH)" ARCH_DISTDIR="$(ARCH_DISTDIR)" \ VERSION_NUMBER=$(GRASS_VERSION_NUMBER) VERSION_DATE=$(GRASS_VERSION_DATE) \ + GRASS_DOCDIR="$(ARCH_DISTDIR)/docs/html" GRASS_MKDOCSDIR="$(ARCH_DISTDIR)/docs/mkdocs" \ $(PYTHON) ./parser_standard_options.py -t "$(GRASS_HOME)/lib/gis/parser_standard_options.c" \ -f "grass" -o "$(HTMLDIR)/parser_standard_options.html" -p 'id="opts_table" class="scroolTable"' endef define build_pso_md GISBASE="$(RUN_GISBASE)" ARCH="$(ARCH)" ARCH_DISTDIR="$(ARCH_DISTDIR)" \ - MDDIR="${MDDIR}" \ + MDDIR="${MDDIR}" GRASS_DOCDIR="$(ARCH_DISTDIR)/docs/html" \ + GRASS_MKDOCSDIR="$(ARCH_DISTDIR)/docs/mkdocs" \ VERSION_NUMBER=$(GRASS_VERSION_NUMBER) VERSION_DATE=$(GRASS_VERSION_DATE) \ $(PYTHON) ./parser_standard_options.py -t "$(GRASS_HOME)/lib/gis/parser_standard_options.c" \ -f "grass" -o "$(MDDIR)/source/parser_standard_options.md" @@ -136,11 +143,13 @@ $(MDDIR)/source/topics.md: $(ALL_MD) define build_class_graphical GISBASE="$(RUN_GISBASE)" ARCH="$(ARCH)" ARCH_DISTDIR="$(ARCH_DISTDIR)" \ VERSION_NUMBER=$(GRASS_VERSION_NUMBER) VERSION_DATE=$(GRASS_VERSION_DATE) \ + GRASS_DOCDIR="$(ARCH_DISTDIR)/docs/html" GRASS_MKDOCSDIR="$(ARCH_DISTDIR)/docs/mkdocs" \ $(PYTHON) ./build_class_graphical.py html $(HTMLDIR) endef define build_class_graphical_md GISBASE="$(RUN_GISBASE)" ARCH="$(ARCH)" ARCH_DISTDIR="$(ARCH_DISTDIR)" \ + GRASS_DOCDIR="$(ARCH_DISTDIR)/docs/html" GRASS_MKDOCSDIR="$(ARCH_DISTDIR)/docs/mkdocs" \ VERSION_NUMBER=$(GRASS_VERSION_NUMBER) VERSION_DATE=$(GRASS_VERSION_DATE) \ $(PYTHON) ./build_class_graphical.py md $(MDDIR)/source endef diff --git a/man/build_html.py b/man/build_html.py index 513ef0e2b6b..f2b44e848d3 100644 --- a/man/build_html.py +++ b/man/build_html.py @@ -432,6 +432,6 @@ def get_desc(cmd): ############################################################################ -man_dir = os.path.join(os.environ["ARCH_DISTDIR"], "docs", "html") +man_dir = os.path.join(os.environ["GRASS_DOCDIR"]) ############################################################################ diff --git a/man/build_md.py b/man/build_md.py index bb990c79016..b8e0d84e55d 100644 --- a/man/build_md.py +++ b/man/build_md.py @@ -94,6 +94,6 @@ def get_desc(cmd): ############################################################################ -man_dir = os.path.join(os.environ["ARCH_DISTDIR"], "docs", "mkdocs", "source") +man_dir = os.path.join(os.environ["GRASS_MKDOCSDIR"], "source") ############################################################################ diff --git a/man/build_rest.py b/man/build_rest.py old mode 100644 new mode 100755 index 6cabe95e4e6..592813a4232 --- a/man/build_rest.py +++ b/man/build_rest.py @@ -360,8 +360,8 @@ def get_desc(cmd): arch_dist_dir = os.environ["ARCH_DISTDIR"] rest_dir = os.path.join(arch_dist_dir, "docs", "rest") -gisbase = os.environ["GISBASE"] -ver = read_file(os.path.join(gisbase, "etc", "VERSIONNUMBER")) +grass_etc_dir = os.environ["GRASS_ETCDIR"] +ver = read_file(os.path.join(grass_etc_dir, "VERSIONNUMBER")) try: grass_version = ver.split()[0].strip() except IndexError: diff --git a/ps/ps.map/makeprocs.c b/ps/ps.map/makeprocs.c index ae0bcf9f196..71a0cf05b32 100644 --- a/ps/ps.map/makeprocs.c +++ b/ps/ps.map/makeprocs.c @@ -22,7 +22,7 @@ int make_procs(void) level = (PS.level != 1) ? 2 : 1; fprintf(PS.fp, "/level %d def\n", level); - snprintf(filename, sizeof(filename), "%s/etc/paint/prolog.ps", G_gisbase()); + snprintf(filename, sizeof(filename), "%s/paint/prolog.ps", G_etc_dir()); fp = fopen(filename, "r"); if (!fp) diff --git a/python/grass/__init__.py b/python/grass/__init__.py index 32717194453..d6701140677 100644 --- a/python/grass/__init__.py +++ b/python/grass/__init__.py @@ -68,8 +68,7 @@ def _translate(text): try: import gettext # pylint: disable=import-outside-toplevel - gisbase = os.environ["GISBASE"] - locale_dir = os.path.join(gisbase, "locale") + locale_dir = os.environ["GRASS_LOCALEDIR"] # With fallback set to True, not finding the translations files for # a language or domain results in a use of null translation, so this # does not raise an exception even if the locale settings is broken diff --git a/python/grass/app/Makefile b/python/grass/app/Makefile index e63287710d2..d38a528ea19 100644 --- a/python/grass/app/Makefile +++ b/python/grass/app/Makefile @@ -30,6 +30,18 @@ $(DSTDIR)/resource_paths.py: resource_paths.py -e 's#@GRASS_VERSION_NUMBER@#$(GRASS_VERSION_NUMBER)#' \ -e 's#@LD_LIBRARY_PATH_VAR@#$(LD_LIBRARY_PATH_VAR)#' \ -e 's#@START_UP@#$(GRASS_NAME)#' \ + -e 's#@GRASS_COLORSDIR@#etc/colors#' \ + -e 's#@GRASS_DOCDIR@#docs/html#' \ + -e 's#@GRASS_ETCBINDIR@#etc#' \ + -e 's#@GRASS_ETCDIR@#etc#' \ + -e 's#@GRASS_FONTSDIR@#fonts#' \ + -e 's#@GRASS_GRAPHICSDIR@#docs/html#' \ + -e 's#@GRASS_GUIRESDIR@#gui#' \ + -e 's#@GRASS_GUISCRIPTDIR@#gui/scripts#' \ + -e 's#@GRASS_GUIWXDIR@#gui/wxpython#' \ + -e 's#@GRASS_LOCALEDIR@#locale#' \ + -e 's#@GRASS_MISCDIR@##' \ + -e 's#@GRASS_MKDOCSDIR@#docs/mkdocs#' \ $< > $@ $(ARCH_DISTDIR)/resource_paths.py: diff --git a/python/grass/app/data.py b/python/grass/app/data.py index 2d8b64d4e5c..3c39669cba3 100644 --- a/python/grass/app/data.py +++ b/python/grass/app/data.py @@ -99,8 +99,7 @@ def _get_startup_location_in_distribution(): Returns startup location if found or None if nothing was found. """ - gisbase = os.getenv("GISBASE") - startup_location = os.path.join(gisbase, "demolocation") + startup_location = os.path.join(os.getenv("GRASS_MISCDIR"), "demolocation") # Find out if startup location exists if os.path.exists(startup_location): @@ -206,16 +205,16 @@ def acquire_mapset_lock( :param message_callback: callback to show messages when locked :param env: system environment variables - The function assumes the `GISBASE` variable is in the environment. The variable is - used to find the lock program. If *env* is not provided, - :external:py:data:`os.environ` is used. + The function assumes the `GRASS_ETCBINDIR` variable is in the environment. + The variable is used to find the lock program. If *env* is not provided, + `os.environ` is used. """ if process_id is None: process_id = os.getpid() if not env: env = os.environ lock_file = os.path.join(mapset_path, ".gislock") - locker_path = os.path.join(env["GISBASE"], "etc", "lock") + locker_path = os.path.join(env["GRASS_ETCBINDIR"], "lock") total_sleep = 0 try_number = 0 initial_sleep = min(initial_sleep, timeout) diff --git a/python/grass/app/resource_paths.py b/python/grass/app/resource_paths.py index df4721d5724..d19cb450981 100644 --- a/python/grass/app/resource_paths.py +++ b/python/grass/app/resource_paths.py @@ -21,3 +21,16 @@ GRASS_PREFIX = "@GRASS_PREFIX@" GISBASE = "@GISBASE_INSTALL_PATH@" + +GRASS_COLORSDIR = "@GRASS_COLORSDIR@" +GRASS_DOCDIR = "@GRASS_DOCDIR@" +GRASS_ETCBINDIR = "@GRASS_ETCBINDIR@" +GRASS_ETCDIR = "@GRASS_ETCDIR@" +GRASS_FONTSDIR = "@GRASS_FONTSDIR@" +GRASS_GRAPHICSDIR = "@GRASS_GRAPHICSDIR@" +GRASS_GUIRESDIR = "@GRASS_GUIRESDIR@" +GRASS_GUISCRIPTDIR = "@GRASS_GUISCRIPTDIR@" +GRASS_GUIWXDIR = "@GRASS_GUIWXDIR@" +GRASS_LOCALEDIR = "@GRASS_LOCALEDIR@" +GRASS_MISCDIR = "@GRASS_MISCDIR@" +GRASS_MKDOCSDIR = "@GRASS_MKDOCSDIR@" diff --git a/python/grass/app/runtime.py b/python/grass/app/runtime.py index c20cb215497..28c9f973512 100644 --- a/python/grass/app/runtime.py +++ b/python/grass/app/runtime.py @@ -35,6 +35,21 @@ def __init__(self, env=None): if env is None: env = os.environ self.env = env + if init_env_vars: + self._gisbase = self.gisbase + self._prefix = self.prefix + self._colors_dir = self.colors_dir + self._doc_dir = self.doc_dir + self._etcbin_dir = self.etcbin_dir + self._etc_dir = self.etc_dir + self._fonts_dir = self.fonts_dir + self._graphics_dir = self.graphics_dir + self._guires_dir = self.guires_dir + self._guiscript_dir = self.guiscript_dir + self._guiwx_dir = self.guiwx_dir + self._locale_dir = self.locale_dir + self._misc_dir = self.misc_dir + self._mkdocs_dir = self.mkdocs_dir @property def version(self): @@ -68,6 +83,54 @@ def gisbase(self): def prefix(self): return self.__get_dir("GRASS_PREFIX") + @property + def colors_dir(self): + return self.__get_dir("GRASS_COLORSDIR") + + @property + def doc_dir(self): + return self.__get_dir("GRASS_DOCDIR") + + @property + def etc_dir(self): + return self.__get_dir("GRASS_ETCDIR") + + @property + def etcbin_dir(self): + return self.__get_dir("GRASS_ETCBINDIR") + + @property + def fonts_dir(self): + return self.__get_dir("GRASS_FONTSDIR") + + @property + def graphics_dir(self): + return self.__get_dir("GRASS_GRAPHICSDIR") + + @property + def guires_dir(self): + return self.__get_dir("GRASS_GUIRESDIR") + + @property + def guiscript_dir(self): + return self.__get_dir("GRASS_GUISCRIPTDIR") + + @property + def guiwx_dir(self): + return self.__get_dir("GRASS_GUIWXDIR") + + @property + def locale_dir(self): + return self.__get_dir("GRASS_LOCALEDIR") + + @property + def misc_dir(self): + return self.__get_dir("GRASS_MISCDIR") + + @property + def mkdocs_dir(self): + return self.__get_dir("GRASS_MKDOCSDIR") + @property def config_projshare(self): return self.env.get("GRASS_PROJSHARE", res_paths.CONFIG_PROJSHARE) diff --git a/python/grass/docs/conf.py b/python/grass/docs/conf.py index bf26c0e74ef..d7939c2a1a3 100644 --- a/python/grass/docs/conf.py +++ b/python/grass/docs/conf.py @@ -19,58 +19,40 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -if not os.getenv("GISBASE"): - sys.exit("GISBASE not defined") -sys.path.insert( - 0, os.path.abspath(os.path.join(os.environ["GISBASE"], "etc", "python", "grass")) -) +if not os.getenv("GRASS_PYDIR"): + sys.exit("GRASS_PYDIR not defined") +sys.path.insert(0, os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass"))) sys.path.insert( 0, - os.path.abspath( - os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "ctypes") - ), + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "ctypes")), ) sys.path.insert( 0, - os.path.abspath( - os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "exceptions") - ), + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "exceptions")), ) sys.path.insert( 0, - os.path.abspath( - os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "gunittest") - ), + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "gunittest")), ) sys.path.insert( 0, - os.path.abspath( - os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "imaging") - ), + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "imaging")), ) sys.path.insert( 0, - os.path.abspath( - os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "pydispatch") - ), + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "pydispatch")), ) sys.path.insert( 0, - os.path.abspath( - os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "pygrass") - ), + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "pygrass")), ) sys.path.insert( 0, - os.path.abspath( - os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "script") - ), + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "script")), ) sys.path.insert( 0, - os.path.abspath( - os.path.join(os.environ["GISBASE"], "etc", "python", "grass", "temporal") - ), + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "temporal")), ) from grass.script import core # noqa: E402 diff --git a/python/grass/script/setup.py b/python/grass/script/setup.py index d2498beb4a1..3596ffa47f3 100644 --- a/python/grass/script/setup.py +++ b/python/grass/script/setup.py @@ -92,9 +92,6 @@ WINDOWS = sys.platform.startswith("win") MACOS = sys.platform.startswith("darwin") -VERSION_MAJOR = "@GRASS_VERSION_MAJOR@" -VERSION_MINOR = "@GRASS_VERSION_MINOR@" - def write_gisrc(dbase, location, mapset): """Write the ``gisrc`` file and return its path.""" @@ -108,7 +105,7 @@ def write_gisrc(dbase, location, mapset): def set_gui_path(): """Insert wxPython GRASS path to sys.path.""" - gui_path = os.path.join(os.environ["GISBASE"], "gui", "wxpython") + gui_path = os.environ["GRASS_GUIWXDIR"] if gui_path and gui_path not in sys.path: sys.path.insert(0, gui_path) @@ -222,17 +219,21 @@ def setup_runtime_env(gisbase=None, *, env=None): set_executable_paths, set_path_to_python_executable, set_python_path_variable, + RuntimePaths, ) + runtime_paths = RuntimePaths(env=env, init_env_vars=True) + gisbase = runtime_paths.gisbase # Set GISBASE - env["GISBASE"] = gisbase set_executable_paths( install_path=gisbase, - grass_config_dir=get_grass_config_dir(VERSION_MAJOR, VERSION_MINOR, env=env), + grass_config_dir=get_grass_config_dir( + runtime_paths.version_major, runtime_paths.version_minor, env=env + ), env=env, ) set_dynamic_library_path( - variable_name="@LD_LIBRARY_PATH_VAR@", install_path=gisbase, env=env + variable_name=runtime_paths.ld_library_path_var, install_path=gisbase, env=env ) set_python_path_variable(install_path=gisbase, env=env) set_path_to_python_executable(env=env) @@ -383,7 +384,7 @@ def init( # If environment is not provided, use the global one. if not env: env = os.environ - setup_runtime_env(grass_path, env=env) + setup_runtime_env(None, env=env) process_id = os.getpid() env["GIS_LOCK"] = str(process_id) @@ -574,9 +575,10 @@ def clean_temp(env=None): env = os.environ gs.verbose(_("Cleaning up temporary files..."), env=env) - gisbase = env["GISBASE"] call( - [os.path.join(gisbase, "etc", "clean_temp")], stdout=subprocess.DEVNULL, env=env + [os.path.join(env["GRASS_ETCBINDIR"], "clean_temp")], + stdout=subprocess.DEVNULL, + env=env, ) diff --git a/python/grass/script/task.py b/python/grass/script/task.py index 5483a858fbd..acafe1ade30 100644 --- a/python/grass/script/task.py +++ b/python/grass/script/task.py @@ -460,7 +460,7 @@ def get_interface_description(cmd): cmd = os.path.splitext(cmd)[0] if cmd == "d.rast3d": - sys.path.insert(0, os.path.join(os.getenv("GISBASE"), "gui", "scripts")) + sys.path.insert(0, os.getenv("GRASS_GUISCRIPTDIR")) p = Popen( [sys.executable, get_real_command(cmd), "--interface-description"], @@ -492,7 +492,7 @@ def get_interface_description(cmd): desc = convert_xml_to_utf8(cmdout) return desc.replace( b"grass-interface.dtd", - os.path.join(os.getenv("GISBASE"), "gui", "xml", "grass-interface.dtd").encode( + os.path.join(os.getenv("GRASS_GUIRESDIR"), "xml", "grass-interface.dtd").encode( "utf-8" ), ) diff --git a/python/grass/script/utils.py b/python/grass/script/utils.py index 34b9521bb6a..e776e735490 100644 --- a/python/grass/script/utils.py +++ b/python/grass/script/utils.py @@ -434,8 +434,8 @@ def get_lib_path(modname, libname=None): from os import getenv from os.path import isdir, join, sep - if isdir(join(getenv("GISBASE"), "etc", modname)): - path = join(os.getenv("GISBASE"), "etc", modname) + if isdir(join(getenv("GRASS_ETCDIR"), modname)): + path = join(os.getenv("GRASS_ETCDIR"), modname) elif ( getenv("GRASS_ADDON_BASE") and libname diff --git a/python/grass/semantic_label/reader.py b/python/grass/semantic_label/reader.py index d6538e5c940..282ea3e8edc 100644 --- a/python/grass/semantic_label/reader.py +++ b/python/grass/semantic_label/reader.py @@ -22,7 +22,7 @@ class SemanticLabelReader: def __init__(self): self._json_files = glob.glob( - os.path.join(os.environ["GISBASE"], "etc", "i.band.library", "*.json") + os.path.join(os.environ["GRASS_ETCDIR"], "i.band.library", "*.json") ) if not self._json_files: msg = "No semantic label definitions found" diff --git a/python/grass/temporal/core.py b/python/grass/temporal/core.py index 530239aa547..aedee29c32a 100644 --- a/python/grass/temporal/core.py +++ b/python/grass/temporal/core.py @@ -448,9 +448,7 @@ def get_tgis_database_string(): def get_sql_template_path(): - base = os.getenv("GISBASE") - base_etc = os.path.join(base, "etc") - return os.path.join(base_etc, "sql") + return os.path.join(os.getenv("GRASS_ETCDIR"), "sql") ############################################################################### diff --git a/python/libgrass_interface_generator/CMakeLists.txt b/python/libgrass_interface_generator/CMakeLists.txt index 2da6bfead17..74316a647ae 100644 --- a/python/libgrass_interface_generator/CMakeLists.txt +++ b/python/libgrass_interface_generator/CMakeLists.txt @@ -61,7 +61,6 @@ set(vedit_INCHDRS ${PostgreSQL_INCLUDE_DIRS} ${GDAL_INCLUDE_DIRS}) set(_c_flags ${CMAKE_C_FLAGS}) if(APPLE AND CMAKE_OSX_SYSROOT) string(APPEND _c_flags " --sysroot ${CMAKE_OSX_SYSROOT}") - message("_c_flags") endif() foreach(module ${MODULES}) diff --git a/raster/r.colors/edit_colors.c b/raster/r.colors/edit_colors.c index 6615854b581..5930da91d75 100644 --- a/raster/r.colors/edit_colors.c +++ b/raster/r.colors/edit_colors.c @@ -471,7 +471,7 @@ int edit_colors(int argc, char **argv, int type, const char *maptype, /* check if this style is a percentage style */ /* don't bother with native dirsep as not needed for backwards * compatibility */ - snprintf(path, GPATH_MAX, "%s/etc/colors/%s", G_gisbase(), style); + snprintf(path, GPATH_MAX, "%s/%s", G_colors_dir(), style); rule_is_percent = check_percent_rule(path); do_scale = 1; } @@ -491,7 +491,7 @@ int edit_colors(int argc, char **argv, int type, const char *maptype, /* don't bother with native dirsep as not needed for backwards * compatibility */ - snprintf(path, GPATH_MAX, "%s/etc/colors/%s", G_gisbase(), rules); + snprintf(path, GPATH_MAX, "%s/%s", G_colors_dir(), rules); if (!Rast_load_fp_colors(&colors, path, min, max)) G_fatal_error(_("Unable to load rules file <%s>"), rules); diff --git a/raster/r.watershed/front/main.c b/raster/r.watershed/front/main.c index 2d7cd0283bc..2ea844ab2b5 100644 --- a/raster/r.watershed/front/main.c +++ b/raster/r.watershed/front/main.c @@ -261,7 +261,7 @@ int main(int argc, char *argv[]) } /* Build command line */ - snprintf(command, GPATH_MAX, "%s/etc/r.watershed/%s", G_gisbase(), + snprintf(command, GPATH_MAX, "%s/r.watershed/%s", G_etcbin_dir(), flag_seg->answer ? "seg" : "ram"); new_argv[new_argc++] = command; diff --git a/raster/r.watershed/shed/com_line.c b/raster/r.watershed/shed/com_line.c index 287ed7d800e..de509eb21ea 100644 --- a/raster/r.watershed/shed/com_line.c +++ b/raster/r.watershed/shed/com_line.c @@ -53,8 +53,8 @@ int com_line_Gwater(INPUT *input, OUTPUT *output) input->fast = 1; input->com_line_ram = (char *)G_calloc(400, sizeof(char)); prog_name = G_store(RAM_NAME); - snprintf(input->com_line_ram, (400 * sizeof(char)), - "\"%s/etc/water/%s\"", G_gisbase(), RAM_NAME); + snprintf(input->com_line_ram, (400 * sizeof(char)), "\"%s/water/%s\"", + G_etc_dir(), RAM_NAME); fprintf(stderr, "\nIf there is not enough ram for the fast mode (%s) to run,\n", RAM_NAME); @@ -64,15 +64,15 @@ int com_line_Gwater(INPUT *input, OUTPUT *output) input->slow = 1; input->com_line_seg = (char *)G_calloc(400, sizeof(char)); snprintf(input->com_line_seg, (400, sizeof(char)), - "\"%s/etc/water/%s\"", G_gisbase(), SEG_NAME); + "\"%s/water/%s\"", G_etc_dir(), SEG_NAME); } } else { input->slow = 1; prog_name = G_store(SEG_NAME); input->com_line_seg = (char *)G_calloc(400, sizeof(char)); - snprintf(input->com_line_seg, (400, sizeof(char)), - "\"%s/etc/water/%s\"", G_gisbase(), SEG_NAME); + snprintf(input->com_line_seg, (400, sizeof(char)), "\"%s/water/%s\"", + G_etc_dir(), SEG_NAME); } G_message(_("\nIf you hit by itself for the next question, this")); diff --git a/scripts/d.polar/d.polar.py b/scripts/d.polar/d.polar.py index 2d5f6d50a7b..d133ff89d83 100755 --- a/scripts/d.polar/d.polar.py +++ b/scripts/d.polar/d.polar.py @@ -236,7 +236,7 @@ def plot_eps(psout): ########## outf = open(psout, "w") - prolog = os.path.join(os.environ["GISBASE"], "etc", "d.polar", "ps_defs.eps") + prolog = os.path.join(os.environ["GRASS_ETCDIR"], "d.polar", "ps_defs.eps") inf = open(prolog) shutil.copyfileobj(inf, outf) inf.close() diff --git a/scripts/db.test/db.test.py b/scripts/db.test/db.test.py index c4ed7d99c54..45a449ad29d 100755 --- a/scripts/db.test/db.test.py +++ b/scripts/db.test/db.test.py @@ -43,7 +43,7 @@ def main(): dbconn = grassdb.db_connection() gcore.message(_("Using DB driver: %s") % dbconn["driver"]) - infile = os.path.join(os.environ["GISBASE"], "etc", "db.test", test_file) + infile = os.path.join(os.environ["GRASS_ETCDIR"], "db.test", test_file) inf = open(infile) while True: diff --git a/scripts/g.extension/g.extension.py b/scripts/g.extension/g.extension.py old mode 100644 new mode 100755 index bfe5d33b861..6d4658ded49 --- a/scripts/g.extension/g.extension.py +++ b/scripts/g.extension/g.extension.py @@ -2355,7 +2355,7 @@ def check_style_file(name): If the files are missing, a warning is issued. """ - dist_file = os.path.join(os.getenv("GISBASE"), "docs", "html", name) + dist_file = os.path.join(os.getenv("GRASS_DOCDIR"), name) addons_file = os.path.join(options["prefix"], "docs", "html", name) try: diff --git a/scripts/g.search.modules/g.search.modules.py b/scripts/g.search.modules/g.search.modules.py index 76a8c4d1f3e..1fc84246cdd 100755 --- a/scripts/g.search.modules/g.search.modules.py +++ b/scripts/g.search.modules/g.search.modules.py @@ -196,8 +196,8 @@ def _search_module( :return dict: modules """ - WXGUIDIR = os.path.join(os.getenv("GISBASE"), "gui", "wxpython") - filename = os.path.join(WXGUIDIR, "xml", "module_items.xml") + GUIRESDIR = os.getenv("GRASS_GUIRESDIR") + filename = os.path.join(GUIRESDIR, "wxpython", "xml", "module_items.xml") with open(filename) as menudata_file: menudata = ET.parse(menudata_file) @@ -213,7 +213,7 @@ def _search_module( items.extend(addon_items) # add system-wide installed addons to modules list - filename_addons_s = os.path.join(os.getenv("GISBASE"), "modules.xml") + filename_addons_s = os.path.join(os.getenv("GRASS_MISCDIR"), "modules.xml") if os.path.isfile(filename_addons_s): with open(filename_addons_s) as addon_menudata_file_s: addon_menudata_s = ET.parse(addon_menudata_file_s) diff --git a/scripts/wxpyimgview/wxpyimgview.py b/scripts/wxpyimgview/wxpyimgview.py old mode 100644 new mode 100755 index ac848791c80..6702f23d24b --- a/scripts/wxpyimgview/wxpyimgview.py +++ b/scripts/wxpyimgview/wxpyimgview.py @@ -45,6 +45,5 @@ image = options["image"] percent = options["percent"] python = os.getenv("GRASS_PYTHON", "python") - gisbase = os.environ["GISBASE"] - script = os.path.join(gisbase, "etc", "wxpyimgview_gui.py") + script = os.path.join(os.environ["GRASS_ETCBINDIR"], "wxpyimgview_gui.py") os.execlp(python, script, script, image, percent) diff --git a/utils/thumbnails.py b/utils/thumbnails.py index c04c806bb13..8e670172919 100755 --- a/utils/thumbnails.py +++ b/utils/thumbnails.py @@ -158,7 +158,7 @@ def main(): os.environ["GRASS_OVERWRITE"] = "1" - color_dir = os.path.join(os.environ["GISBASE"], "etc", "colors") + color_dir = os.environ["GRASS_COLORSDIR"] output_dir = sys.argv[1] if not os.path.exists(output_dir): diff --git a/vector/v.label.sa/font.c b/vector/v.label.sa/font.c index 73dbaadf5e4..57593535ce9 100644 --- a/vector/v.label.sa/font.c +++ b/vector/v.label.sa/font.c @@ -54,7 +54,7 @@ struct GFONT_CAP *find_font_from_freetypecap(const char *font) capfile); } if (fp == NULL) { - snprintf(file, sizeof(file), "%s/etc/fontcap", G_gisbase()); + snprintf(file, sizeof(file), "%s/fontcap", G_etc_dir()); if ((fp = fopen(file, "r")) == NULL) G_warning(_("%s: No font definition file"), file); } From 27b9528dd523cd68e0557f983e1f2764ab0afbfa Mon Sep 17 00:00:00 2001 From: Nicklas Larsson Date: Mon, 28 Jul 2025 19:16:17 +0200 Subject: [PATCH 02/11] correct bad rebase --- python/grass/app/runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/grass/app/runtime.py b/python/grass/app/runtime.py index 28c9f973512..f2eb1945e48 100644 --- a/python/grass/app/runtime.py +++ b/python/grass/app/runtime.py @@ -31,7 +31,7 @@ class RuntimePaths: The resource paths are also set as environmental variables. """ - def __init__(self, env=None): + def __init__(self, env=None, init_env_vars=False): if env is None: env = os.environ self.env = env From ff20546ce19c5f075a1802f57886e42ac264c090 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Thu, 9 Oct 2025 12:37:12 -0400 Subject: [PATCH 03/11] Avoid list of variables in the constructor and in the methods, replace by a single dict object at the class level. Use dynamic attributes to avoid repeating the access code. Separate env modification from path retrieval as in #6469. --- python/grass/app/runtime.py | 133 +++++++----------- .../grass/app/tests/grass_app_runtime_test.py | 66 +++++++++ 2 files changed, 120 insertions(+), 79 deletions(-) create mode 100644 python/grass/app/tests/grass_app_runtime_test.py diff --git a/python/grass/app/runtime.py b/python/grass/app/runtime.py index f2eb1945e48..a7e8bc8ba95 100644 --- a/python/grass/app/runtime.py +++ b/python/grass/app/runtime.py @@ -28,28 +28,47 @@ class RuntimePaths: """Get runtime paths to resources and basic GRASS build properties - The resource paths are also set as environmental variables. + The resource paths are accessible as attributes (e.g., `.gisbase`, `.etc_dir`) + and can optionally be exported to environment variables. + + Example: + + >>> paths = RuntimePaths(init_env_vars=True) + >>> paths.etc_dir + '/usr/lib/grass/etc' + >>> os.environ["GRASS_ETCDIR"] + '/usr/lib/grass/etc' """ + # Mapping of attribute names to environment variable names + _env_vars = { + "gisbase": "GISBASE", + "prefix": "GRASS_PREFIX", + "colors_dir": "GRASS_COLORSDIR", + "doc_dir": "GRASS_DOCDIR", + "etc_dir": "GRASS_ETCDIR", + "etcbin_dir": "GRASS_ETCBINDIR", + "fonts_dir": "GRASS_FONTSDIR", + "graphics_dir": "GRASS_GRAPHICSDIR", + "guires_dir": "GRASS_GUIRESDIR", + "guiscript_dir": "GRASS_GUISCRIPTDIR", + "guiwx_dir": "GRASS_GUIWXDIR", + "locale_dir": "GRASS_LOCALEDIR", + "misc_dir": "GRASS_MISCDIR", + "mkdocs_dir": "GRASS_MKDOCSDIR", + } + def __init__(self, env=None, init_env_vars=False): if env is None: env = os.environ self.env = env if init_env_vars: - self._gisbase = self.gisbase - self._prefix = self.prefix - self._colors_dir = self.colors_dir - self._doc_dir = self.doc_dir - self._etcbin_dir = self.etcbin_dir - self._etc_dir = self.etc_dir - self._fonts_dir = self.fonts_dir - self._graphics_dir = self.graphics_dir - self._guires_dir = self.guires_dir - self._guiscript_dir = self.guiscript_dir - self._guiwx_dir = self.guiwx_dir - self._locale_dir = self.locale_dir - self._misc_dir = self.misc_dir - self._mkdocs_dir = self.mkdocs_dir + self.set_env_vars() + + def set_env_vars(self): + """Populate all GRASS-related environment variables.""" + for env_var in self._env_vars.values(): + self.env[env_var] = self.__get_dir(env_var) @property def version(self): @@ -75,78 +94,34 @@ def grass_exe_name(self): def grass_version_git(self): return res_paths.GRASS_VERSION_GIT - @property - def gisbase(self): - return self.__get_dir("GISBASE") - - @property - def prefix(self): - return self.__get_dir("GRASS_PREFIX") - - @property - def colors_dir(self): - return self.__get_dir("GRASS_COLORSDIR") - - @property - def doc_dir(self): - return self.__get_dir("GRASS_DOCDIR") - - @property - def etc_dir(self): - return self.__get_dir("GRASS_ETCDIR") - - @property - def etcbin_dir(self): - return self.__get_dir("GRASS_ETCBINDIR") - - @property - def fonts_dir(self): - return self.__get_dir("GRASS_FONTSDIR") - - @property - def graphics_dir(self): - return self.__get_dir("GRASS_GRAPHICSDIR") - - @property - def guires_dir(self): - return self.__get_dir("GRASS_GUIRESDIR") - - @property - def guiscript_dir(self): - return self.__get_dir("GRASS_GUISCRIPTDIR") - - @property - def guiwx_dir(self): - return self.__get_dir("GRASS_GUIWXDIR") - - @property - def locale_dir(self): - return self.__get_dir("GRASS_LOCALEDIR") - - @property - def misc_dir(self): - return self.__get_dir("GRASS_MISCDIR") - - @property - def mkdocs_dir(self): - return self.__get_dir("GRASS_MKDOCSDIR") - @property def config_projshare(self): return self.env.get("GRASS_PROJSHARE", res_paths.CONFIG_PROJSHARE) + def __getattr__(self, name): + """Access paths by attributes.""" + if name in self._env_vars: + env_var = self._env_vars[name] + return self.__get_dir(env_var) + msg = f"{type(self).__name__!r} has no attribute {name!r}" + raise AttributeError(msg) + + def __dir__(self): + """List both static and dynamic attributes.""" + base_dir = set(super().__dir__()) + dynamic_dir = set(self._env_vars.keys()) + return sorted(base_dir | dynamic_dir) + def __get_dir(self, env_var): """Get the directory stored in the environmental variable 'env_var' - If the environmental variable not yet set, it is retrived and + If the environmental variable not yet set, it is retrieved and set from resource_paths.""" - if env_var in self.env and len(self.env[env_var]) > 0: - res = os.path.normpath(self.env[env_var]) - else: - path = getattr(res_paths, env_var) - res = os.path.normpath(os.path.join(res_paths.GRASS_PREFIX, path)) - self.env[env_var] = res - return res + if env_var in self.env and self.env[env_var]: + return os.path.normpath(self.env[env_var]) + # Default to path from the installation + path = getattr(res_paths, env_var) + return os.path.normpath(os.path.join(res_paths.GRASS_PREFIX, path)) def get_grass_config_dir(major_version, minor_version, env): diff --git a/python/grass/app/tests/grass_app_runtime_test.py b/python/grass/app/tests/grass_app_runtime_test.py new file mode 100644 index 00000000000..7ed8dec2f1f --- /dev/null +++ b/python/grass/app/tests/grass_app_runtime_test.py @@ -0,0 +1,66 @@ +import os + +import pytest + +from grass.app.runtime import RuntimePaths + + +def test_attr_access_does_not_modify_env(): + """Accessing attribute should not change environment.""" + env = {} + rp = RuntimePaths(env=env) + assert "GRASS_COLORSDIR" not in env + value = rp.colors_dir # access the attribute + assert value + assert "GRASS_COLORSDIR" not in env, "env was modified unexpectedly" + + +def test_explicit_env_vars_set(): + """Explicit call should set the env vars.""" + env = {} + paths = RuntimePaths(env=env) + paths.set_env_vars() + assert "GRASS_COLORSDIR" in env + assert env["GRASS_COLORSDIR"] == paths.colors_dir + + +def test_constructor_parameter_env_vars_set(): + """Constructor with parameter should set the env vars.""" + env = {} + paths = RuntimePaths(env=env, init_env_vars=True) + assert "GRASS_COLORSDIR" in env + assert env["GRASS_COLORSDIR"] == paths.colors_dir + + +def test_dir_lists_dynamic_attributes_but_does_not_modify_env(): + """dir() should show dynamic attrs but not set env.""" + env = {} + paths = RuntimePaths(env=env) + listing = dir(paths) + assert "colors_dir" in listing + assert "GRASS_COLORSDIR" not in env, "dir() should not modify env" + + +def test_existing_env_value_is_respected(): + """If env already contains GRASS_COLORSDIR, its value is used.""" + value = "/custom/colors" + env = {"GRASS_COLORSDIR": value} + paths = RuntimePaths(env=env) + assert paths.colors_dir == os.path.normpath(value) + assert env["GRASS_COLORSDIR"] == value + + +def test_invalid_attribute_raises(): + """Unknown attribute access should raise AttributeError.""" + rp = RuntimePaths(env={}) + with pytest.raises(AttributeError): + assert rp.unknown_attr # avoiding unused value + + +def test_returned_attribute_consistent(): + """Repeated accesses should return the same value.""" + paths = RuntimePaths(env={}) + first = paths.colors_dir + second = paths.colors_dir + assert first == second + assert first == RuntimePaths(env={}).colors_dir From 83fac85af3533366497d9b6d3b81731b4997f317 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Fri, 10 Oct 2025 14:59:19 -0400 Subject: [PATCH 04/11] Manually sync to version of RuntimePaths from #6482 --- lib/init/grass.py | 11 ++++++-- python/grass/app/runtime.py | 52 +++++++++++++++++++++++------------- python/grass/script/setup.py | 24 ++++++++--------- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/lib/init/grass.py b/lib/init/grass.py index fae0b5a2640..a5d01824450 100755 --- a/lib/init/grass.py +++ b/lib/init/grass.py @@ -2118,6 +2118,7 @@ def main() -> None: find_grass_python_package() from grass.app.runtime import RuntimePaths + from grass.script.setup import get_install_path global \ CMD_NAME, \ @@ -2129,14 +2130,20 @@ def main() -> None: GISBASE, \ CONFIG_PROJSHARE - runtime_paths = RuntimePaths(init_env_vars=True) + runtime_paths = RuntimePaths(set_env_variables=True) CMD_NAME = runtime_paths.grass_exe_name GRASS_VERSION = runtime_paths.version GRASS_VERSION_MAJOR = runtime_paths.version_major GRASS_VERSION_MINOR = runtime_paths.version_minor LD_LIBRARY_PATH_VAR = runtime_paths.ld_library_path_var GRASS_VERSION_GIT = runtime_paths.grass_version_git - GISBASE = runtime_paths.gisbase + gisbase = runtime_paths.gisbase + if not os.path.isdir(gisbase): + gisbase = get_install_path(gisbase) + # Set the main prefix again. + # See also grass.script.setup.setup_runtime_env. + runtime_paths = RuntimePaths(set_env_variables=True, prefix=gisbase) + GISBASE = gisbase CONFIG_PROJSHARE = runtime_paths.config_projshare grass_config_dir = create_grass_config_dir() diff --git a/python/grass/app/runtime.py b/python/grass/app/runtime.py index a7e8bc8ba95..c523df512e7 100644 --- a/python/grass/app/runtime.py +++ b/python/grass/app/runtime.py @@ -17,7 +17,7 @@ import subprocess import sys -from . import resource_paths as res_paths +from . import resource_paths # Get the system name WINDOWS = sys.platform.startswith("win") @@ -33,14 +33,14 @@ class RuntimePaths: Example: - >>> paths = RuntimePaths(init_env_vars=True) + >>> paths = RuntimePaths(set_env_variables=True) >>> paths.etc_dir '/usr/lib/grass/etc' >>> os.environ["GRASS_ETCDIR"] '/usr/lib/grass/etc' """ - # Mapping of attribute names to environment variable names + # Mapping of attribute names to environment variable name except the prefix. _env_vars = { "gisbase": "GISBASE", "prefix": "GRASS_PREFIX", @@ -58,45 +58,59 @@ class RuntimePaths: "mkdocs_dir": "GRASS_MKDOCSDIR", } - def __init__(self, env=None, init_env_vars=False): + def __init__(self, *, env=None, set_env_variables=False, prefix=None): if env is None: env = os.environ self.env = env - if init_env_vars: - self.set_env_vars() + if prefix: + self._custom_prefix = os.path.normpath(prefix) + self._custom_prefix = self._custom_prefix.removesuffix( + resource_paths.GISBASE + ) + else: + self._custom_prefix = None + if set_env_variables: + self.set_env_variables() - def set_env_vars(self): + def set_env_variables(self): """Populate all GRASS-related environment variables.""" + self.env["GRASS_PREFIX"] = self.prefix for env_var in self._env_vars.values(): - self.env[env_var] = self.__get_dir(env_var) + self.env[env_var] = self.__get_dir(env_var, use_env_values=False) @property def version(self): - return res_paths.GRASS_VERSION + return resource_paths.GRASS_VERSION @property def version_major(self): - return res_paths.GRASS_VERSION_MAJOR + return resource_paths.GRASS_VERSION_MAJOR @property def version_minor(self): - return res_paths.GRASS_VERSION_MINOR + return resource_paths.GRASS_VERSION_MINOR @property def ld_library_path_var(self): - return res_paths.LD_LIBRARY_PATH_VAR + return resource_paths.LD_LIBRARY_PATH_VAR @property def grass_exe_name(self): - return res_paths.GRASS_EXE_NAME + return resource_paths.GRASS_EXE_NAME @property def grass_version_git(self): - return res_paths.GRASS_VERSION_GIT + return resource_paths.GRASS_VERSION_GIT @property def config_projshare(self): - return self.env.get("GRASS_PROJSHARE", res_paths.CONFIG_PROJSHARE) + return self.env.get("GRASS_PROJSHARE", resource_paths.CONFIG_PROJSHARE) + + @property + def prefix(self): + if self._custom_prefix: + return self._custom_prefix + return os.path.normpath(resource_paths.GRASS_PREFIX) def __getattr__(self, name): """Access paths by attributes.""" @@ -112,16 +126,16 @@ def __dir__(self): dynamic_dir = set(self._env_vars.keys()) return sorted(base_dir | dynamic_dir) - def __get_dir(self, env_var): + def __get_dir(self, env_var, *, use_env_values=True): """Get the directory stored in the environmental variable 'env_var' If the environmental variable not yet set, it is retrieved and set from resource_paths.""" - if env_var in self.env and self.env[env_var]: + if use_env_values and env_var in self.env and self.env[env_var]: return os.path.normpath(self.env[env_var]) # Default to path from the installation - path = getattr(res_paths, env_var) - return os.path.normpath(os.path.join(res_paths.GRASS_PREFIX, path)) + path = getattr(resource_paths, env_var) + return os.path.normpath(os.path.join(self.prefix, path)) def get_grass_config_dir(major_version, minor_version, env): diff --git a/python/grass/script/setup.py b/python/grass/script/setup.py index 3596ffa47f3..fb67d73b7ff 100644 --- a/python/grass/script/setup.py +++ b/python/grass/script/setup.py @@ -203,16 +203,6 @@ def setup_runtime_env(gisbase=None, *, env=None): If _gisbase_ is not provided, a heuristic is used to find the path to GRASS installation (see the :func:`get_install_path` function for details). """ - if not gisbase: - gisbase = get_install_path() - - # Accept Path objects. - gisbase = os.fspath(gisbase) - - # If environment is not provided, use the global one. - if not env: - env = os.environ - from grass.app.runtime import ( get_grass_config_dir, set_dynamic_library_path, @@ -222,9 +212,19 @@ def setup_runtime_env(gisbase=None, *, env=None): RuntimePaths, ) - runtime_paths = RuntimePaths(env=env, init_env_vars=True) + # If environment is not provided, use the global one. + if not env: + env = os.environ + + runtime_paths = RuntimePaths(env=env, prefix=gisbase) gisbase = runtime_paths.gisbase - # Set GISBASE + if not os.path.isdir(gisbase): + gisbase = get_install_path(gisbase) + # Set the main prefix again. + # See also the main grass executable code. + runtime_paths = RuntimePaths(env=env, prefix=gisbase) + runtime_paths.set_env_variables() + set_executable_paths( install_path=gisbase, grass_config_dir=get_grass_config_dir( From 9cc3fb52510b83b813e7b6f16e8bc9cb6e5956b5 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Fri, 17 Oct 2025 22:57:47 -0400 Subject: [PATCH 05/11] Fix and add missing lines from merge --- python/grass/app/tests/grass_app_runtime_test.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/python/grass/app/tests/grass_app_runtime_test.py b/python/grass/app/tests/grass_app_runtime_test.py index 8a0166a5465..33608db983c 100644 --- a/python/grass/app/tests/grass_app_runtime_test.py +++ b/python/grass/app/tests/grass_app_runtime_test.py @@ -88,6 +88,7 @@ def test_attr_access_does_not_modify_env_colors(): def test_attr_access_does_not_modify_env_gisbase(): """Accessing attribute should not change environment.""" + env = {} paths = RuntimePaths(env=env) assert "GISBASE" not in env value = paths.gisbase # access the attribute @@ -99,13 +100,15 @@ def test_explicit_env_vars_set_colors(): """Explicit call should set the env vars.""" env = {} paths = RuntimePaths(env=env) - paths.set_env_vars() + paths.set_env_variables() + assert env["GRASS_COLORSDIR"] == paths.colors_dir assert "GRASS_COLORSDIR" in env def test_explicit_env_vars_set_gisbase(): """Explicit call should set the env vars.""" - assert env["GRASS_COLORSDIR"] == paths.colors_dir + env = {} + paths = RuntimePaths(env=env) paths.set_env_variables() assert "GISBASE" in env assert env["GISBASE"] == paths.gisbase @@ -114,13 +117,14 @@ def test_explicit_env_vars_set_gisbase(): def test_constructor_parameter_env_vars_set_colors(): """Constructor with parameter should set the env vars.""" env = {} - paths = RuntimePaths(env=env, init_env_vars=True) + paths = RuntimePaths(env=env, set_env_variables=True) assert "GRASS_COLORSDIR" in env assert env["GRASS_COLORSDIR"] == paths.colors_dir def test_constructor_parameter_env_vars_set_gisenv(): """Constructor with parameter should set the env vars.""" + env = {} paths = RuntimePaths(env=env, set_env_variables=True) assert "GISBASE" in env assert env["GISBASE"] == paths.gisbase @@ -143,7 +147,7 @@ def test_dir_lists_dynamic_attributes_but_does_not_modify_env_gisbase(): assert "gisbase" in listing assert "GISBASE" not in env, "dir() should not modify env" - + def test_existing_env_value_is_respected_colors(): """If env already contains GRASS_COLORSDIR, its value is used.""" value = "/custom/colors" @@ -177,7 +181,7 @@ def test_returned_attribute_consistent_colors(): assert first == second assert first == RuntimePaths(env={}).colors_dir - + def test_returned_attribute_consistent_gisbase(): """Repeated accesses should return the same value.""" paths = RuntimePaths(env={}) From 268cbcd49c999116cb5674993ae501d7f502ba34 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Fri, 17 Oct 2025 23:04:46 -0400 Subject: [PATCH 06/11] Pass the initial path to setup runtime as done on main (relevant when it is a user-provided path) --- python/grass/script/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/grass/script/setup.py b/python/grass/script/setup.py index b6e9127d3aa..e6b93b514c5 100644 --- a/python/grass/script/setup.py +++ b/python/grass/script/setup.py @@ -397,7 +397,7 @@ def init( # If environment is not provided, use the global one. if not env: env = os.environ - setup_runtime_env(None, env=env) + setup_runtime_env(grass_path, env=env) process_id = os.getpid() env["GIS_LOCK"] = str(process_id) From cfd4dfe448ad56b4f3aea2b7dcde4e10cf607d0e Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Fri, 17 Oct 2025 23:42:03 -0400 Subject: [PATCH 07/11] Always fail when g.proj gives non-zero return code (should be moved to a separate PR) --- python/grass/script/core.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/python/grass/script/core.py b/python/grass/script/core.py index 10ed0a43c1c..0e814082bd6 100644 --- a/python/grass/script/core.py +++ b/python/grass/script/core.py @@ -2107,8 +2107,15 @@ def local_env(): try_remove(tmp_gisrc) tmp_gisrc = None - if ps.returncode != 0 and error: - raise ScriptError(repr(error)) + if ps.returncode != 0: + raise ScriptError( + repr(error) + if error + else ( + f"g.proj failed with return code {ps.returncode}, " + "but no error message was produced" + ) + ) # If a session was created for messages, but not used for subprocesses, # we still need to clean it up. From d03ad091747bc3f5e6ee3ca15f651bca9fb48b23 Mon Sep 17 00:00:00 2001 From: Nicklas Larsson Date: Mon, 15 Jun 2026 10:19:04 +0200 Subject: [PATCH 08/11] add GRASS_MISCDIR for locale/grass_po_stats.py; fix install dir for fontcap (FHS) --- CMakeLists.txt | 2 ++ cmake/modules/GRASSInstallDirs.cmake | 1 + general/CMakeLists.txt | 8 ++++---- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b968f2db1c5..a10e0077e6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,6 +233,7 @@ if(WIN32) "GRASS_GUISCRIPTDIR=${RUNTIME_GRASS_GUISCRIPTDIR}" "GRASS_GUIWXDIR=${GUI_WXPYTHON_DIR}" "GRASS_LOCALEDIR=${RUNTIME_GRASS_LOCALEDIR}" + "GRASS_MISCDIR=${RUNTIME_GRASS_MISCDIR}" "GRASS_MKDOCSDIR=${RUNTIME_GRASS_MKDOCSDIR}" "ARCH=${BUILD_ARCH}" "ARCH_DISTDIR=${RUN_GISBASE_NATIVE}" "LANGUAGE=C" "MODULE_TOPDIR=${MODULE_TOPDIR}" "HTMLDIR=${DOC_DIR}" @@ -256,6 +257,7 @@ else() "GRASS_GUISCRIPTDIR=${RUNTIME_GRASS_GUISCRIPTDIR}" "GRASS_GUIWXDIR=${GUI_WXPYTHON_DIR}" "GRASS_LOCALEDIR=${RUNTIME_GRASS_LOCALEDIR}" + "GRASS_MISCDIR=${RUNTIME_GRASS_MISCDIR}" "GRASS_MKDOCSDIR=${RUNTIME_GRASS_MKDOCSDIR}" "ARCH=${BUILD_ARCH}" "ARCH_DISTDIR=${RUN_GISBASE_NATIVE}" "LANGUAGE=C" "MODULE_TOPDIR=${MODULE_TOPDIR}" "HTMLDIR=${DOC_DIR}" diff --git a/cmake/modules/GRASSInstallDirs.cmake b/cmake/modules/GRASSInstallDirs.cmake index 31cb3bb64da..6afc1fa2e78 100644 --- a/cmake/modules/GRASSInstallDirs.cmake +++ b/cmake/modules/GRASSInstallDirs.cmake @@ -114,6 +114,7 @@ set(RUNTIME_GRASS_LOCALEDIR "${OUTDIR}/${GRASS_INSTALL_LOCALEDIR}") set(RUNTIME_GRASS_GUIRESDIR "${OUTDIR}/${GRASS_INSTALL_GUIDIR}") set(RUNTIME_GRASS_GUISCRIPTDIR "${OUTDIR}/${GRASS_INSTALL_GUISCRIPTDIR}") set(RUNTIME_GRASS_DOCDIR "${OUTDIR}/${GRASS_INSTALL_DOCDIR}") +set(RUNTIME_GRASS_MISCDIR "${OUTDIR}/${GRASS_INSTALL_MISCDIR}") set(RUNTIME_GRASS_MKDOCSDIR "${OUTDIR}/${GRASS_INSTALL_MKDOCSDIR}") set(RUNTIME_GRASS_FONTSDIR "${OUTDIR}/${GRASS_INSTALL_FONTSDIR}") set(RUNTIME_GRASS_COLORSDIR "${OUTDIR}/${GRASS_INSTALL_COLORSDIR}") diff --git a/general/CMakeLists.txt b/general/CMakeLists.txt index f61c1131d13..c4386fb7028 100644 --- a/general/CMakeLists.txt +++ b/general/CMakeLists.txt @@ -12,14 +12,14 @@ build_program_in_subdir(g.mkfontcap DEPENDS grass_gis PRIMARY_DEPENDS Freetype::Freetype) add_dependencies(g.mkfontcap fonts) -# TODO(FHS): revert GRASS_INSTALL_ETCBINDIR to GRASS_INSTALL_ETCDIR +# TODO(FHS): rewire ${OUTDIR} path to ${PREFIX} in installed version of fontcap add_custom_command( TARGET g.mkfontcap POST_BUILD COMMAND ${grass_env_command} ${OUTDIR}/${GRASS_INSTALL_BINDIR}/g.mkfontcap -s - > ${OUTDIR}/${GRASS_INSTALL_ETCBINDIR}/fontcap) -install(FILES ${OUTDIR}/${GRASS_INSTALL_ETCBINDIR}/fontcap - DESTINATION ${GRASS_INSTALL_ETCBINDIR}) + > ${OUTDIR}/${GRASS_INSTALL_ETCDIR}/fontcap) +install(FILES ${OUTDIR}/${GRASS_INSTALL_ETCDIR}/fontcap + DESTINATION ${GRASS_INSTALL_ETCDIR}) build_program_in_subdir(g.parser DEPENDS grass_gis) build_program_in_subdir(g.pnmcomp DEPENDS grass_gis) build_program_in_subdir(g.ppmtopng DEPENDS grass_gis PRIMARY_DEPENDS PNG::PNG) From 2ee5faae4a2285302f5a033375e09045ab90accd Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Sat, 4 Jul 2026 06:17:26 -0400 Subject: [PATCH 09/11] libgis: Avoid infinite recursion in G_init_locale on missing variable G_locale_dir() calls G_fatal_error() when GRASS_LOCALEDIR is not set, and G_fatal_error() translates its message, which re-enters G_init_locale() and recurses until the stack overflows. Read the variable directly and skip bindtextdomain() when it is not set, matching the pre-existing behavior for GISBASE. Likely the cause of the g.proj stack overflow (0xC00000FD) seen on Windows CI in the FHS preparation PR. Found and fixed with Claude Code. --- lib/gis/locale.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/gis/locale.c b/lib/gis/locale.c index f1aabf31f71..16b9acd289c 100644 --- a/lib/gis/locale.c +++ b/lib/gis/locale.c @@ -39,7 +39,9 @@ void G_init_locale(void) #ifdef LC_MESSAGES setlocale(LC_MESSAGES, ""); #endif - const char *localedir = G_locale_dir(); + /* Not using G_locale_dir(): its G_fatal_error() would translate the + message and re-enter G_init_locale(), recursing without bound. */ + const char *localedir = getenv("GRASS_LOCALEDIR"); if (localedir && *localedir) { bindtextdomain("grasslibs", localedir); From 8af2d2a73f810c53fbd9c1968fc073624eb6263d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 15:14:57 +0000 Subject: [PATCH 10/11] grass.script: Set up resource paths when only GISBASE is set runtime_env_is_active() treated GISBASE on PATH as an active runtime, so ensure_runtime_env() skipped the setup when a wrapper script (such as the OSGeo4W env.bat) set only GISBASE. The GRASS_*DIR variables were then missing and tools failed with "Incomplete GRASS session: Variable 'GRASS_ETCDIR' not set", e.g., in create_project(). Require all resource directory variables for an active runtime, and derive them from an existing GISBASE instead of the build-time prefix, which points elsewhere for a relocated installation. Investigated and written with the help of Claude Code. --- python/grass/app/runtime.py | 5 +++ python/grass/script/setup.py | 14 ++++++-- .../script/tests/grass_script_setup_test.py | 34 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/python/grass/app/runtime.py b/python/grass/app/runtime.py index 2628cb91316..e97b65069e9 100644 --- a/python/grass/app/runtime.py +++ b/python/grass/app/runtime.py @@ -76,6 +76,11 @@ def set_env_variables(self): for env_var in self._env_vars.values(): self.env[env_var] = self.__get_dir(env_var, use_env_values=False) + @classmethod + def env_variable_names(cls): + """Return names of the environment variables holding the resource paths.""" + return list(cls._env_vars.values()) + @property def version(self): return resource_paths.GRASS_VERSION diff --git a/python/grass/script/setup.py b/python/grass/script/setup.py index f5b7e76a0c8..7c6558b991a 100644 --- a/python/grass/script/setup.py +++ b/python/grass/script/setup.py @@ -207,7 +207,8 @@ def setup_runtime_env(gisbase=None, *, env=None): provided, modifies the global environment (os.environ). Pass a copy of the environment if you don't want the source environment modified. - If _gisbase_ is not provided, a heuristic is used to find the path to GRASS + If _gisbase_ is not provided, GISBASE from the environment is used if set. + Otherwise, a heuristic is used to find the path to GRASS installation (see the :func:`get_install_path` function for details). """ from grass.app.runtime import ( @@ -227,7 +228,10 @@ def setup_runtime_env(gisbase=None, *, env=None): # Remove mapset-specific variables that should not leak into the # runtime environment being set up. sanitize_mapset_environment(env) - runtime_paths = RuntimePaths(env=env, prefix=gisbase) + # Derive the resource paths from an existing GISBASE rather than from + # the build-time prefix, which is wrong for a relocated installation + # (e.g., a copied distribution or a custom OSGeo4W root). + runtime_paths = RuntimePaths(env=env, prefix=gisbase or env.get("GISBASE")) gisbase = runtime_paths.gisbase if not Path(gisbase).is_dir(): gisbase = get_install_path(gisbase) @@ -262,11 +266,17 @@ def runtime_env_is_active(env=None): If *env* is not provided, uses the global environment (os.environ). """ + from grass.app.runtime import RuntimePaths + if not env: env = os.environ gisbase = env.get("GISBASE") if not gisbase: return False + # GISBASE alone (e.g., set by a wrapper script) is not enough because + # tools read the resource directories from their own variables. + if not all(env.get(name) for name in RuntimePaths.env_variable_names()): + return False # Check also path to tools. return gisbase in env["PATH"] diff --git a/python/grass/script/tests/grass_script_setup_test.py b/python/grass/script/tests/grass_script_setup_test.py index 454ee04d736..fc72b38fe41 100644 --- a/python/grass/script/tests/grass_script_setup_test.py +++ b/python/grass/script/tests/grass_script_setup_test.py @@ -13,6 +13,7 @@ import grass.script as gs from grass.exceptions import CalledModuleError from grass.app.data import MapsetLockingException +from grass.app.runtime import RuntimePaths RUNTIME_GISBASE_SHOULD_BE_PRESENT = "Runtime (GISBASE) should be present" SESSION_FILE_NOT_DELETED = "Session file not deleted" @@ -469,6 +470,39 @@ def test_grass_path_types_in_setup(tmp_path, path_type): assert "GISBASE" in env +def test_runtime_env_with_only_gisbase_is_not_active(): + """GISBASE on PATH without the resource directory variables is not enough""" + gisbase = gs.setup.get_install_path() + env = os.environ.copy() + for name in RuntimePaths.env_variable_names(): + env.pop(name, None) + env["GISBASE"] = gisbase + env["PATH"] = os.pathsep.join([os.path.join(gisbase, "bin"), env["PATH"]]) + assert not gs.setup.runtime_env_is_active(env=env) + gs.setup.ensure_runtime_env(env=env) + assert gs.setup.runtime_env_is_active(env=env) + assert Path(env["GRASS_ETCDIR"]).is_dir() + + +def test_setup_runtime_env_uses_gisbase_from_env(tmp_path): + """Resource paths are derived from GISBASE, not from the build-time prefix""" + paths = RuntimePaths(env={}) + relocated_prefix = tmp_path / "relocated" + try: + relocated_prefix.symlink_to(paths.prefix, target_is_directory=True) + except OSError: + pytest.skip("Creating symbolic links is not permitted") + gisbase = os.path.normpath( + relocated_prefix / os.path.relpath(paths.gisbase, paths.prefix) + ) + env = os.environ.copy() + env["GISBASE"] = gisbase + gs.setup.setup_runtime_env(env=env) + assert env["GISBASE"] == gisbase + assert Path(env["GRASS_ETCDIR"]).is_relative_to(relocated_prefix) + assert Path(env["GRASS_ETCDIR"]).is_dir() + + @pytest.mark.parametrize("variable", ["GRASS_REGION", "WIND_OVERRIDE", "GRASS_MASK"]) @pytest.mark.usefixtures("mock_no_session") def test_init_removes_mapset_variables(tmp_path, variable): From 3c8cf2d121230b9bad855200f67ddaf4847590d4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 15:31:26 +0000 Subject: [PATCH 11/11] libgis: Fall back to GISBASE for unset resource directories Tools run directly by a program which sets only GISBASE and GISRC failed with "Incomplete GRASS session: Variable 'GRASS_ETCDIR' not set" because G_etc_dir() and the other resource directory getters required their GRASS_*DIR variable. When the variable is not set, use GISBASE with a path relative to it. CMake computes the relative paths from the install layout, so they also work with FHS. The defaults are the legacy layout, which is the only one Autotools produces. G_init_locale() gets the same fallback so that translations keep working. Investigated and written with the help of Claude Code. --- lib/gis/CMakeLists.txt | 17 ++++++ lib/gis/locale.c | 18 ++++++- lib/gis/resource_dirs.c | 60 +++++++++++++++++---- lib/gis/resource_dirs.h | 32 +++++++++++ lib/gis/tests/lib_gis_resource_dirs_test.py | 27 ++++++++++ 5 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 lib/gis/resource_dirs.h create mode 100644 lib/gis/tests/lib_gis_resource_dirs_test.py diff --git a/lib/gis/CMakeLists.txt b/lib/gis/CMakeLists.txt index 90ebb135a65..768a64d48b1 100644 --- a/lib/gis/CMakeLists.txt +++ b/lib/gis/CMakeLists.txt @@ -5,6 +5,23 @@ if(NOT WIN32) endif() set(grass_gis_DEFS "-DGRASS_VERSION_DATE=\"${GRASS_VERSION_DATE}\"") +# Resource directories relative to GISBASE, used when the corresponding +# GRASS_*DIR variable is not set (see resource_dirs.h). +foreach( + dir + COLORSDIR + ETCBINDIR + ETCDIR + FONTSDIR + LOCALEDIR +) + cmake_path( + RELATIVE_PATH GRASS_INSTALL_${dir} + BASE_DIRECTORY "${GISBASE_DIR}" + OUTPUT_VARIABLE rel_dir + ) + list(APPEND grass_gis_DEFS "-DGRASS_${dir}_REL=\"${rel_dir}\"") +endforeach() if(MSVC) set(grass_gis_DEFS "${grass_gis_DEFS};-D_USE_MATH_DEFINES=1") set(gislib_INCLUDES "${PROJECT_SOURCE_DIR}/msvc") diff --git a/lib/gis/locale.c b/lib/gis/locale.c index 806f4dd7403..0b96e6436ae 100644 --- a/lib/gis/locale.c +++ b/lib/gis/locale.c @@ -12,12 +12,15 @@ */ #include +#include #include #include #include #include #include +#include "resource_dirs.h" + #if defined(HAVE_LIBINTL_H) && defined(USE_NLS) #define NO_NLS_UNUSED #else @@ -40,8 +43,21 @@ void G_init_locale(void) /* Not using G_locale_dir(): its G_fatal_error() would translate the message and re-enter G_init_locale(), recursing without bound. */ const char *localedir = getenv("GRASS_LOCALEDIR"); + char fallback[GPATH_MAX]; + + if (!localedir || !*localedir) { + /* Same fallback as G_locale_dir(). */ + const char *gisbase = getenv("GISBASE"); + + localedir = NULL; + if (gisbase && *gisbase) { + snprintf(fallback, sizeof(fallback), "%s/%s", gisbase, + GRASS_LOCALEDIR_REL); + localedir = fallback; + } + } - if (localedir && *localedir) { + if (localedir) { bindtextdomain("grasslibs", localedir); bindtextdomain("grassmods", localedir); } diff --git a/lib/gis/resource_dirs.c b/lib/gis/resource_dirs.c index 8602ceefe49..8ac75e4c16a 100644 --- a/lib/gis/resource_dirs.c +++ b/lib/gis/resource_dirs.c @@ -10,44 +10,84 @@ SPDX-License-Identifier: GPL-2.0-or-later */ +#include #include #include #include -static const char *get_g_env(const char *); +#include "resource_dirs.h" + +static const char *get_g_env(const char *, const char *, int *, char **); const char *G_colors_dir(void) { - return get_g_env("GRASS_COLORSDIR"); + static int initialized; + static char *fallback; + + return get_g_env("GRASS_COLORSDIR", GRASS_COLORSDIR_REL, &initialized, + &fallback); } const char *G_etcbin_dir(void) { - return get_g_env("GRASS_ETCBINDIR"); + static int initialized; + static char *fallback; + + return get_g_env("GRASS_ETCBINDIR", GRASS_ETCBINDIR_REL, &initialized, + &fallback); } const char *G_etc_dir(void) { - return get_g_env("GRASS_ETCDIR"); + static int initialized; + static char *fallback; + + return get_g_env("GRASS_ETCDIR", GRASS_ETCDIR_REL, &initialized, &fallback); } const char *G_fonts_dir(void) { - return get_g_env("GRASS_FONTSDIR"); + static int initialized; + static char *fallback; + + return get_g_env("GRASS_FONTSDIR", GRASS_FONTSDIR_REL, &initialized, + &fallback); } const char *G_locale_dir(void) { - return get_g_env("GRASS_LOCALEDIR"); + static int initialized; + static char *fallback; + + return get_g_env("GRASS_LOCALEDIR", GRASS_LOCALEDIR_REL, &initialized, + &fallback); } -static const char *get_g_env(const char *env_var) +/* Return the value of env_var. If it is not set, e.g., when a tool is run + directly by a program which sets only GISBASE, return GISBASE/rel_path, + built on the first call and kept in *fallback. */ +static const char *get_g_env(const char *env_var, const char *rel_path, + int *initialized, char **fallback) { const char *value = getenv(env_var); - if (value) + if (value && *value) return value; - G_fatal_error(_("Incomplete GRASS session: Variable '%s' not set"), - env_var); + /* Not using G_gisbase(): its G_getenv() would fail with a message about + GISBASE rather than about env_var. */ + const char *gisbase = getenv("GISBASE"); + if (!gisbase || !*gisbase) + G_fatal_error(_("Incomplete GRASS session: Variable '%s' not set"), + env_var); + + if (!G_is_initialized(initialized)) { + char path[GPATH_MAX]; + + snprintf(path, sizeof(path), "%s/%s", gisbase, rel_path); + *fallback = G_store(path); + G_initialize_done(initialized); + } + + return *fallback; } diff --git a/lib/gis/resource_dirs.h b/lib/gis/resource_dirs.h new file mode 100644 index 00000000000..c6f6d635daf --- /dev/null +++ b/lib/gis/resource_dirs.h @@ -0,0 +1,32 @@ +/*! + \file lib/gis/resource_dirs.h + + \brief GIS Library - Default resource directories relative to GISBASE. + + SPDX-FileCopyrightText: GRASS Development Team + SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef GRASS_LIB_GIS_RESOURCE_DIRS_H +#define GRASS_LIB_GIS_RESOURCE_DIRS_H + +/* Paths relative to GISBASE, used when the corresponding GRASS_*DIR variable + is not set. CMake defines them from the install layout; the defaults are + the legacy layout, which is the only one the Autotools build produces. */ +#ifndef GRASS_COLORSDIR_REL +#define GRASS_COLORSDIR_REL "etc/colors" +#endif +#ifndef GRASS_ETCBINDIR_REL +#define GRASS_ETCBINDIR_REL "etc" +#endif +#ifndef GRASS_ETCDIR_REL +#define GRASS_ETCDIR_REL "etc" +#endif +#ifndef GRASS_FONTSDIR_REL +#define GRASS_FONTSDIR_REL "fonts" +#endif +#ifndef GRASS_LOCALEDIR_REL +#define GRASS_LOCALEDIR_REL "locale" +#endif + +#endif diff --git a/lib/gis/tests/lib_gis_resource_dirs_test.py b/lib/gis/tests/lib_gis_resource_dirs_test.py new file mode 100644 index 00000000000..550ccf08c77 --- /dev/null +++ b/lib/gis/tests/lib_gis_resource_dirs_test.py @@ -0,0 +1,27 @@ +"""Test fallback of resource directories to GISBASE""" + +import pytest + +import grass.script as gs +from grass.app.runtime import RuntimePaths + + +@pytest.fixture +def env_with_only_gisbase(xy_session_for_module): + """Session environment without the GRASS_*DIR variables, but with GISBASE""" + env = xy_session_for_module.env.copy() + for name in RuntimePaths.env_variable_names(): + if name != "GISBASE": + env.pop(name, None) + return env + + +def test_colors_dir_falls_back_to_gisbase(env_with_only_gisbase): + """Color rules are found without GRASS_COLORSDIR""" + rules = gs.read_command("r.colors", flags="l", env=env_with_only_gisbase) + assert "viridis" in rules.split() + + +def test_etc_dir_falls_back_to_gisbase(env_with_only_gisbase): + """Element list in etc is found without GRASS_ETCDIR""" + gs.run_command("g.list", type="raster", env=env_with_only_gisbase)