diff --git a/CMakeLists.txt b/CMakeLists.txt index adef2a9d65c..638cb990a9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,38 +213,11 @@ if(WITH_FHS) ${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(MAKE_DIRECTORY "${RUNTIME_GISBASE}/gui/wxpython") 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 + ${OUTDIR}/${GRASS_INSTALL_GUIDIR}/wxpython/xml + ${RUNTIME_GISBASE}/gui/wxpython/xml SYMBOLIC ) endif() @@ -290,6 +263,17 @@ if(WIN32) "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_MISCDIR=${RUNTIME_GRASS_MISCDIR}" + "GRASS_MKDOCSDIR=${RUNTIME_GRASS_MKDOCSDIR}" "ARCH=${BUILD_ARCH}" "ARCH_DISTDIR=${RUN_GISBASE_NATIVE}" "LANGUAGE=C" @@ -314,6 +298,17 @@ else() "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_MISCDIR=${RUNTIME_GRASS_MISCDIR}" + "GRASS_MKDOCSDIR=${RUNTIME_GRASS_MKDOCSDIR}" "ARCH=${BUILD_ARCH}" "ARCH_DISTDIR=${RUN_GISBASE_NATIVE}" "LANGUAGE=C" @@ -392,23 +387,29 @@ add_custom_target( 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 + ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/colortables COMMAND ${CMAKE_COMMAND} -E copy_directory - ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/colortables + ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/colortables ${OUTDIR}/${GRASS_INSTALL_MKDOCSDIR}/source/colortables - BYPRODUCTS ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/colortables + BYPRODUCTS ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/colortables COMMENT "Creating thumbnails" DEPENDS ALL_MODULES ) set_target_properties(r_colors_thumbnails PROPERTIES FOLDER Misc) install( - DIRECTORY ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/colortables - DESTINATION ${GRASS_INSTALL_DOCDIR} + 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 26292c32269..7954c1368c4 100644 --- a/cmake/modules/GRASSInstallDirs.cmake +++ b/cmake/modules/GRASSInstallDirs.cmake @@ -30,8 +30,12 @@ if(WITH_FHS) ) set(GRASS_INSTALL_DEMODIR "${GRASS_INSTALL_SHAREDIR}/demolocation") 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 @@ -54,10 +58,13 @@ else() set(GRASS_INSTALL_DEVDOCDIR "${GISBASE_DIR}/html") set(GRASS_INSTALL_MANDIR "${GISBASE_DIR}/docs/man/man1") 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() set(GRASS_INSTALL_CMAKECONFDIR "${CMAKE_INSTALL_LIBDIR}/cmake") @@ -85,12 +92,41 @@ 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}") message(STATUS "GRASS_INSTALL_CMAKECONFDIR ${GRASS_INSTALL_CMAKECONFDIR}") message(STATUS "GRASS_INSTALL_CMAKEMODULEDIR ${GRASS_INSTALL_CMAKEMODULEDIR}") set(OUTDIR "${CMAKE_BINARY_DIR}/output") set(GISBASE ${CMAKE_INSTALL_PREFIX}/${GISBASE_DIR}) + +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_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}") +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 65c600d9dfc..c89f6d63fc1 100644 --- a/display/CMakeLists.txt +++ b/display/CMakeLists.txt @@ -60,22 +60,28 @@ add_custom_command( 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 + ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/barscales COMMAND ${CMAKE_COMMAND} -E make_directory ${OUTDIR}/${GRASS_INSTALL_MKDOCSDIR}/source/barscales COMMAND ${CMAKE_COMMAND} -E copy ${d_barscale_png} ${OUTDIR}/${GRASS_INSTALL_MKDOCSDIR}/source/barscales - BYPRODUCTS ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/barscales + BYPRODUCTS ${OUTDIR}/${GRASS_INSTALL_GRAPHICSDIR}/barscales ) install( - DIRECTORY ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/barscales - DESTINATION ${GRASS_INSTALL_DOCDIR} + 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 @@ -156,9 +162,15 @@ if(WITH_DOCS) DESTINATION ${OUTDIR}/${GRASS_INSTALL_MKDOCSDIR}/source/northarrows ) install( - DIRECTORY ${OUTDIR}/${GRASS_INSTALL_DOCDIR}/northarrows - DESTINATION ${GRASS_INSTALL_DOCDIR} + 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( diff --git a/display/d.linegraph/main.c b/display/d.linegraph/main.c index c5e41f8b4cb..fa2e2ea3f08 100644 --- a/display/d.linegraph/main.c +++ b/display/d.linegraph/main.c @@ -86,7 +86,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) @@ -99,7 +99,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 083289352e8..4afd66bc2e6 100644 --- a/display/d.vect.thematic/main.c +++ b/display/d.vect.thematic/main.c @@ -642,7 +642,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) @@ -655,7 +655,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 81b8a608792..b7517479923 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 1b05f67c55e..30359661d73 100644 --- a/doc/examples/gui/wxpython/frame.py +++ b/doc/examples/gui/wxpython/frame.py @@ -20,7 +20,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 039e2eaee39..9b2d29d6669 --- a/doc/examples/gui/wxpython/g.gui.example.py +++ b/doc/examples/gui/wxpython/g.gui.example.py @@ -29,7 +29,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/CMakeLists.txt b/general/CMakeLists.txt index af71860f550..b1beab4cdf5 100644 --- a/general/CMakeLists.txt +++ b/general/CMakeLists.txt @@ -15,7 +15,7 @@ build_program_in_subdir( ) 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 set(fontcap_file ${OUTDIR}/${GRASS_INSTALL_ETCBINDIR}/fontcap) add_custom_command( diff --git a/general/g.gui/main.c b/general/g.gui/main.c index f658d5f3fbd..2a5654abb8b 100644 --- a/general/g.gui/main.c +++ b/general/g.gui/main.c @@ -101,8 +101,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 f398bec0966..9f4af1e1859 100644 --- a/general/g.list/main.c +++ b/general/g.list/main.c @@ -363,7 +363,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 b2fad4caae4..fc124c41fd0 100644 --- a/general/g.mapset/main.c +++ b/general/g.mapset/main.c @@ -242,7 +242,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); @@ -265,7 +265,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 19b290c4043..cf106fcc4e6 100644 --- a/general/g.mapsets/main.c +++ b/general/g.mapsets/main.c @@ -214,8 +214,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 8da9b6089ef..5fa43a061b3 100644 --- a/general/g.mkfontcap/main.c +++ b/general/g.mkfontcap/main.c @@ -83,13 +83,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 af79836ff28..8dd8a775ea6 100644 --- a/general/g.mkfontcap/stroke_fonts.c +++ b/general/g.mkfontcap/stroke_fonts.c @@ -45,12 +45,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 a1888a18bcd..79c79abbb8b --- a/gui/scripts/d.wms.py +++ b/gui/scripts/d.wms.py @@ -160,7 +160,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 61e2ce55eee..e73d574ae23 100644 --- a/gui/wxpython/core/globalvar.py +++ b/gui/wxpython/core/globalvar.py @@ -24,11 +24,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] @@ -247,7 +246,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 4861e278830..940b89a5014 100644 --- a/gui/wxpython/core/settings.py +++ b/gui/wxpython/core/settings.py @@ -104,7 +104,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 a42be365962..8a6f7286951 100644 --- a/gui/wxpython/core/toolboxes.py +++ b/gui/wxpython/core/toolboxes.py @@ -29,7 +29,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 da040eb3bba..96ab0972452 100644 --- a/gui/wxpython/gui_core/ghelp.py +++ b/gui/wxpython/gui_core/ghelp.py @@ -270,7 +270,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 Path(copyfile).exists(): copytext = Path(copyfile).read_text() else: @@ -297,7 +297,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 Path(licfile).exists(): with open(licfile) as licenceFile: license = "".join(licenceFile.readlines()) @@ -350,7 +350,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 Path(authfile).exists(): with codecs.open(authfile, encoding="utf-8", mode="r") as authorsFile: authors = "".join(authorsFile.readlines()) @@ -375,9 +375,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 Path(contribfile).exists(): contribs = [] errLines = [] @@ -460,7 +462,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 Path(translatorsfile).exists(): translators = {} errLines = [] @@ -640,7 +642,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 Path(statsfile).exists(): import json @@ -756,10 +758,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 Path(self.fspath).exists(): 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 a852f6265ca..edc7ac8076b 100644 --- a/gui/wxpython/gui_core/widgets.py +++ b/gui/wxpython/gui_core/widgets.py @@ -1750,7 +1750,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 ) @@ -1758,9 +1758,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): @@ -1768,7 +1766,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 0ca07b04613..ac1965cc208 100644 --- a/gui/wxpython/image2target/ii2t_gis_set.py +++ b/gui/wxpython/image2target/ii2t_gis_set.py @@ -86,11 +86,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 0462bc792b0..10f20353cc5 100644 --- a/gui/wxpython/lmgr/frame.py +++ b/gui/wxpython/lmgr/frame.py @@ -33,9 +33,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 @@ -1547,7 +1544,7 @@ def OnSystemInfo(self, event): # check also OSGeo4W on MS Windows if ( sys.platform == "win32" - and not Path(os.getenv("GISBASE"), "WinGRASS-README.url").exists() + and not Path(os.getenv("GRASS_MISCDIR"), "WinGRASS-README.url").exists() ): osgeo4w = " (OSGeo4W)" else: diff --git a/gui/wxpython/main_window/frame.py b/gui/wxpython/main_window/frame.py index 6c9230ad0ce..b17c37140cc 100644 --- a/gui/wxpython/main_window/frame.py +++ b/gui/wxpython/main_window/frame.py @@ -39,9 +39,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 @@ -1779,7 +1776,7 @@ def OnSystemInfo(self, event): # check also OSGeo4W on MS Windows if ( sys.platform == "win32" - and not Path(os.getenv("GISBASE"), "WinGRASS-README.url").exists() + and not Path(os.getenv("GRASS_MISCDIR"), "WinGRASS-README.url").exists() ): osgeo4w = " (OSGeo4W)" else: diff --git a/gui/wxpython/psmap/dialogs.py b/gui/wxpython/psmap/dialogs.py index 2dc39c6d623..2c4341af7b0 100644 --- a/gui/wxpython/psmap/dialogs.py +++ b/gui/wxpython/psmap/dialogs.py @@ -2202,13 +2202,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) @@ -6192,8 +6192,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 b56ddc64d5d..c74755b03a9 100644 --- a/gui/wxpython/web_services/cap_interface.py +++ b/gui/wxpython/web_services/cap_interface.py @@ -23,7 +23,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 d0bd2ea41b2..ef8f2ee3943 100644 --- a/gui/wxpython/web_services/widgets.py +++ b/gui/wxpython/web_services/widgets.py @@ -60,7 +60,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 460e9ffe375..30cb992ecfe 100644 --- a/include/Make/Install.make +++ b/include/Make/Install.make @@ -130,8 +130,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 @@ -153,6 +154,18 @@ $(DESTDIR)$(INST_DIR)/$(RESOURCE_PATHS): $(ARCH_DISTDIR)/resource_paths.py -e 's#'@BUILD_ARCH@'#$(ARCH)#' \ -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#' \ $< > $@ $(PYTHON) -m py_compile $@ 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 106c9801ef4..1f5e9a65908 100644 --- a/include/grass/defs/gis.h +++ b/include/grass/defs/gis.h @@ -436,6 +436,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 a5916297bdb..41e272cbe31 100644 --- a/include/grass/gprojects.h +++ b/include/grass/gprojects.h @@ -28,11 +28,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 ca407379658..6f459c13dcb 100644 --- a/lib/driver/font2.c +++ b/lib/driver/font2.c @@ -129,7 +129,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); } } @@ -142,7 +142,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 b7dbd44ad6b..8f04c39182c 100644 --- a/lib/driver/parse_ftcap.c +++ b/lib/driver/parse_ftcap.c @@ -84,7 +84,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/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/color_rules.c b/lib/gis/color_rules.c index 56aee00db5e..7e45c1c7a58 100644 --- a/lib/gis/color_rules.c +++ b/lib/gis/color_rules.c @@ -269,7 +269,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); @@ -285,8 +285,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")); @@ -362,7 +361,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 3c2d74ca981..c1284f03177 100644 --- a/lib/gis/datum.c +++ b/lib/gis/datum.c @@ -11,7 +11,7 @@ * *****************************************************************************/ -#define DATUMTABLE "/etc/proj/datum.table" +#define DATUMTABLE "/proj/datum.table" #include #include @@ -145,7 +145,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 2ff2c7e86c8..f0963f23692 100644 --- a/lib/gis/get_ellipse.c +++ b/lib/gis/get_ellipse.c @@ -253,7 +253,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 700dfc00eb5..91c42cc19cb 100644 --- a/lib/gis/is.c +++ b/lib/gis/is.c @@ -46,7 +46,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 41b1f0a2121..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 @@ -37,14 +40,24 @@ void G_init_locale(void) #ifdef LC_MESSAGES setlocale(LC_MESSAGES, ""); #endif - const char *gisbase = getenv("GISBASE"); + /* 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 (gisbase && *gisbase) { - char localedir[GPATH_MAX]; + if (!localedir || !*localedir) { + /* Same fallback as G_locale_dir(). */ + const char *gisbase = getenv("GISBASE"); - strcpy(localedir, gisbase); - strcat(localedir, "/locale"); + localedir = NULL; + if (gisbase && *gisbase) { + snprintf(fallback, sizeof(fallback), "%s/%s", gisbase, + GRASS_LOCALEDIR_REL); + localedir = fallback; + } + } + if (localedir) { bindtextdomain("grasslibs", localedir); bindtextdomain("grassmods", localedir); } diff --git a/lib/gis/parser.c b/lib/gis/parser.c index 599160caf08..9289ef227b4 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..8ac75e4c16a --- /dev/null +++ b/lib/gis/resource_dirs.c @@ -0,0 +1,93 @@ +/*! + \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 +#include + +#include "resource_dirs.h" + +static const char *get_g_env(const char *, const char *, int *, char **); + +const char *G_colors_dir(void) +{ + static int initialized; + static char *fallback; + + return get_g_env("GRASS_COLORSDIR", GRASS_COLORSDIR_REL, &initialized, + &fallback); +} + +const char *G_etcbin_dir(void) +{ + static int initialized; + static char *fallback; + + return get_g_env("GRASS_ETCBINDIR", GRASS_ETCBINDIR_REL, &initialized, + &fallback); +} + +const char *G_etc_dir(void) +{ + static int initialized; + static char *fallback; + + return get_g_env("GRASS_ETCDIR", GRASS_ETCDIR_REL, &initialized, &fallback); +} + +const char *G_fonts_dir(void) +{ + static int initialized; + static char *fallback; + + return get_g_env("GRASS_FONTSDIR", GRASS_FONTSDIR_REL, &initialized, + &fallback); +} + +const char *G_locale_dir(void) +{ + static int initialized; + static char *fallback; + + return get_g_env("GRASS_LOCALEDIR", GRASS_LOCALEDIR_REL, &initialized, + &fallback); +} + +/* 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 && *value) + return value; + + /* 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) diff --git a/lib/init/Makefile b/lib/init/Makefile index d2f62cba43d..120ec197b5c 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 c52b123ac9d..a8ce3fa6dd5 100755 --- a/lib/init/grass.py +++ b/lib/init/grass.py @@ -1151,7 +1151,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" @@ -1232,7 +1232,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 diff --git a/lib/manage/read_list.c b/lib/manage/read_list.c index e25e403f8b6..0a275c51b62 100644 --- a/lib/manage/read_list.c +++ b/lib/manage/read_list.c @@ -64,7 +64,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 fc14c51398d..1913eed4937 100644 --- a/lib/proj/convert.c +++ b/lib/proj/convert.c @@ -24,7 +24,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 **); @@ -490,8 +490,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); @@ -626,8 +626,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); @@ -995,13 +994,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 4ffca3cbadc..4d8661ce25f 100644 --- a/lib/proj/datum.c +++ b/lib/proj/datum.c @@ -267,7 +267,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) { @@ -344,7 +344,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 51e14ee6262..319ceb0b029 100644 --- a/lib/proj/ellipse.c +++ b/lib/proj/ellipse.c @@ -229,7 +229,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 497e88140e9..758cc50f781 100644 --- a/lib/raster/color_rules.c +++ b/lib/raster/color_rules.c @@ -315,7 +315,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 c111fe633f1..6a74feefcb2 100644 --- a/lib/symbol/read.c +++ b/lib/symbol/read.c @@ -270,7 +270,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 d60b091b680..b58bea50fc1 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 index 92bdc2f6233..990e6cc8465 100755 --- a/locale/grass_po_stats.py +++ b/locale/grass_po_stats.py @@ -157,5 +157,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 3f71bf7cd99..ec95bf7a3df 100644 --- a/man/Makefile +++ b/man/Makefile @@ -93,35 +93,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 @@ -129,13 +134,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" @@ -148,11 +155,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 9603d5cffea..f1f116200ed 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 a6ba78cf5d5..de400cf8d98 100644 --- a/man/build_md.py +++ b/man/build_md.py @@ -109,6 +109,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 5298aa76f83..a5142283828 --- a/man/build_rest.py +++ b/man/build_rest.py @@ -362,8 +362,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 45c97ad4930..1e9d15462e9 100644 --- a/python/grass/__init__.py +++ b/python/grass/__init__.py @@ -66,8 +66,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 c823a2c5a48..65915ce42b9 100644 --- a/python/grass/app/Makefile +++ b/python/grass/app/Makefile @@ -38,6 +38,18 @@ $(DSTDIR)/resource_paths.py: resource_paths.py | $(DSTDIR) -e 's#@BUILD_ARCH@#$(ARCH)#' \ -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 6f538971d52..e1b5cdcceae 100644 --- a/python/grass/app/data.py +++ b/python/grass/app/data.py @@ -97,8 +97,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 Path(startup_location).exists(): @@ -204,16 +203,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 cd3b3145a57..f5177d89acb 100644 --- a/python/grass/app/resource_paths.py +++ b/python/grass/app/resource_paths.py @@ -24,6 +24,19 @@ 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@" GRASS_CMAKE_CONFIG = "@GRASS_INSTALL_CMAKECONFDIR@" GRASS_CMAKE_MODULES = "@GRASS_INSTALL_CMAKEMODULEDIR@" GRASS_CMAKE_PREFIX_PATH = "@CMAKE_PREFIX_PATH@" diff --git a/python/grass/app/runtime.py b/python/grass/app/runtime.py index a6d6612dfb0..e97b65069e9 100644 --- a/python/grass/app/runtime.py +++ b/python/grass/app/runtime.py @@ -42,6 +42,18 @@ class RuntimePaths: # Mapping of attribute names to environment variable name except the prefix. _env_vars = { "gisbase": "GISBASE", + "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, set_env_variables=False, prefix=None): @@ -64,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/app/tests/grass_app_runtime_test.py b/python/grass/app/tests/grass_app_runtime_test.py index 739c3be6c96..33608db983c 100644 --- a/python/grass/app/tests/grass_app_runtime_test.py +++ b/python/grass/app/tests/grass_app_runtime_test.py @@ -76,7 +76,17 @@ def test_env_gisbase_with_custom_prefix(custom_prefix, path_type): assert env["GISBASE"].startswith(os.path.normpath(custom_prefix)) -def test_attr_access_does_not_modify_env(): +def test_attr_access_does_not_modify_env_colors(): + """Accessing attribute should not change environment.""" + env = {} + paths = RuntimePaths(env=env) + assert "GRASS_COLORSDIR" not in env + value = paths.colors_dir # access the attribute + assert value + assert "GRASS_COLORSDIR" not in env, "env was modified unexpectedly" + + +def test_attr_access_does_not_modify_env_gisbase(): """Accessing attribute should not change environment.""" env = {} paths = RuntimePaths(env=env) @@ -86,7 +96,16 @@ def test_attr_access_does_not_modify_env(): assert "GISBASE" not in env, "env was modified unexpectedly" -def test_explicit_env_vars_set(): +def test_explicit_env_vars_set_colors(): + """Explicit call should set the env vars.""" + env = {} + paths = RuntimePaths(env=env) + 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.""" env = {} paths = RuntimePaths(env=env) @@ -95,7 +114,15 @@ def test_explicit_env_vars_set(): assert env["GISBASE"] == paths.gisbase -def test_constructor_parameter_env_vars_set(): +def test_constructor_parameter_env_vars_set_colors(): + """Constructor with parameter should set the env vars.""" + env = {} + 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) @@ -103,7 +130,16 @@ def test_constructor_parameter_env_vars_set(): assert env["GISBASE"] == paths.gisbase -def test_dir_lists_dynamic_attributes_but_does_not_modify_env(): +def test_dir_lists_dynamic_attributes_but_does_not_modify_env_colors(): + """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_dir_lists_dynamic_attributes_but_does_not_modify_env_gisbase(): """dir() should show dynamic attrs but not set env.""" env = {} paths = RuntimePaths(env=env) @@ -112,7 +148,16 @@ def test_dir_lists_dynamic_attributes_but_does_not_modify_env(): assert "GISBASE" not in env, "dir() should not modify env" -def test_existing_env_value_is_respected(): +def test_existing_env_value_is_respected_colors(): + """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_existing_env_value_is_respected_gisbase(): """If env already contains GISBASE, its value is used.""" value = "/custom/path/to/grass" env = {"GISBASE": value} @@ -128,7 +173,16 @@ def test_invalid_attribute_raises(): assert paths.unknown_attribute # avoiding unused value -def test_returned_attribute_consistent(): +def test_returned_attribute_consistent_colors(): + """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 + + +def test_returned_attribute_consistent_gisbase(): """Repeated accesses should return the same value.""" paths = RuntimePaths(env={}) first = paths.gisbase diff --git a/python/grass/docs/conf.py b/python/grass/docs/conf.py index e8aa6584fe7..d018a6b3774 100644 --- a/python/grass/docs/conf.py +++ b/python/grass/docs/conf.py @@ -17,9 +17,44 @@ from shutil import copy import pathlib -# The grass package is imported from GISBASE, which the build puts on PYTHONPATH. -if not os.getenv("GISBASE"): - sys.exit("GISBASE not defined") +# 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("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["GRASS_PYDIR"], "grass", "ctypes")), +) +sys.path.insert( + 0, + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "exceptions")), +) +sys.path.insert( + 0, + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "gunittest")), +) +sys.path.insert( + 0, + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "imaging")), +) +sys.path.insert( + 0, + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "pydispatch")), +) +sys.path.insert( + 0, + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "pygrass")), +) +sys.path.insert( + 0, + os.path.abspath(os.path.join(os.environ["GRASS_PYDIR"], "grass", "script")), +) +sys.path.insert( + 0, + 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/core.py b/python/grass/script/core.py index d40b9904753..00316c9a45b 100644 --- a/python/grass/script/core.py +++ b/python/grass/script/core.py @@ -2104,8 +2104,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. diff --git a/python/grass/script/setup.py b/python/grass/script/setup.py index 0c21230e623..7c6558b991a 100644 --- a/python/grass/script/setup.py +++ b/python/grass/script/setup.py @@ -104,7 +104,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) @@ -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"] @@ -590,9 +600,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 f6561354247..261ab851056 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/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): diff --git a/python/grass/script/utils.py b/python/grass/script/utils.py index 29f31b63237..13897f62a60 100644 --- a/python/grass/script/utils.py +++ b/python/grass/script/utils.py @@ -501,8 +501,8 @@ def get_lib_path(modname, libname=None): from os import getenv from os.path import join, sep - if Path(getenv("GISBASE"), "etc", modname).is_dir(): - path = join(os.getenv("GISBASE"), "etc", modname) + if Path(getenv("GRASS_ETCDIR"), modname).is_dir(): + 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 6dc7c5eae27..1be486c64c9 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 1dbb8e3b558..ef3868a9c78 100644 --- a/python/grass/temporal/core.py +++ b/python/grass/temporal/core.py @@ -475,7 +475,7 @@ def get_tgis_database_string() -> str | None: def get_sql_template_path() -> str: - return str(Path(os.getenv("GISBASE"), "etc", "sql")) + return str(Path(os.getenv("GRASS_ETCDIR"), "sql")) ############################################################################### diff --git a/python/libgrass_interface_generator/CMakeLists.txt b/python/libgrass_interface_generator/CMakeLists.txt index 7237b276074..eb09e5ebff7 100644 --- a/python/libgrass_interface_generator/CMakeLists.txt +++ b/python/libgrass_interface_generator/CMakeLists.txt @@ -69,7 +69,6 @@ if(MSVC) string(APPEND _c_flags " -I${CMAKE_SOURCE_DIR}/msvc") elseif(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 8ae4873c427..16c8e386f12 100644 --- a/raster/r.colors/edit_colors.c +++ b/raster/r.colors/edit_colors.c @@ -468,7 +468,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; } @@ -488,7 +488,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 e8945390098..e36cc885e48 100644 --- a/raster/r.watershed/front/main.c +++ b/raster/r.watershed/front/main.c @@ -260,7 +260,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 c1f12c82dc8..f6941397f1f 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 a2129837c80..23d9f876079 100755 --- a/scripts/d.polar/d.polar.py +++ b/scripts/d.polar/d.polar.py @@ -234,7 +234,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 b69a73afc66..19a4a2e2207 100755 --- a/scripts/db.test/db.test.py +++ b/scripts/db.test/db.test.py @@ -42,7 +42,7 @@ def main(): gcore.fatal(_("Database connection not defined. Run db.connect.")) 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 index 8d644151255..5dbbf884e78 100755 --- a/scripts/g.extension/g.extension.py +++ b/scripts/g.extension/g.extension.py @@ -2427,7 +2427,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 aeb25f3f603..3569b5cf5de 100755 --- a/scripts/g.search.modules/g.search.modules.py +++ b/scripts/g.search.modules/g.search.modules.py @@ -194,8 +194,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) @@ -211,7 +211,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 Path(filename_addons_s).is_file(): 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 06950b12961..32b0f15a3f2 --- a/scripts/wxpyimgview/wxpyimgview.py +++ b/scripts/wxpyimgview/wxpyimgview.py @@ -37,6 +37,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 1cee51600f9..f9de7e92b95 100755 --- a/utils/thumbnails.py +++ b/utils/thumbnails.py @@ -155,7 +155,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] Path(output_dir).mkdir(parents=True, exist_ok=True) 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); }