gdalraster::gdal_global_reg_names()
#> character(0)This article explains the systems-level design behind gdalraster.windows: why the package exists, how the GDAL runtime bundle is built, and why runtime activation works the way it does.
GDAL’s Algorithm API registers algorithms through static C++ constructors, file-scope objects whose constructors insert entries into a global registry when libgdal is loaded. Under some Windows toolchain states (notably Rtools/MXE builds where dependencies are static archives), the linker’s dead-code elimination discarded those self-registration translation units, leaving the registry empty (see firelab/gdalraster#826).
The visible symptom:
gdalraster::gdal_global_reg_names()
#> character(0)The upstream fix landed in GDAL 3.12.2 (OSGeo/gdal#13592), and muparser (required by parts of the Algorithm API) was added to the Rtools GDAL build in release 6768 (see firelab/gdalraster#858). Until a fixed GDAL ships in the default toolchain, and for anyone needing a pinned, feature-rich GDAL, this package provides a known good runtime: a custom GDAL build plus the activation logic to load it reliably.
| Term | What it is |
|---|---|
| MinGW-w64 | GCC-based toolchain producing native Win32 .exe/.dll (no POSIX emulation layer) |
| MSYS2 | Distribution + pacman package manager shipping several MinGW-w64 toolchain variants; the build environment, not the runtime target |
| UCRT | Microsoft’s Universal C Runtime (default since VS2015); mixing binaries linked against different C runtimes is unsafe (incompatible heap allocators, FILE*, etc.) |
| UCRT64 | The MSYS2 environment targeting UCRT — the variant compatible with Rtools |
| Rtools45 | CRAN’s Windows toolchain for R 4.5 and later; UCRT64/MinGW-based, so C++ ABI-compatible with MSYS2 UCRT64 builds of the same GCC line |
The invariant this package maintains: GDAL and gdalraster are both compiled with compatible MinGW/UCRT toolchains.
gdalraster must be rebuilt from sourceC++ has no standardized ABI across compilers. Name mangling, vtable layout, exception unwinding, and object layout all differ between MSVC and MinGW/GCC, and can drift between incompatible GCC configurations. gdalraster binds to GDAL’s C++ API through Rcpp (not a C extern "C" shim), so gdalraster.dll and libgdal-*.dll must come from the same ABI world.
That is why install_gdalraster() builds from source against the bundle’s headers and import library rather than reusing the CRAN binary (which is linked — statically — against Rtools’ own GDAL):
gdalraster.windows::install_gdalraster()Internally this uses withr::with_makevars(), which writes a scoped Makevars file and points the R_MAKEVARS_USER environment variable at it for the duration of the install — compile/link flags never leak into your persistent configuration.
From tools/build_gdal.sh:
| Flag | Purpose |
|---|---|
-DGDAL_USE_MUPARSER=ON |
Algorithm API support (expression evaluation) |
-DGDAL_USE_ARROW/PARQUET/HDF5/NETCDF/GEOS/SPATIALITE=ON |
Extended driver profile beyond the lean Rtools build |
-DGDAL_ENABLE_DRIVER_PDF=OFF (+ poppler/podofo/pdfium OFF) |
The MSYS2 libpodofo.dll fails its DLL initialization routine (Windows error 1114) in any process; a libgdal that imports it cannot be loaded at all, so the PDF driver is excluded from the bundle |
-DGDAL_HIDE_INTERNAL_SYMBOLS=ON |
Restricts the export table to the public API (PE/COFF DLLs cap export ordinals at 65,535 and GDAL’s full symbol set exceeds it — see OSGeo/gdal#4706) |
-Wl,--kill-at |
Strips @N stdcall decoration from exports so symbol names match what loaders expect |
-static-libgcc -static-libstdc++ + whole-archive winpthread |
Embeds the GCC runtime into the DLLs — end users need neither Rtools nor MSYS2 at runtime |
collect_dlls.shProducing libgdal-*.dll is half the job; it imports dozens of transitive DLLs (GEOS, PROJ, Arrow’s deep tree, HDF5, …). tools/collect_dlls.sh walks the full PE import tree with ntldd -R, copies every dependency that resolves to the UCRT64 prefix into the bundle’s bin/, and fails if any non-Windows-system dependency remains unresolved. The bundle is therefore a verified-closed set: the only external imports are Windows system DLLs.
Final bundle layout:
<gdal_home>/
├── bin/ libgdal-*.dll + closed transitive DLL set
├── include/ headers (compile-time, for install_gdalraster())
├── lib/ libgdal.dll.a import library (compile-time)
├── share/ gdal/ + proj/ runtime data
└── python/ osgeo_utils (pure-python, for embedded-python algorithms)
No executables ship in the bundle (BUILD_APPS=OFF): consumers are R processes loading the DLL, not command-line users.
Windows resolves a DLL’s import table at load time using a fixed search order (executable directory, System32, then PATH). When library(gdalraster) loads gdalraster.dll, Windows immediately needs libgdal-*.dll — if the bundle’s bin/ is not discoverable at that exact moment, loading fails with LoadLibrary failure.
activate_gdal_runtime() handles this, session-scoped:
<gdal_home>/bin to PATHGDAL_DATA, PROJ_LIB, PROJ_DATA (GDAL and PROJ require their runtime data trees for CRS operations)<gdal_home>/python to PYTHONPATH (next section)dyn.load(..., local = FALSE, now = TRUE) — mapping it into the process’s loaded-module list before gdalraster.dll asks for it; local = FALSE loads into the global symbol namespace so subsequent DLLs resolve against itSome GDAL algorithms (e.g. gdal driver gpkg validate) are thin C++ entry points around Python implementations. At first use, libgdal locates a python.exe on PATH, dynamically loads the matching libpython DLL (no static CPython link), calls Py_Initialize(), and imports osgeo_utils.samples.validate_gpkg.
osgeo_utils (GDAL’s gdal-utils distribution) is pure Python — no compiled extension modules, hence no CPython version/ABI coupling. The bundle ships it under <gdal_home>/python, version-locked to the built GDAL tag, and activation exposes it via PYTHONPATH. The compiled osgeo SWIG bindings are deliberately not bundled: they would pin the bundle to a single CPython ABI, and the Python-implemented validators degrade gracefully without them.
alg <- gdalraster::gdal_alg(cmd = "driver gpkg validate")
alg$setArg("dataset", "file.gpkg")
alg$setArg("full-check", TRUE)
alg$run()
alg$output()These are independent concerns, and conflating them is the most common source of confusion:
install_gdalraster()): PKG_CPPFLAGS points at <gdal_home>/include, PKG_LIBS at <gdal_home>/lib — scoped via withr.PATH/preload state — handled by activate_gdal_runtime().A successful link does not imply a loadable session, and vice versa.
The bundle is built entirely from the repository — no local machine state. For a new GDAL release:
git tag gdal-v3.14.0 && git push origin gdal-v3.14.0or dispatch the build workflow with gdal_version=v3.14.0. The version string drives the GDAL source checkout, cache key, asset name (gdal-ucrt64-v3.14.0-windows-x64.zip), and release tag. CI cache keys hash the build scripts, so any build-logic change forces a fresh compile. Published bundles are available on the releases page.
Local (non-CI) reproduction from an MSYS2 UCRT64 shell:
export GDAL_VER=v3.14.0
export INSTALL_DIR=/c/gdal-install
export BUNDLE_DIR=/c/gdal-bundle
bash tools/build_gdal.sh
bash tools/collect_dlls.sh