From ddedaa579c95c9b3535c4ac11909f48ced979366 Mon Sep 17 00:00:00 2001 From: Paulo van Breugel Date: Sun, 10 May 2026 11:22:23 +0200 Subject: [PATCH 1/3] g.extension: support alternative addon servers on MS-Windows Proposal to add the option to point at an alternative addon server for MS-Window users. --- scripts/g.extension/g.extension.html | 84 +++++++++++++++++++++++++++- scripts/g.extension/g.extension.md | 75 ++++++++++++++++++++++++- scripts/g.extension/g.extension.py | 61 ++++++++++++++++++-- 3 files changed, 209 insertions(+), 11 deletions(-) mode change 100755 => 100644 scripts/g.extension/g.extension.py diff --git a/scripts/g.extension/g.extension.html b/scripts/g.extension/g.extension.html index cdbadb07bf6..b9460cdcb89 100644 --- a/scripts/g.extension/g.extension.html +++ b/scripts/g.extension/g.extension.html @@ -126,6 +126,46 @@

Compilation and installation

as manual page are compiled, not only the source code (which is really necessary to compile just in case of C). +

Using an alternative addon server (MS-Windows)

+ +On MS-Windows, g.extension downloads precompiled addon ZIPs from the +official WinGRASS server (http://wingrass.fsv.cvut.cz) by default. +The base URL of the server can be redirected to a mirror or a private addon +server by setting ADDONS_BASE_URL. The setting affects both the +addon listing (g.extension -l, -c, -g) +and the install download URL, so listing and install always come from the same +place. + +

+The setting can be applied in two ways: + +

+ +

+To revert to the default server, unset the value: + +

+g.gisenv unset=ADDONS_BASE_URL
+
+ +

+Alternative servers are: +

+

EXAMPLES

Download and install of an extension

@@ -143,6 +183,35 @@

Download and install of an extension

g.extension r.stream.distance +

Installing from an alternative addon server (MS-Windows)

+ +On MS-Windows, redirect the addon server to a mirror or private host +once per session, then use g.extension normally — listing and install +both go through the configured server. + +
+# Point at the alternative server (persists across GRASS sessions)
+g.gisenv set="ADDONS_BASE_URL=https://ecodiv.earth/share"
+
+# List what the alternative server provides
+g.extension -l
+
+# Install an extension from it
+g.extension extension=r.tpi
+
+# Revert to the default WinGRASS server
+g.gisenv unset=ADDONS_BASE_URL
+
+ +

+For a one-off shell session, set the environment variable instead: + +

+# cmd.exe
+set GRASS_ADDONS_BASE_URL=https://ecodiv.earth/share
+g.extension extension=r.tpi
+
+

Download and install of an extension when behind a proxy

Example for an open http proxy: @@ -204,8 +273,10 @@

Installing from various online repositories: GitHub, GitLab, Bitbucket

g.extension r.example url=http://example.com/.../r.example?format=zip -Note that because of MS-Windows operating system architecture, -only official repository is supported on this platform. +On Windows, this will work also, but only when pointing at a server with +pre-compiled addons and set up using the same directory layout as the +official WinGRASS server, namely {base}/grass{MM}/addons/grass-{M.M.P}/ +containing one ZIP per extension and a modules.xml listing.

Install a specific version from Addons

@@ -248,6 +319,12 @@

KNOWN ISSUES

(a Python replacement for Python scripts should be implemented). +

+On Windows, it is now possible to point at an alternative server. However, note +that this server should be set up using the same directory layout as the +official WinGRASS server, namely {base}/grass{MM}/addons/grass-{M.M.P}/ +containing pre-compiled extensions as ZIP file, and a modules.xml listing. +

TROUBLESHOOTING

Since extensions have to be compiled on Unix based systems (Linux, Mac OSX etc.) @@ -276,4 +353,5 @@

AUTHORS

Markus Neteler (original shell script)
Martin Landa, Czech Technical University in Prague, Czech Republic (Python rewrite)
-Vaclav Petras, NCSU GeoForAll Lab (support for general sources, partial refactoring) +Vaclav Petras, NCSU GeoForAll Lab (support for general sources, partial refactoring)
+Paulo van Breugel, HAS green academy (add option for Windows users to point at alternative repositories) diff --git a/scripts/g.extension/g.extension.md b/scripts/g.extension/g.extension.md index 78767c5059f..0953527630e 100644 --- a/scripts/g.extension/g.extension.md +++ b/scripts/g.extension/g.extension.md @@ -115,6 +115,39 @@ extensions. The reason is that more things such as manual page are compiled, not only the source code (which is really necessary to compile just in case of C). +### Using an alternative addon server (MS-Windows) + +On MS-Windows, *g.extension* downloads precompiled addon ZIPs from the +official WinGRASS server (`http://wingrass.fsv.cvut.cz`) by default. +The base URL of the server can be redirected to a mirror or a private +addon server by setting `ADDONS_BASE_URL`. The setting affects both the +addon listing (`g.extension -l`, `-c`, `-g`) and the install download +URL, so listing and install always come from the same place. + +The setting can be applied in two ways: + +- From inside GRASS (persists across sessions, settable from the GUI's + variables panel as well): + + ```sh + g.gisenv set="ADDONS_BASE_URL=https://your.server.example" + ``` + +- As a shell environment variable named `GRASS_ADDONS_BASE_URL` (useful + for one-off shell sessions or CI jobs). The gisenv setting takes + precedence over the environment variable; if neither is set, the + default WinGRASS server is used. + +To revert to the default server, unset the value: + +```sh +g.gisenv unset=ADDONS_BASE_URL +``` + +Alternative servers are: + +* (Python extensions only) + ## EXAMPLES ### Download and install of an extension @@ -132,6 +165,34 @@ convenience, a shorter syntax can be used: g.extension r.stream.distance ``` +### Installing from an alternative addon server (MS-Windows) + +On MS-Windows, redirect the addon server to a mirror or private host +once per session, then use *g.extension* normally — listing and install +both go through the configured server. + +```sh +# Point at the alternative server (persists across GRASS sessions) +g.gisenv set="ADDONS_BASE_URL=https://ecodiv.earth/share" + +# List what the alternative server provides +g.extension -l + +# Install an extension from it +g.extension extension=r.tpi + +# Revert to the default WinGRASS server +g.gisenv unset=ADDONS_BASE_URL +``` + +For a one-off shell session, set the environment variable instead: + +```sh +# cmd.exe +set GRASS_ADDONS_BASE_URL=https://ecodiv.earth/share +g.extension extension=r.tpi +``` + ### Download and install of an extension when behind a proxy Example for an open http proxy: @@ -198,8 +259,10 @@ can be used: g.extension r.example url=http://example.com/.../r.example?format=zip ``` -Note that because of MS-Windows operating system architecture, only -official repository is supported on this platform. +On Windows, this will work also, but only when pointing at a server with +pre-compiled addons and set up using the same directory layout as the +official WinGRASS server, namely `{base}/grass{MM}/addons/grass-{M.M.P}/` +containing one ZIP per extension and a `modules.xml` listing. ### Install a specific version from Addons @@ -239,6 +302,12 @@ On MS-Windows, only the official repository is working because there is no way of compiling the modules (a Python replacement for Python scripts should be implemented). +On Windows, it is now possible to point at an alternative server. However, note +that this server should be set up using the same directory layout as the +official WinGRASS server, namely `{base}/grass{MM}/addons/grass-{M.M.P}/` +containing pre-compiled extensions as ZIP file, and a `modules.xml` listing. + + ## TROUBLESHOOTING Since extensions have to be compiled on Unix based systems (Linux, Mac @@ -269,3 +338,5 @@ Martin Landa, Czech Technical University in Prague, Czech Republic Vaclav Petras, [NCSU GeoForAll Lab](https://geospatial.ncsu.edu/geoforall/) (support for general sources, partial refactoring) +Paulo van Breugel, HAS green academy (add option for Windows users to +point at alternative repositories) diff --git a/scripts/g.extension/g.extension.py b/scripts/g.extension/g.extension.py old mode 100755 new mode 100644 index ab1428e5934..548db49c576 --- a/scripts/g.extension/g.extension.py +++ b/scripts/g.extension/g.extension.py @@ -1590,6 +1590,37 @@ def install_module_xml(mlist): return mlist +def get_addons_base_url(): + """Resolve the base URL of the addon server (Windows binary repository). + + Precedence (highest first): + 1. ``ADDONS_BASE_URL`` set via ``g.gisenv set="ADDONS_BASE_URL=..."``. + Persists across GRASS sessions and is settable from inside GRASS. + 2. ``GRASS_ADDONS_BASE_URL`` shell environment variable. Useful for + CI / system administrators / one-off shell sessions. + 3. The built-in default ``http://wingrass.fsv.cvut.cz`` (the official + WinGRASS server). + + The returned URL has any trailing slash stripped so callers can append + path segments cleanly. + + The same URL is used for both the Windows ZIP download path and the + addon listing (``modules.xml`` for ``g.extension -l/-c/-g``), so a user + who points this at a custom server gets a self-consistent view. + """ + try: + gisenv_value = gs.gisenv().get("ADDONS_BASE_URL") + except Exception: # noqa: BLE001 + # gisenv() can fail outside a GRASS session; fall through. + gisenv_value = None + if gisenv_value: + return gisenv_value.rstrip("/") + env_value = os.environ.get("GRASS_ADDONS_BASE_URL") + if env_value: + return env_value.rstrip("/") + return "http://wingrass.fsv.cvut.cz" + + def install_extension_win(name): """Install extension on MS Windows""" gs.message( @@ -1598,7 +1629,7 @@ def install_extension_win(name): # build base URL base_url = ( - "http://wingrass.fsv.cvut.cz/" + f"{get_addons_base_url()}/" f"grass{VERSION[0]}{VERSION[1]}/addons/" f"grass-{VERSION[0]}.{VERSION[1]}.{VERSION[2]}" ) @@ -2597,11 +2628,29 @@ def resolve_xmlurl_prefix(url, source=None): """ gs.debug("resolve_xmlurl_prefix(url={0}, source={1})".format(url, source)) if source in {"official", "official_fork"}: - # use pregenerated modules XML file - # Define branch to fetch from (latest or current version) - version_branch = get_version_branch(VERSION[0]) - - url = "https://grass.osgeo.org/addons/{}/".format(version_branch) + # If the user has overridden the addon server (via the + # ADDONS_BASE_URL gisenv setting or GRASS_ADDONS_BASE_URL env var), + # take the listing URL from the same base so listing and install are + # served from one consistent source. modules.xml lives next to the + # ZIP files in the per-patch directory. + override = None + try: + override = gs.gisenv().get("ADDONS_BASE_URL") or os.environ.get( + "GRASS_ADDONS_BASE_URL" + ) + except Exception: # noqa: BLE001 + override = os.environ.get("GRASS_ADDONS_BASE_URL") + if override: + override = override.rstrip("/") + url = ( + f"{override}/grass{VERSION[0]}{VERSION[1]}/addons/" + f"grass-{VERSION[0]}.{VERSION[1]}.{VERSION[2]}/" + ) + else: + # use pregenerated modules XML file + # Define branch to fetch from (latest or current version) + version_branch = get_version_branch(VERSION[0]) + url = "https://grass.osgeo.org/addons/{}/".format(version_branch) # else try to get extensions XMl from SVN repository (provided URL) # the exact action depends on subsequent code (somewhere) From d16f6c3bb5a1c05957632269a1128560542ba314 Mon Sep 17 00:00:00 2001 From: Paulo van Breugel Date: Sun, 10 May 2026 12:03:21 +0200 Subject: [PATCH 2/3] some tweaks --- scripts/g.extension/g.extension.html | 13 ++++-- scripts/g.extension/g.extension.md | 4 ++ scripts/g.extension/g.extension.py | 69 ++++++++++++++++------------ 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/scripts/g.extension/g.extension.html b/scripts/g.extension/g.extension.html index b9460cdcb89..be9a3a92d95 100644 --- a/scripts/g.extension/g.extension.html +++ b/scripts/g.extension/g.extension.html @@ -146,11 +146,10 @@

Using an alternative addon server (MS-Windows)

g.gisenv set="ADDONS_BASE_URL=https://your.server.example" -
  • - As a shell environment variable named GRASS_ADDONS_BASE_URL - (useful for one-off shell sessions or CI jobs). The gisenv setting takes - precedence over the environment variable; if neither is set, the default - WinGRASS server is used.
  • +
  • As a shell environment variable named GRASS_ADDONS_BASE_URL (useful + for one-off shell sessions or CI jobs). The gisenv setting takes + precedence over the environment variable; if neither is set, the + default WinGRASS server is used.
  • @@ -166,6 +165,10 @@

    Using an alternative addon server (MS-Windows)

  • https://ecodiv.earth/share (Python extensions only)
  • +

    +Note, settings explained in this section are is honoured on MS-Windows only; on +Linux/macOS it has no effect. +

    EXAMPLES

    Download and install of an extension

    diff --git a/scripts/g.extension/g.extension.md b/scripts/g.extension/g.extension.md index 0953527630e..f05f9f94f3f 100644 --- a/scripts/g.extension/g.extension.md +++ b/scripts/g.extension/g.extension.md @@ -148,6 +148,10 @@ Alternative servers are: * (Python extensions only) + +Note, settings explained in this section are is honoured on MS-Windows only; on +Linux/macOS it has no effect. + ## EXAMPLES ### Download and install of an extension diff --git a/scripts/g.extension/g.extension.py b/scripts/g.extension/g.extension.py index 548db49c576..a1dc1832c98 100644 --- a/scripts/g.extension/g.extension.py +++ b/scripts/g.extension/g.extension.py @@ -8,7 +8,7 @@ # Vaclav Petras (support for general sources) # PURPOSE: Tool to download and install extensions into local installation # -# COPYRIGHT: (C) 2009-2025 by Markus Neteler, and the GRASS Development Team +# COPYRIGHT: (C) 2009-2026 by Markus Neteler, and the GRASS Development Team # # This program is free software under the GNU General # Public License (>=v2). Read the file COPYING that @@ -1188,7 +1188,9 @@ def install_extension(source=None, url=None, xmlurl=None, branch=None): ret1 = 0 new_modules_ext = None if sys.platform == "win32": - ret1, new_modules_ext, new_files_ext = install_extension_win(extension) + ret1, new_modules_ext, new_files_ext = install_extension_win( + extension, url=url + ) else: ( ret1, @@ -1600,13 +1602,6 @@ def get_addons_base_url(): CI / system administrators / one-off shell sessions. 3. The built-in default ``http://wingrass.fsv.cvut.cz`` (the official WinGRASS server). - - The returned URL has any trailing slash stripped so callers can append - path segments cleanly. - - The same URL is used for both the Windows ZIP download path and the - addon listing (``modules.xml`` for ``g.extension -l/-c/-g``), so a user - who points this at a custom server gets a self-consistent view. """ try: gisenv_value = gs.gisenv().get("ADDONS_BASE_URL") @@ -1621,21 +1616,35 @@ def get_addons_base_url(): return "http://wingrass.fsv.cvut.cz" -def install_extension_win(name): - """Install extension on MS Windows""" +def install_extension_win(name, url=None): + """Install extension on MS Windows.""" gs.message( _("Downloading precompiled GRASS Addons <{}>...").format(options["extension"]) ) - # build base URL - base_url = ( - f"{get_addons_base_url()}/" - f"grass{VERSION[0]}{VERSION[1]}/addons/" - f"grass-{VERSION[0]}.{VERSION[1]}.{VERSION[2]}" - ) + # Resolve the final ZIP URL. + if url: + if not url.lower().endswith(".zip"): + gs.fatal( + _( + "On MS-Windows, the 'url' option must point to a ZIP file " + "(URL ending in '.zip'). To use a different addon server, " + "set the ADDONS_BASE_URL gisenv variable or the " + "GRASS_ADDONS_BASE_URL environment variable instead." + ) + ) + zip_url = url + else: + base = get_addons_base_url() + base_url = ( + f"{base}/" + f"grass{VERSION[0]}{VERSION[1]}/addons/" + f"grass-{VERSION[0]}.{VERSION[1]}.{VERSION[2]}" + ) + zip_url = f"{base_url}/{name}.zip" # resolve ZIP URL - source, url = resolve_source_code(url="{0}/{1}.zip".format(base_url, name)) + source, url = resolve_source_code(url=zip_url) # to hide non-error messages from subprocesses outdev = open(os.devnull, "w") if gs.verbosity() <= 2 else sys.stdout @@ -2628,18 +2637,18 @@ def resolve_xmlurl_prefix(url, source=None): """ gs.debug("resolve_xmlurl_prefix(url={0}, source={1})".format(url, source)) if source in {"official", "official_fork"}: - # If the user has overridden the addon server (via the - # ADDONS_BASE_URL gisenv setting or GRASS_ADDONS_BASE_URL env var), - # take the listing URL from the same base so listing and install are - # served from one consistent source. modules.xml lives next to the - # ZIP files in the per-patch directory. + # On MS-Windows only, the user can redirect both listing and install + # to a custom addon server via the ADDONS_BASE_URL gisenv setting or + # the GRASS_ADDONS_BASE_URL env var. + # On non-Windows, this override is intentionally not honoured override = None - try: - override = gs.gisenv().get("ADDONS_BASE_URL") or os.environ.get( - "GRASS_ADDONS_BASE_URL" - ) - except Exception: # noqa: BLE001 - override = os.environ.get("GRASS_ADDONS_BASE_URL") + if sys.platform == "win32": + try: + override = gs.gisenv().get("ADDONS_BASE_URL") or os.environ.get( + "GRASS_ADDONS_BASE_URL" + ) + except Exception: # noqa: BLE001 + override = os.environ.get("GRASS_ADDONS_BASE_URL") if override: override = override.rstrip("/") url = ( @@ -2979,7 +2988,7 @@ def main(): if options["operation"] == "add": check_dirs() if sys.platform == "win32": - install_extension() + install_extension(url=original_url) else: if original_url == "" or flags["o"]: # Query GitHub API only if extension will be downloaded From 95e672ab3a391d50d676f791d8b7bf205ff125f5 Mon Sep 17 00:00:00 2001 From: Paulo van Breugel Date: Sun, 10 May 2026 21:59:30 +0200 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- scripts/g.extension/g.extension.html | 12 ++++++------ scripts/g.extension/g.extension.md | 4 +--- scripts/g.extension/g.extension.py | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/scripts/g.extension/g.extension.html b/scripts/g.extension/g.extension.html index be9a3a92d95..df26a14bb5b 100644 --- a/scripts/g.extension/g.extension.html +++ b/scripts/g.extension/g.extension.html @@ -128,12 +128,12 @@

    Compilation and installation

    Using an alternative addon server (MS-Windows)

    -On MS-Windows, g.extension downloads precompiled addon ZIPs from the -official WinGRASS server (http://wingrass.fsv.cvut.cz) by default. -The base URL of the server can be redirected to a mirror or a private addon -server by setting ADDONS_BASE_URL. The setting affects both the -addon listing (g.extension -l, -c, -g) -and the install download URL, so listing and install always come from the same +On MS-Windows, g.extension downloads precompiled addon ZIPs from the +official WinGRASS server (http://wingrass.fsv.cvut.cz) by default. +The base URL of the server can be redirected to a mirror or a private addon +server by setting ADDONS_BASE_URL. The setting affects both the +addon listing (g.extension -l, -c, -g) +and the install download URL, so listing and install always come from the same place.

    diff --git a/scripts/g.extension/g.extension.md b/scripts/g.extension/g.extension.md index f05f9f94f3f..893177fb2a6 100644 --- a/scripts/g.extension/g.extension.md +++ b/scripts/g.extension/g.extension.md @@ -146,8 +146,7 @@ g.gisenv unset=ADDONS_BASE_URL Alternative servers are: -* (Python extensions only) - +- (Python extensions only) Note, settings explained in this section are is honoured on MS-Windows only; on Linux/macOS it has no effect. @@ -311,7 +310,6 @@ that this server should be set up using the same directory layout as the official WinGRASS server, namely `{base}/grass{MM}/addons/grass-{M.M.P}/` containing pre-compiled extensions as ZIP file, and a `modules.xml` listing. - ## TROUBLESHOOTING Since extensions have to be compiled on Unix based systems (Linux, Mac diff --git a/scripts/g.extension/g.extension.py b/scripts/g.extension/g.extension.py index a1dc1832c98..7db2c306391 100644 --- a/scripts/g.extension/g.extension.py +++ b/scripts/g.extension/g.extension.py @@ -2639,7 +2639,7 @@ def resolve_xmlurl_prefix(url, source=None): if source in {"official", "official_fork"}: # On MS-Windows only, the user can redirect both listing and install # to a custom addon server via the ADDONS_BASE_URL gisenv setting or - # the GRASS_ADDONS_BASE_URL env var. + # the GRASS_ADDONS_BASE_URL env var. # On non-Windows, this override is intentionally not honoured override = None if sys.platform == "win32":