From ca3a73136aec7505d9b8ab80eb4bc7aef9c4e7c3 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 05:41:01 +0000 Subject: [PATCH 01/29] feat(extensions): refresh PostGIS raster support against main --- .../maintainers/extension-packaging-policy.md | 14 +- .../src/generated/extension-smoke.ts | 5 +- .../native/tools/build-windows-extensions.sh | 23 ++ .../tools/windows-extension-sources.mts | 128 ++++++++-- .../tools/windows-extension-sources.test.mts | 85 ++++++- src/extensions/catalog/native-components.toml | 18 +- src/extensions/evidence/matrix.toml | 1 + .../postgis/dependencies/gdal/source.toml | 4 + .../postgis/dependencies/geos/source.toml | 4 +- .../postgis/dependencies/json-c/source.toml | 4 +- .../postgis/dependencies/libxml2/source.toml | 4 +- .../postgis/dependencies/proj/source.toml | 4 +- .../postgis/dependencies/sqlite/source.toml | 4 +- src/extensions/external/postgis/recipe.toml | 48 +++- src/extensions/external/postgis/source.toml | 8 +- .../targets/native-static-registry.toml | 28 ++- .../external/postgis/targets/native.toml | 30 ++- .../external/postgis/targets/wasix.toml | 47 +++- .../external/postgis/tools/build_wasix.sh | 87 ++++++- .../postgis/tools/reproducible-time.sh | 30 ++- .../postgis/tools/windows/build-sql.sh | 3 + .../postgis/tools/windows/meson.build.in | 197 +++++++++------ .../postgis/tools/windows/postgis_config.h.in | 2 +- .../postgis/upstream-license-data.json | 70 ++++-- .../generated/docs/extension-evidence.json | 1 + .../generated/extensions.catalog.json | 2 +- .../generated/mobile/static-extensions.tsv | 2 +- .../generated/mobile/static-registry.json | 26 ++ src/extensions/generated/sdk/extensions.json | 119 +++++++-- .../sdk/ios-static-dependencies.json | 1 + .../generated/wasix/extensions.json | 17 +- .../extension-upstream-licenses.test.mts | 3 +- .../tools/native-component-contract.test.mts | 19 +- .../runtime/bin/build-postgres18-linux.sh | 136 +++++++++-- .../runtime/bin/build-postgres18-macos.sh | 142 +++++++++-- .../runtime/bin/mobile-postgis-extensions.sh | 228 ++++++++++++++++-- .../bin/mobile-postgis-extensions.test.sh | 109 +++++++++ .../bin/postgis-reproducible-time.test.sh | 10 +- src/native/runtime/moon.yml | 6 +- src/native/runtime/tools/test-symbol-scope.sh | 2 +- .../rust-bindings/src/generated/extensions.rs | 44 +++- .../src/liboliphaunt/root/extensions.rs | 8 + .../android/extension-legal-catalog.json | 30 ++- .../oliphaunt/android/extensions.properties | 2 +- ...OliphauntExtensionCatalogContractTest.java | 20 +- .../react-native/src/generated/extensions.ts | 56 ++++- .../sdks/ts/src/generated/extensions.ts | 55 ++++- src/test-fixtures/extensions/postgis.sql | 30 +++ .../runtime/assets/build/build_wasix_gdal.sh | 124 ++++++++++ .../build/wasix_shim/gdal_wasix_compat.h | 22 ++ .../runtime/assets/build/wasix_third_party.sh | 4 +- .../runtime/assets/generated/wasix-dl.exports | 10 + .../runtime/tools/xtask/src/asset_checks.rs | 2 + .../runtime/tools/xtask/src/asset_pipeline.rs | 2 +- src/wasix/runtime/tools/xtask/src/fs_utils.rs | 32 +++ .../src/oliphaunt/generated_extensions.rs | 14 +- .../sdks/rust/src/oliphaunt/postgres_mod.rs | 1 + .../sdks/ts/src/__tests__/extensions.test.ts | 31 +++ .../ts/src/hosts/browser/wasix-runtime.ts | 1 + src/wasix/sdks/ts/src/resources/extensions.ts | 2 + 60 files changed, 1878 insertions(+), 283 deletions(-) create mode 100644 src/extensions/external/postgis/dependencies/gdal/source.toml create mode 100755 src/native/runtime/bin/mobile-postgis-extensions.test.sh create mode 100755 src/wasix/runtime/assets/build/build_wasix_gdal.sh create mode 100644 src/wasix/runtime/assets/build/wasix_shim/gdal_wasix_compat.h diff --git a/src/docs/maintainers/extension-packaging-policy.md b/src/docs/maintainers/extension-packaging-policy.md index e08f30b94..60f96138c 100644 --- a/src/docs/maintainers/extension-packaging-policy.md +++ b/src/docs/maintainers/extension-packaging-policy.md @@ -458,10 +458,16 @@ OpenSSL checkout and links `pgcrypto` against the staged static `libcrypto`. `uuid-ossp` is mobile-prebuilt through the first-party portable UUID static `libuuid` archive. The Windows native producer links the same portable UUID source directly into the `uuid-ossp` module and installs the matching -control/SQL files. The Windows native PostGIS producer builds the pinned GEOS, -PROJ, SQLite, json-c, and libxml2 dependency stack, links the generated -`postgis-3` module against those static archives, and stages the matching -extension SQL plus `proj/proj.db`. +control/SQL files. The Windows native PostGIS producer builds the pinned GDAL, +GEOS, PROJ, SQLite, json-c, and libxml2 dependency stack, links the generated +`postgis-3` and `postgis_raster-3` modules against those static archives, and stages +the matching extension SQL, GDAL data, and `proj/proj.db`. The PostGIS product +owns both SQL extensions; applications can enable raster with +`CREATE EXTENSION postgis_raster` after enabling `postgis`. Mobile carriers +register the combined core/raster archive. Desktop and WASIX carriers ship +separate modules. GDAL includes GTiff/VRT and GeoJSON/Shapefile drivers; raster +smoke tests explicitly enable GTiff and verify pixel values, dimensions, and +SRID through a GeoTIFF round trip after creation and restart. ## Target-Specific PG18.4 Support diff --git a/src/examples/native/react-native-expo/src/generated/extension-smoke.ts b/src/examples/native/react-native-expo/src/generated/extension-smoke.ts index 08d274371..7452feb56 100644 --- a/src/examples/native/react-native-expo/src/generated/extension-smoke.ts +++ b/src/examples/native/react-native-expo/src/generated/extension-smoke.ts @@ -7,7 +7,7 @@ export type GeneratedMobileExtensionProof = { readonly selectedExtensionDependencies: readonly string[]; }; -export const GENERATED_MOBILE_EXTENSION_METADATA_SHA256 = "c1d2e09905d7ecc0172173b34b9e4104dad58987503dd3dab7af4ae78890d9f3" as const; +export const GENERATED_MOBILE_EXTENSION_METADATA_SHA256 = "26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a" as const; export const GENERATED_MOBILE_EXTENSION_PLAN = [ { @@ -357,11 +357,12 @@ export const GENERATED_MOBILE_EXTENSION_SMOKE = { "SELECT * FROM finish();" ], "postgis": [ + "CREATE EXTENSION IF NOT EXISTS postgis_raster;", "DROP TABLE IF EXISTS oliphaunt_postgis_points;", "CREATE TABLE oliphaunt_postgis_points(id int PRIMARY KEY, geom geometry(Point, 4326));", "INSERT INTO oliphaunt_postgis_points VALUES\n (1, ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326)),\n (2, ST_SetSRID(ST_MakePoint(-71.061, 48.433), 4326));", "CREATE INDEX oliphaunt_postgis_points_gix ON oliphaunt_postgis_points USING GIST (geom);", - "-- oliphaunt-verify\nDO $$\nDECLARE\n distance float8;\n srid int;\n area float8;\n polygons int;\n nearby int;\nBEGIN\n SELECT ST_Distance(ST_GeomFromText('POINT(0 0)'), ST_GeomFromText('POINT(3 4)')) INTO distance;\n IF distance <> 5 THEN\n RAISE EXCEPTION 'postgis geometry distance failed: %', distance;\n END IF;\n IF ST_AsText(ST_Buffer(ST_GeomFromText('POINT(0 0)'), 1, 'quad_segs=1')) IS NULL THEN\n RAISE EXCEPTION 'postgis buffer failed';\n END IF;\n IF NOT ST_Within(\n ST_GeomFromText('POINT(0.5 0.5)'),\n ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))')\n ) THEN\n RAISE EXCEPTION 'postgis within failed';\n END IF;\n SELECT ST_SRID(ST_Transform(ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326), 3857)) INTO srid;\n IF srid <> 3857 THEN\n RAISE EXCEPTION 'postgis transform failed: %', srid;\n END IF;\n SELECT ST_Area(ST_Transform(ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-71.1776848522251 42.3902896512902,-71.1776843766797 42.3903701743239,-71.1775844305465 42.3903829478009,-71.1775825927231 42.3902893647987,-71.1776848522251 42.3902896512902)')), 4326), 26986)) INTO area;\n IF area <= 0 THEN\n RAISE EXCEPTION 'postgis projected area failed: %', area;\n END IF;\n SELECT ST_NumGeometries(ST_Polygonize(ARRAY[\n ST_GeomFromText('LINESTRING(0 0, 1 0)'),\n ST_GeomFromText('LINESTRING(1 0, 1 1)'),\n ST_GeomFromText('LINESTRING(1 1, 0 1)'),\n ST_GeomFromText('LINESTRING(0 1, 0 0)')\n ])) INTO polygons;\n IF polygons <> 1 THEN\n RAISE EXCEPTION 'postgis polygonize failed: %', polygons;\n END IF;\n SELECT count(*) INTO nearby\n FROM oliphaunt_postgis_points\n WHERE ST_DWithin(\n geom::geography,\n ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326)::geography,\n 200\n );\n IF nearby <> 2 THEN\n RAISE EXCEPTION 'postgis dwithin failed: %', nearby;\n END IF;\nEND $$;" + "-- oliphaunt-verify\nDO $$\nDECLARE\n distance float8;\n srid int;\n area float8;\n polygons int;\n nearby int;\n gdal_drivers int;\n raster_roundtrip raster;\n raster_value float8;\nBEGIN\n SET LOCAL postgis.gdal_enabled_drivers = 'GTiff';\n SELECT ST_Distance(ST_GeomFromText('POINT(0 0)'), ST_GeomFromText('POINT(3 4)')) INTO distance;\n IF distance <> 5 THEN\n RAISE EXCEPTION 'postgis geometry distance failed: %', distance;\n END IF;\n IF ST_AsText(ST_Buffer(ST_GeomFromText('POINT(0 0)'), 1, 'quad_segs=1')) IS NULL THEN\n RAISE EXCEPTION 'postgis buffer failed';\n END IF;\n IF NOT ST_Within(\n ST_GeomFromText('POINT(0.5 0.5)'),\n ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))')\n ) THEN\n RAISE EXCEPTION 'postgis within failed';\n END IF;\n SELECT ST_SRID(ST_Transform(ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326), 3857)) INTO srid;\n IF srid <> 3857 THEN\n RAISE EXCEPTION 'postgis transform failed: %', srid;\n END IF;\n SELECT ST_Area(ST_Transform(ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-71.1776848522251 42.3902896512902,-71.1776843766797 42.3903701743239,-71.1775844305465 42.3903829478009,-71.1775825927231 42.3902893647987,-71.1776848522251 42.3902896512902)')), 4326), 26986)) INTO area;\n IF area <= 0 THEN\n RAISE EXCEPTION 'postgis projected area failed: %', area;\n END IF;\n SELECT ST_NumGeometries(ST_Polygonize(ARRAY[\n ST_GeomFromText('LINESTRING(0 0, 1 0)'),\n ST_GeomFromText('LINESTRING(1 0, 1 1)'),\n ST_GeomFromText('LINESTRING(1 1, 0 1)'),\n ST_GeomFromText('LINESTRING(0 1, 0 0)')\n ])) INTO polygons;\n IF polygons <> 1 THEN\n RAISE EXCEPTION 'postgis polygonize failed: %', polygons;\n END IF;\n SELECT count(*) INTO nearby\n FROM oliphaunt_postgis_points\n WHERE ST_DWithin(\n geom::geography,\n ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326)::geography,\n 200\n );\n IF nearby <> 2 THEN\n RAISE EXCEPTION 'postgis dwithin failed: %', nearby;\n END IF;\n SELECT count(*) INTO gdal_drivers\n FROM ST_GDALDrivers()\n WHERE short_name = 'GTiff' AND can_read AND can_write;\n IF gdal_drivers <> 1 THEN\n RAISE EXCEPTION 'postgis raster GeoTIFF driver is unavailable';\n END IF;\n SELECT ST_FromGDALRaster(\n ST_AsGDALRaster(\n ST_AddBand(\n ST_MakeEmptyRaster(2, 2, 0, 0, 1, -1, 0, 0, 4326),\n '8BUI'::text,\n 7::double precision,\n 0::double precision\n ),\n 'GTiff'\n )\n ) INTO raster_roundtrip;\n SELECT ST_Value(raster_roundtrip, 1, 1, 1) INTO raster_value;\n IF raster_value IS DISTINCT FROM 7 OR ST_SRID(raster_roundtrip) IS DISTINCT FROM 4326\n OR ST_Width(raster_roundtrip) IS DISTINCT FROM 2 OR ST_Height(raster_roundtrip) IS DISTINCT FROM 2 THEN\n RAISE EXCEPTION 'postgis raster GDAL round-trip failed: value %, srid %',\n raster_value,\n ST_SRID(raster_roundtrip);\n END IF;\nEND $$;" ], "seg": [ "DO $$ BEGIN IF '7(+-)1'::seg::text <> '6 .. 8' THEN RAISE EXCEPTION 'seg cast failed'; END IF; END $$;" diff --git a/src/extensions/artifacts/native/tools/build-windows-extensions.sh b/src/extensions/artifacts/native/tools/build-windows-extensions.sh index eb17ffd30..a0ab35b82 100644 --- a/src/extensions/artifacts/native/tools/build-windows-extensions.sh +++ b/src/extensions/artifacts/native/tools/build-windows-extensions.sh @@ -87,6 +87,29 @@ windows_extension_postgis_dependencies() { cp "$work_root/proj-windows-build/data/proj.db" "$prefix/share/proj/proj.db" fi fi + prefix="$dependency_root/gdal" + if ! (first_file "$prefix" gdal.lib) >/dev/null 2>&1 || [ ! -f "$prefix/share/gdal/gdalvrt.xsd" ]; then + windows_extension_cmake gdal "$external_checkout_root/gdal" "$prefix" \ + -DBUILD_SHARED_LIBS=OFF -DBUILD_APPS=OFF -DBUILD_TESTING=OFF \ + -DBUILD_CSHARP_BINDINGS=OFF -DBUILD_JAVA_BINDINGS=OFF -DBUILD_PYTHON_BINDINGS=OFF \ + -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF -DOGR_BUILD_OPTIONAL_DRIVERS=OFF \ + -DGDAL_ENABLE_DRIVER_GTIFF=ON -DGDAL_ENABLE_DRIVER_VRT=ON \ + -DOGR_ENABLE_DRIVER_GEOJSON=ON -DOGR_ENABLE_DRIVER_SHAPE=ON \ + -DGDAL_USE_EXTERNAL_LIBS=OFF -DGDAL_USE_INTERNAL_LIBS=ON -DGDAL_USE_OPENMP=OFF \ + -DGDAL_USE_CURL=OFF -DGDAL_USE_OPENSSL=OFF -DGDAL_USE_ICONV=OFF \ + -DGDAL_USE_GEOS=ON "-DGEOS_INCLUDE_DIR=$(native_path "$dependency_root/geos/include")" \ + "-DGEOS_LIBRARY=$(native_path "$(first_file "$dependency_root/geos" geos_c.lib)")" \ + -DGDAL_USE_JSONC=ON -DGDAL_USE_JSONC_INTERNAL=OFF \ + "-DJSONC_INCLUDE_DIR=$(native_path "$dependency_root/json-c/include/json-c")" \ + "-DJSONC_LIBRARY=$(native_path "$(first_file "$dependency_root/json-c" json-c.lib json-c-static.lib)")" \ + -DGDAL_USE_LIBXML2=ON "-DLIBXML2_INCLUDE_DIR=$(native_path "$dependency_root/libxml2/include/libxml2")" \ + "-DLIBXML2_LIBRARY=$(native_path "$(first_file "$dependency_root/libxml2" libxml2s.lib libxml2.lib xml2.lib)")" \ + -DGDAL_USE_PROJ=ON "-DPROJ_INCLUDE_DIR=$(native_path "$dependency_root/proj/include")" \ + "-DPROJ_LIBRARY=$(native_path "$(first_file "$dependency_root/proj" proj.lib libproj.lib)")" \ + -DGDAL_USE_SQLITE3=ON "-DSQLite3_INCLUDE_DIR=$(native_path "$dependency_root/sqlite/include")" \ + "-DSQLite3_LIBRARY=$(native_path "$dependency_root/sqlite/lib/sqlite3.lib")" \ + -DACCEPT_MISSING_SQLITE3_MUTEX_ALLOC=ON + fi } windows_extension_generate() { diff --git a/src/extensions/artifacts/native/tools/windows-extension-sources.mts b/src/extensions/artifacts/native/tools/windows-extension-sources.mts index 3a2ef1259..9690b8071 100644 --- a/src/extensions/artifacts/native/tools/windows-extension-sources.mts +++ b/src/extensions/artifacts/native/tools/windows-extension-sources.mts @@ -117,6 +117,10 @@ export function prepareSimple(config: Config, extension: string) { 'bcrypt.lib', 'ws2_32.lib', 'user32.lib', + 'ole32.lib', + 'oleaut32.lib', + 'uuid.lib', + 'msvcprt.lib', ); } else { copyFileSync( @@ -244,6 +248,7 @@ export function preparePostgis(config: Config) { LOCALE_DIR: path.join(config.install, 'share/locale').replaceAll('\\', '/'), BUILD_DATE: sourceDate(config), GEOS_VERSION: number(componentVersion('geos')), + GDAL_VERSION: number(componentVersion('gdal')), LIBXML_VERSION: componentVersion('libxml2'), PROJ_VERSION: number(componentVersion('proj')), VERSION: pgis.version, @@ -281,6 +286,30 @@ export function preparePostgis(config: Config) { path.join(directory, 'extensions/postgis/postgis.control'), { EXTVERSION: pgis.version, EXTENSION: 'postgis', MODULEPATH: '$libdir/postgis-3' }, ); + write( + path.join(directory, 'raster/raster_config.h'), + `#ifndef POSTGIS_RASTER_CONFIG_H +#define POSTGIS_RASTER_CONFIG_H 1 +#define POSTGIS_GDAL_VERSION ${values.GDAL_VERSION} +#endif +`, + ); + expand( + path.join(directory, 'extensions/postgis_raster/postgis_raster.control.in'), + path.join(directory, 'extensions/postgis_raster/postgis_raster.control'), + { + EXTVERSION: pgis.version, + EXTENSION: 'postgis_raster', + MODULEPATH: '$libdir/postgis_raster-3', + }, + ); + for (const file of ['raster/rt_core/librtcore.h', 'raster/rt_pg/rtpostgis.c']) { + replace( + path.join(directory, file), + '# define __attribute__ (x)', + '# ifndef __attribute__\n# define __attribute__(x)\n# endif', + ); + } copyFileSync( path.join(postgisTemplates(config), 'postgis-compat.h'), path.join(directory, 'oliphaunt_postgis_windows_compat.h'), @@ -289,7 +318,7 @@ export function preparePostgis(config: Config) { path.join(postgisTemplates(config), 'flatgeobuf-compat.h'), path.join(directory, 'oliphaunt_flatgeobuf_windows_compat.h'), ); - for (const folder of ['postgis', 'libpgcommon', 'liblwgeom']) { + for (const folder of ['postgis', 'libpgcommon', 'liblwgeom', 'raster/rt_core', 'raster/rt_pg']) { for (const file of glob(path.join(directory, folder), '**/*.{c,h}')) { const target = path.join(directory, folder, file), text = read(target); @@ -349,10 +378,14 @@ export function postgisMeson(config: Config) { 'deps/flatgeobuf', 'deps/flatgeobuf/include', 'deps/ryu', + 'raster', + 'raster/rt_core', + 'raster/rt_pg', ].map((file) => `/I${path.join(directory, file)}`), ); cArgs.push( ...[ + 'gdal/include', 'geos/include', 'proj/include', 'json-c/include', @@ -362,6 +395,7 @@ export function postgisMeson(config: Config) { ); const linkArgs = [ firstLibrary(path.join(dependencies, 'flatgeobuf'), ['flatgeobuf.lib']), + firstLibrary(path.join(dependencies, 'gdal'), ['gdal.lib']), firstLibrary(path.join(dependencies, 'geos'), ['geos_c.lib']), firstLibrary(path.join(dependencies, 'geos'), ['geos.lib']), firstLibrary(path.join(dependencies, 'proj'), ['proj.lib', 'libproj.lib']), @@ -373,10 +407,16 @@ export function postgisMeson(config: Config) { 'advapi32.lib', 'shell32.lib', 'user32.lib', + 'ole32.lib', + 'oleaut32.lib', + 'uuid.lib', + 'msvcprt.lib', ]; const data = [ 'extensions/postgis/postgis.control', ...glob(directory, 'extensions/postgis/sql/postgis--*.sql'), + 'extensions/postgis_raster/postgis_raster.control', + ...glob(directory, 'extensions/postgis_raster/sql/postgis_raster--*.sql'), ]; const contrib = [ 'postgis/legacy.sql', @@ -405,6 +445,7 @@ export function postgisMeson(config: Config) { path.join(dependencies, 'proj/share/proj/proj.db'), path.join(directory, 'share/proj/proj.db'), ); + copy(path.join(dependencies, 'gdal/share/gdal'), path.join(directory, 'share/gdal')); appendSubdir(config, 'postgis'); } @@ -413,13 +454,30 @@ const withoutTransactions = (text: string) => const joined = (...texts: string[]) => texts.map((text) => (text.endsWith('\n') ? text : text + '\n')).join(''); +function extensionDropGuards(text: string, extension: string) { + return text + .split(/\r?\n/) + .flatMap((line) => { + const drop = line.match(/^(DROP .*);/); + return drop + ? [ + `SELECT @extschema@.postgis_extension_drop_if_exists('${extension}', '${drop[1]}');`, + line, + ] + : [line]; + }) + .join('\n'); +} + export function postgisSql(config: Config, phase: string) { const directory = postgisDirectory(config), pgis = postgisVersion(directory); const pg = path.join(directory, 'postgis'), sql = path.join(directory, 'extensions/postgis/sql'); const raster = path.join(directory, 'raster/rt_pg'); + const rasterSql = path.join(directory, 'extensions/postgis_raster/sql'); mkdirSync(sql, { recursive: true }); + mkdirSync(rasterSql, { recursive: true }); if (phase === 'postgis-sql-preprocess') { const template = ( input: string, @@ -469,11 +527,19 @@ export function postgisSql(config: Config, phase: string) { path.join(raster, name + '.sql.in'), path.join(raster, name + '.sql'), [pg, raster], - '$libdir/rtpostgis-3', + '$libdir/postgis_raster-3', false, true, ); } + template( + path.join(raster, 'rtpostgis.sql.in'), + path.join(rasterSql, 'rtpostgis.sql'), + [pg, raster], + '$libdir/postgis_raster-3', + true, + false, + ); } else if (phase === 'postgis-sql-upgrade') { const upgrade = (body: string) => joined( @@ -493,20 +559,23 @@ export function postgisSql(config: Config, phase: string) { upgrade(path.join(sql, 'postgis_upgrade_for_extension.sql.in')), ); write(path.join(sql, 'postgis_upgrade_for_extension.sql'), extensionUpgrade); + write(path.join(sql, 'postgis_upgrade.sql'), extensionDropGuards(extensionUpgrade, 'postgis')); write( - path.join(sql, 'postgis_upgrade.sql'), - extensionUpgrade - .split(/\r?\n/) - .flatMap((line) => { - const drop = line.match(/^(DROP .*);/); - return drop - ? [ - `SELECT @extschema@.postgis_extension_drop_if_exists('postgis', '${drop[1]}');`, - line, - ] - : [line]; - }) - .join('\n'), + path.join(rasterSql, 'rtpostgis_upgrade.sql'), + extensionDropGuards( + withoutTransactions( + joined( + ...[ + path.join(pg, 'common_before_upgrade.sql'), + path.join(raster, 'rtpostgis_upgrade_cleanup.sql'), + path.join(rasterSql, 'rtpostgis_upgrade.sql.in'), + path.join(raster, 'rtpostgis_drop.sql'), + path.join(pg, 'common_after_upgrade.sql'), + ].map(read), + ), + ), + 'postgis_raster', + ), ); write( path.join(sql, 'raster_drop_all.sql'), @@ -554,9 +623,34 @@ export function postgisSql(config: Config, phase: string) { write(path.join(sql, 'postgis--TEMPLATED--TO--ANY.sql'), tag); write(path.join(sql, `postgis--${pgis.version}--ANY.sql`), tag); write(path.join(sql, 'postgis--unpackaged.sql'), '-- Nothing to do here\n'); + const rasterGuard = '\\echo Use "CREATE EXTENSION postgis_raster" to load this file. \\quit'; + write( + path.join(rasterSql, `postgis_raster--${pgis.version}.sql`), + joined(rasterGuard, read(path.join(rasterSql, 'rtpostgis.sql'))), + ); + write( + path.join(rasterSql, `postgis_raster--ANY--${pgis.version}.sql`), + joined( + rasterGuard, + ...[ + path.join(directory, 'extensions/postgis_extension_helper.sql'), + path.join(rasterSql, 'rtpostgis_upgrade.sql'), + path.join(directory, 'extensions/postgis_extension_helper_uninstall.sql'), + ].map(read), + ), + ); + const rasterTag = tag.replaceAll('extension postgis ', 'extension postgis_raster '); + write(path.join(rasterSql, 'postgis_raster--TEMPLATED--TO--ANY.sql'), rasterTag); + write(path.join(rasterSql, `postgis_raster--${pgis.version}--ANY.sql`), rasterTag); + write(path.join(rasterSql, 'postgis_raster--unpackaged.sql'), '-- Nothing to do here\n'); } else if (phase === 'postgis-sql-unpackaged') { - const file = path.join(sql, `postgis--unpackaged--${pgis.version}.sql`); - write(file, joined(read(file), read(path.join(sql, `postgis--ANY--${pgis.version}.sql`)))); + for (const [name, folder] of [ + ['postgis', sql], + ['postgis_raster', rasterSql], + ]) { + const file = path.join(folder, `${name}--unpackaged--${pgis.version}.sql`); + write(file, joined(read(file), read(path.join(folder, `${name}--ANY--${pgis.version}.sql`)))); + } } else throw new Error(`unknown SQL phase ${phase}`); } diff --git a/src/extensions/artifacts/native/tools/windows-extension-sources.test.mts b/src/extensions/artifacts/native/tools/windows-extension-sources.test.mts index 8b409d260..528219b40 100644 --- a/src/extensions/artifacts/native/tools/windows-extension-sources.test.mts +++ b/src/extensions/artifacts/native/tools/windows-extension-sources.test.mts @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import path from 'node:path'; -import { pgxsSources, prepareSimple } from './windows-extension-sources.mts'; +import { pgxsSources, postgisSql, prepareSimple } from './windows-extension-sources.mts'; test('Windows textsearch compiles upstream layout checks and installs the upgrade chain', () => { const root = mkdtempSync(path.join(tmpdir(), 'textsearch-windows-')); @@ -42,3 +42,86 @@ test('Windows textsearch compiles upstream layout checks and installs the upgrad rmSync(root, { recursive: true, force: true }); } }); + +test('Windows raster SQL installs and upgrades the raster module under its own extension identity', () => { + const root = mkdtempSync(path.join(tmpdir(), 'postgis-windows-')); + const directory = path.join(root, 'postgres/contrib/oliphaunt_external/postgis'); + const sql = 'extensions/postgis/sql/'; + const rasterSql = 'extensions/postgis_raster/sql/'; + const write = (file: string, text: string) => { + const target = path.join(directory, file); + mkdirSync(path.dirname(target), { recursive: true }); + writeFileSync(target, text); + }; + const originalEpoch = process.env.SOURCE_DATE_EPOCH; + try { + const manifest = path.join(root, 'src/extensions/external/postgis/source.toml'); + mkdirSync(path.dirname(manifest), { recursive: true }); + writeFileSync(manifest, 'source_date_epoch = 1780943336\n'); + process.env.SOURCE_DATE_EPOCH = '1780943336'; + write( + 'Version.config', + 'POSTGIS_MAJOR_VERSION=3\nPOSTGIS_MINOR_VERSION=6\nPOSTGIS_MICRO_VERSION=4\n', + ); + for (const file of [ + 'postgis/common_before_upgrade.sql', + 'postgis/postgis_before_upgrade.sql', + 'postgis/postgis_after_upgrade.sql', + 'postgis/common_after_upgrade.sql', + 'postgis/postgis_upgrade.sql.in', + sql + 'postgis_upgrade_for_extension.sql.in', + 'raster/rt_pg/rtpostgis_upgrade_cleanup.sql', + 'raster/rt_pg/rtpostgis_drop.sql', + 'raster/rt_pg/uninstall_rtpostgis.sql', + sql + 'raster_unpackage_body.sql', + sql + 'postgis_for_extension.sql', + sql + 'spatial_ref_sys_config_dump.sql', + sql + 'spatial_ref_sys.sql', + 'extensions/postgis_extension_helper.sql', + 'extensions/postgis_extension_helper_uninstall.sql', + ]) + write(file, '-- fixture\n'); + write( + rasterSql + 'rtpostgis.sql', + "CREATE FUNCTION raster_fixture() RETURNS text AS '$libdir/postgis_raster-3', 'raster_fixture' LANGUAGE C;\n", + ); + write(rasterSql + 'rtpostgis_upgrade.sql.in', 'DROP FUNCTION raster_fixture();\n'); + write('extensions/postgis/unpackage_raster_if_needed.sql', '-- UNPACKAGE_CODE\n-- end\n'); + const config = { + repo: root, + work: root, + postgres: path.join(root, 'postgres'), + install: path.join(root, 'install'), + }; + postgisSql(config, 'postgis-sql-upgrade'); + postgisSql(config, 'postgis-sql-install'); + const read = (file: string) => readFileSync(path.join(directory, file), 'utf8'); + const install = read(rasterSql + 'postgis_raster--3.6.4.sql'); + assert(install.includes('CREATE EXTENSION postgis_raster')); + assert(install.includes('$libdir/postgis_raster-3')); + const upgrade = read(rasterSql + 'postgis_raster--ANY--3.6.4.sql'); + assert( + upgrade.includes( + "postgis_extension_drop_if_exists('postgis_raster', 'DROP FUNCTION raster_fixture()')", + ), + ); + assert(!upgrade.includes('BEGIN;')); + assert( + read(rasterSql + 'postgis_raster--3.6.4--ANY.sql').includes( + 'extension postgis_raster version', + ), + ); + for (const [folder, name] of [ + [sql, 'postgis'], + [rasterSql, 'postgis_raster'], + ]) { + write(folder + name + '--unpackaged--3.6.4.sql', '-- unpackaged objects\n'); + } + postgisSql(config, 'postgis-sql-unpackaged'); + assert(read(rasterSql + 'postgis_raster--unpackaged--3.6.4.sql').endsWith(upgrade)); + } finally { + if (originalEpoch === undefined) delete process.env.SOURCE_DATE_EPOCH; + else process.env.SOURCE_DATE_EPOCH = originalEpoch; + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/src/extensions/catalog/native-components.toml b/src/extensions/catalog/native-components.toml index 9f0b50544..db7673a47 100644 --- a/src/extensions/catalog/native-components.toml +++ b/src/extensions/catalog/native-components.toml @@ -68,6 +68,16 @@ archive-candidates = ["libiconv/lib/libiconv.a"] id = "libcharset" archive-candidates = ["libiconv/lib/libcharset.a"] +[[components]] +id = "gdal" +source = "gdal" +depends-on = ["geos", "proj", "libxml2", "json-c"] +runtime-files = ["gdal/gdalvrt.xsd"] + +[[components.link-units]] +id = "gdal" +archive-candidates = ["gdal/lib/libgdal.a"] + [[components]] id = "openssl" source = "openssl" @@ -93,28 +103,28 @@ extension = "postgis" family = "native" kind = "native-dynamic" targets = ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"] -roots = ["geos", "proj", "libxml2", "json-c"] +roots = ["gdal"] [[requirements]] extension = "postgis" family = "native" kind = "native-static-registry" targets = ["ios-xcframework"] -roots = ["geos", "proj", "libxml2", "json-c"] +roots = ["gdal"] [[requirements]] extension = "postgis" family = "native" kind = "native-static-registry" targets = ["android-arm64-v8a", "android-x86_64"] -roots = ["geos", "proj", "libxml2", "json-c", "libiconv"] +roots = ["libiconv", "gdal"] [[requirements]] extension = "postgis" family = "wasix" kind = "wasix-runtime" targets = ["wasix-portable"] -roots = ["geos", "proj", "libxml2", "json-c", "libiconv"] +roots = ["libiconv", "gdal"] [[requirements]] extension = "pgcrypto" diff --git a/src/extensions/evidence/matrix.toml b/src/extensions/evidence/matrix.toml index f2f3de647..8230a7391 100644 --- a/src/extensions/evidence/matrix.toml +++ b/src/extensions/evidence/matrix.toml @@ -14,6 +14,7 @@ source-digest-inputs = [ "src/extensions/external/pg_textsearch/tests/upgrade/source.toml", "src/extensions/external/pg_uuidv7/source.toml", "src/extensions/external/pgtap/source.toml", + "src/extensions/external/postgis/dependencies/gdal/source.toml", "src/extensions/external/postgis/dependencies/geos/source.toml", "src/extensions/external/postgis/dependencies/json-c/source.toml", "src/extensions/external/postgis/dependencies/libiconv/source.toml", diff --git a/src/extensions/external/postgis/dependencies/gdal/source.toml b/src/extensions/external/postgis/dependencies/gdal/source.toml new file mode 100644 index 000000000..3dbef63fd --- /dev/null +++ b/src/extensions/external/postgis/dependencies/gdal/source.toml @@ -0,0 +1,4 @@ +name = "gdal" +url = "https://github.com/OSGeo/gdal.git" +branch = "v3.13.3" +commit = "c8b4c45fca87d3e6fbf80e7a7898b8a661ad0edc" diff --git a/src/extensions/external/postgis/dependencies/geos/source.toml b/src/extensions/external/postgis/dependencies/geos/source.toml index a066a551b..79c4dd4f4 100644 --- a/src/extensions/external/postgis/dependencies/geos/source.toml +++ b/src/extensions/external/postgis/dependencies/geos/source.toml @@ -1,4 +1,4 @@ name = "geos" url = "https://github.com/libgeos/geos.git" -branch = "3.14.1" -commit = "c389f532d25fe6228861d9b19339f9cb57ca4bdb" +branch = "3.15.0" +commit = "d0228513abb0c29c185443cf2bfb06c9281024b5" diff --git a/src/extensions/external/postgis/dependencies/json-c/source.toml b/src/extensions/external/postgis/dependencies/json-c/source.toml index 9e3862321..ec0ed8a30 100644 --- a/src/extensions/external/postgis/dependencies/json-c/source.toml +++ b/src/extensions/external/postgis/dependencies/json-c/source.toml @@ -1,4 +1,4 @@ name = "json-c" url = "https://github.com/json-c/json-c.git" -branch = "json-c-0.18-20240915" -commit = "41a55cfcedb54d9c1874f2f0eb07b504091d7e37" +branch = "json-c-0.19-20260627" +commit = "aa716cd8d663c976b99b0f30f102ee1d8ef63146" diff --git a/src/extensions/external/postgis/dependencies/libxml2/source.toml b/src/extensions/external/postgis/dependencies/libxml2/source.toml index 349df0ecb..14bb9fddb 100644 --- a/src/extensions/external/postgis/dependencies/libxml2/source.toml +++ b/src/extensions/external/postgis/dependencies/libxml2/source.toml @@ -1,5 +1,5 @@ name = "libxml2" url = "https://gitlab.gnome.org/GNOME/libxml2.git" mirror_url = "https://github.com/GNOME/libxml2.git" -branch = "v2.14.6" -commit = "d23960a130c5bb82779c9405fbbf85e65fb3c57c" +branch = "v2.15.4" +commit = "96498992efa48d52b0e8b83058bd88dbdaf153c1" diff --git a/src/extensions/external/postgis/dependencies/proj/source.toml b/src/extensions/external/postgis/dependencies/proj/source.toml index 4e7134ddc..ead4a5c1d 100644 --- a/src/extensions/external/postgis/dependencies/proj/source.toml +++ b/src/extensions/external/postgis/dependencies/proj/source.toml @@ -1,4 +1,4 @@ name = "proj" url = "https://github.com/OSGeo/PROJ.git" -branch = "9.8.1" -commit = "f08fa86c478c4bbbf003b1ec751dd84aa6eca486" +branch = "9.9.0" +commit = "1df2be61c0e4732aa1326126113776b291ae7657" diff --git a/src/extensions/external/postgis/dependencies/sqlite/source.toml b/src/extensions/external/postgis/dependencies/sqlite/source.toml index 61eab3638..92dc47bb5 100644 --- a/src/extensions/external/postgis/dependencies/sqlite/source.toml +++ b/src/extensions/external/postgis/dependencies/sqlite/source.toml @@ -1,4 +1,4 @@ name = "sqlite" url = "https://github.com/sqlite/sqlite.git" -branch = "version-3.53.1" -commit = "ccd445d76a9362c63add000354fac84ba9022176" +branch = "version-3.54.1" +commit = "57520227c95396ce39ba6ecaac8d475b78dd3964" diff --git a/src/extensions/external/postgis/recipe.toml b/src/extensions/external/postgis/recipe.toml index 1a0758969..4a0b79d97 100644 --- a/src/extensions/external/postgis/recipe.toml +++ b/src/extensions/external/postgis/recipe.toml @@ -21,17 +21,53 @@ needs_superuser = true trusted = false [artifacts] -control_files = ["share/postgresql/extension/postgis.control"] -sql_globs = ["share/postgresql/extension/postgis--*.sql"] +control_files = [ + "share/postgresql/extension/postgis.control", + "share/postgresql/extension/postgis_raster.control", +] +sql_globs = [ + "share/postgresql/extension/postgis--*.sql", + "share/postgresql/extension/postgis_raster--*.sql", +] extension_sql_file_prefixes = [ "postgis_comments", "postgis_proc_set_search_path", + "postgis_raster", "rtpostgis", ] extension_sql_file_names = ["uninstall_postgis.sql"] native_modules = ["lib/postgresql/postgis-3.so"] -native_dependency_modules = ["lib/postgresql/liboliphaunt_postgis_deps.so"] +native_dependency_modules = [ + "lib/postgresql/postgis_raster-3.so", + "lib/postgresql/liboliphaunt_postgis_deps.so", +] data_files = [ + "share/postgresql/gdal/GDALLogoBW.svg", + "share/postgresql/gdal/GDALLogoColor.svg", + "share/postgresql/gdal/GDALLogoGS.svg", + "share/postgresql/gdal/LICENSE.TXT", + "share/postgresql/gdal/cubewerx_extra.wkt", + "share/postgresql/gdal/ecw_cs.wkt", + "share/postgresql/gdal/epsg.wkt", + "share/postgresql/gdal/esri_StatePlane_extra.wkt", + "share/postgresql/gdal/gdal_algorithm.schema.json", + "share/postgresql/gdal/gdalicon.png", + "share/postgresql/gdal/gdalinfo_output.schema.json", + "share/postgresql/gdal/gdalmdiminfo_output.schema.json", + "share/postgresql/gdal/gdalvrt.xsd", + "share/postgresql/gdal/leaflet_template.html", + "share/postgresql/gdal/ogr_fields_override.schema.json", + "share/postgresql/gdal/ogrinfo_output.schema.json", + "share/postgresql/gdal/ozi_datum.csv", + "share/postgresql/gdal/ozi_ellips.csv", + "share/postgresql/gdal/pci_datum.txt", + "share/postgresql/gdal/pci_ellips.txt", + "share/postgresql/gdal/stateplane.csv", + "share/postgresql/gdal/template_tiles.mapml", + "share/postgresql/gdal/tms_LINZAntarticaMapTileGrid.json", + "share/postgresql/gdal/tms_MapML_APSTILE.json", + "share/postgresql/gdal/tms_MapML_CBMTILE.json", + "share/postgresql/gdal/tms_NZTM2000.json", "share/postgresql/contrib/postgis-3.6/legacy.sql", "share/postgresql/contrib/postgis-3.6/legacy_gist.sql", "share/postgresql/contrib/postgis-3.6/legacy_minimal.sql", @@ -44,6 +80,7 @@ data_files = [ ] headers = [] licenses = [ + "share/licenses/gdal/LICENSE.TXT", "share/licenses/geos/COPYING", "share/licenses/geos/src/deps/ryu/LICENSE", "share/licenses/geos/src/deps/ryu/LICENSE-Apache2", @@ -62,6 +99,11 @@ licenses = [ "share/licenses/sqlite/LICENSE.md", ] +[[runtime_environment]] +name = "GDAL_DATA" +path = "share/postgresql/gdal" +required_file = "gdalvrt.xsd" + [[runtime_environment]] name = "PROJ_DATA" path = "share/postgresql/proj" diff --git a/src/extensions/external/postgis/source.toml b/src/extensions/external/postgis/source.toml index f5ea03afb..38805d43e 100644 --- a/src/extensions/external/postgis/source.toml +++ b/src/extensions/external/postgis/source.toml @@ -1,11 +1,11 @@ name = "postgis" url = "https://github.com/postgis/postgis.git" -branch = "3.6.3" -commit = "3d12666588a84b23a3147618eaa9b40b0fe5e796" -source_date_epoch = 1776193981 +branch = "3.6.4" +commit = "94d984bd083635c1d253db0f87cf80b32548e406" +source_date_epoch = 1780943336 [extension-control] sql-name = "postgis" source-path = "extensions/postgis/postgis.control.in" source-default-version = "@EXTVERSION@" -default-version = "3.6.3" +default-version = "3.6.4" diff --git a/src/extensions/external/postgis/targets/native-static-registry.toml b/src/extensions/external/postgis/targets/native-static-registry.toml index 55b5b4da0..569cd4447 100644 --- a/src/extensions/external/postgis/targets/native-static-registry.toml +++ b/src/extensions/external/postgis/targets/native-static-registry.toml @@ -10,6 +10,32 @@ platform_targets = [ ] module_files = ["postgis-3"] data_files = [ + "share/postgresql/gdal/GDALLogoBW.svg", + "share/postgresql/gdal/GDALLogoColor.svg", + "share/postgresql/gdal/GDALLogoGS.svg", + "share/postgresql/gdal/LICENSE.TXT", + "share/postgresql/gdal/cubewerx_extra.wkt", + "share/postgresql/gdal/ecw_cs.wkt", + "share/postgresql/gdal/epsg.wkt", + "share/postgresql/gdal/esri_StatePlane_extra.wkt", + "share/postgresql/gdal/gdal_algorithm.schema.json", + "share/postgresql/gdal/gdalicon.png", + "share/postgresql/gdal/gdalinfo_output.schema.json", + "share/postgresql/gdal/gdalmdiminfo_output.schema.json", + "share/postgresql/gdal/gdalvrt.xsd", + "share/postgresql/gdal/leaflet_template.html", + "share/postgresql/gdal/ogr_fields_override.schema.json", + "share/postgresql/gdal/ogrinfo_output.schema.json", + "share/postgresql/gdal/ozi_datum.csv", + "share/postgresql/gdal/ozi_ellips.csv", + "share/postgresql/gdal/pci_datum.txt", + "share/postgresql/gdal/pci_ellips.txt", + "share/postgresql/gdal/stateplane.csv", + "share/postgresql/gdal/template_tiles.mapml", + "share/postgresql/gdal/tms_LINZAntarticaMapTileGrid.json", + "share/postgresql/gdal/tms_MapML_APSTILE.json", + "share/postgresql/gdal/tms_MapML_CBMTILE.json", + "share/postgresql/gdal/tms_NZTM2000.json", "share/postgresql/contrib/postgis-3.6/legacy.sql", "share/postgresql/contrib/postgis-3.6/legacy_gist.sql", "share/postgresql/contrib/postgis-3.6/legacy_minimal.sql", @@ -21,14 +47,12 @@ data_files = [ "share/postgresql/proj/proj.db", ] configure_flags = [ - "--without-raster", "--without-topology", "--without-sfcgal", "--without-address-standardizer", "--without-protobuf", ] excluded_sql_extensions = [ - "postgis_raster", "postgis_topology", "postgis_sfcgal", "postgis_tiger_geocoder", diff --git a/src/extensions/external/postgis/targets/native.toml b/src/extensions/external/postgis/targets/native.toml index d50eba130..d8ca7f3d6 100644 --- a/src/extensions/external/postgis/targets/native.toml +++ b/src/extensions/external/postgis/targets/native.toml @@ -3,9 +3,35 @@ artifact_family = "native-dynamic" build_kind = "autotools" module_files = [ "lib/postgresql/postgis-3.so", - "lib/postgresql/liboliphaunt_postgis_deps.so", + "lib/postgresql/postgis_raster-3.so", ] data_files = [ + "share/postgresql/gdal/GDALLogoBW.svg", + "share/postgresql/gdal/GDALLogoColor.svg", + "share/postgresql/gdal/GDALLogoGS.svg", + "share/postgresql/gdal/LICENSE.TXT", + "share/postgresql/gdal/cubewerx_extra.wkt", + "share/postgresql/gdal/ecw_cs.wkt", + "share/postgresql/gdal/epsg.wkt", + "share/postgresql/gdal/esri_StatePlane_extra.wkt", + "share/postgresql/gdal/gdal_algorithm.schema.json", + "share/postgresql/gdal/gdalicon.png", + "share/postgresql/gdal/gdalinfo_output.schema.json", + "share/postgresql/gdal/gdalmdiminfo_output.schema.json", + "share/postgresql/gdal/gdalvrt.xsd", + "share/postgresql/gdal/leaflet_template.html", + "share/postgresql/gdal/ogr_fields_override.schema.json", + "share/postgresql/gdal/ogrinfo_output.schema.json", + "share/postgresql/gdal/ozi_datum.csv", + "share/postgresql/gdal/ozi_ellips.csv", + "share/postgresql/gdal/pci_datum.txt", + "share/postgresql/gdal/pci_ellips.txt", + "share/postgresql/gdal/stateplane.csv", + "share/postgresql/gdal/template_tiles.mapml", + "share/postgresql/gdal/tms_LINZAntarticaMapTileGrid.json", + "share/postgresql/gdal/tms_MapML_APSTILE.json", + "share/postgresql/gdal/tms_MapML_CBMTILE.json", + "share/postgresql/gdal/tms_NZTM2000.json", "share/postgresql/contrib/postgis-3.6/legacy.sql", "share/postgresql/contrib/postgis-3.6/legacy_gist.sql", "share/postgresql/contrib/postgis-3.6/legacy_minimal.sql", @@ -17,14 +43,12 @@ data_files = [ "share/postgresql/proj/proj.db", ] configure_flags = [ - "--without-raster", "--without-topology", "--without-sfcgal", "--without-address-standardizer", "--without-protobuf", ] excluded_sql_extensions = [ - "postgis_raster", "postgis_topology", "postgis_sfcgal", "postgis_tiger_geocoder", diff --git a/src/extensions/external/postgis/targets/wasix.toml b/src/extensions/external/postgis/targets/wasix.toml index 7e17550ba..78ead3d7d 100644 --- a/src/extensions/external/postgis/targets/wasix.toml +++ b/src/extensions/external/postgis/targets/wasix.toml @@ -4,9 +4,36 @@ build_kind = "autotools" build_script = "src/extensions/external/postgis/tools/build_wasix.sh" module_files = [ "lib/postgresql/postgis-3.so", + "lib/postgresql/postgis_raster-3.so", "lib/postgresql/liboliphaunt_postgis_deps.so", ] data_files = [ + "share/postgresql/gdal/GDALLogoBW.svg", + "share/postgresql/gdal/GDALLogoColor.svg", + "share/postgresql/gdal/GDALLogoGS.svg", + "share/postgresql/gdal/LICENSE.TXT", + "share/postgresql/gdal/cubewerx_extra.wkt", + "share/postgresql/gdal/ecw_cs.wkt", + "share/postgresql/gdal/epsg.wkt", + "share/postgresql/gdal/esri_StatePlane_extra.wkt", + "share/postgresql/gdal/gdal_algorithm.schema.json", + "share/postgresql/gdal/gdalicon.png", + "share/postgresql/gdal/gdalinfo_output.schema.json", + "share/postgresql/gdal/gdalmdiminfo_output.schema.json", + "share/postgresql/gdal/gdalvrt.xsd", + "share/postgresql/gdal/leaflet_template.html", + "share/postgresql/gdal/ogr_fields_override.schema.json", + "share/postgresql/gdal/ogrinfo_output.schema.json", + "share/postgresql/gdal/ozi_datum.csv", + "share/postgresql/gdal/ozi_ellips.csv", + "share/postgresql/gdal/pci_datum.txt", + "share/postgresql/gdal/pci_ellips.txt", + "share/postgresql/gdal/stateplane.csv", + "share/postgresql/gdal/template_tiles.mapml", + "share/postgresql/gdal/tms_LINZAntarticaMapTileGrid.json", + "share/postgresql/gdal/tms_MapML_APSTILE.json", + "share/postgresql/gdal/tms_MapML_CBMTILE.json", + "share/postgresql/gdal/tms_NZTM2000.json", "share/postgresql/contrib/postgis-3.6/legacy.sql", "share/postgresql/contrib/postgis-3.6/legacy_gist.sql", "share/postgresql/contrib/postgis-3.6/legacy_minimal.sql", @@ -18,7 +45,6 @@ data_files = [ "share/postgresql/proj/proj.db", ] configure_flags = [ - "--without-raster", "--without-topology", "--without-sfcgal", "--without-address-standardizer", @@ -29,14 +55,17 @@ configure_flags = [ required_build_files = [ "postgis/postgis-3.so", "postgis/liboliphaunt_postgis_deps.so", + "raster/rt_pg/postgis_raster-3.so", "extensions/postgis/postgis.control", + "extensions/postgis_raster/postgis_raster.control", + "share/gdal/gdalvrt.xsd", "share/proj/proj.db", ] required_build_globs = [ "extensions/postgis/sql/postgis--*.sql", + "extensions/postgis_raster/sql/postgis_raster--*.sql", ] excluded_sql_extensions = [ - "postgis_raster", "postgis_topology", "postgis_sfcgal", "postgis_tiger_geocoder", @@ -50,6 +79,12 @@ runtime_path = "lib/postgresql/liboliphaunt_postgis_deps.so" build_path = "postgis/postgis/liboliphaunt_postgis_deps.so" aot_file = "postgis_deps-llvm-opta.bin.zst" +[[native_support_modules]] +name = "postgis_raster" +runtime_path = "lib/postgresql/postgis_raster-3.so" +build_path = "postgis/raster/rt_pg/postgis_raster-3.so" +aot_file = "postgis_raster-llvm-opta.bin.zst" + [staging] module_source_dir = "postgis/postgis" control_source = "postgis/extensions/postgis/postgis.control" @@ -58,3 +93,11 @@ sql_source_dir = "postgis/extensions/postgis/sql" [[staging.data_dirs]] source = "postgis/share/proj" destination = "share/proj" + +[[staging.data_dirs]] +source = "postgis/share/gdal" +destination = "share/postgresql/gdal" + +[[staging.data_dirs]] +source = "postgis/share/extension-raster" +destination = "share/postgresql/extension" diff --git a/src/extensions/external/postgis/tools/build_wasix.sh b/src/extensions/external/postgis/tools/build_wasix.sh index ef7e073c0..fd596b5e0 100755 --- a/src/extensions/external/postgis/tools/build_wasix.sh +++ b/src/extensions/external/postgis/tools/build_wasix.sh @@ -86,7 +86,7 @@ $dependency_stamp_block profile=$(oliphaunt_wasix_wasix_profile_signature) wasixcc=$wasixcc_version configure=$(oliphaunt_wasix_extension_configure_signature "$REPO_ROOT" postgis) -link=postgis-plus-selected-support-side-module-v2" +link=postgis-raster-plus-selected-support-side-module-v3" if [ -f "$POSTGIS_BUILD_DIR/.oliphaunt-wasix-postgis-build" ] && oliphaunt_wasix_extension_build_outputs_exist "$REPO_ROOT" postgis "$POSTGIS_BUILD_DIR" --quiet && @@ -118,6 +118,31 @@ esac EOF chmod +x "$geos_config" + gdal_config="$POSTGIS_BUILD_DIR/oliphaunt-gdal-config" + cat >"$gdal_config" <&2 + exit 1 + ;; +esac +EOF + chmod +x "$gdal_config" + pkg_config="$POSTGIS_BUILD_DIR/oliphaunt-pkg-config" cat >"$pkg_config" <&2 oliphaunt_wasix_extension_build_outputs_exist "$REPO_ROOT" postgis "$POSTGIS_BUILD_DIR" diff --git a/src/extensions/external/postgis/tools/reproducible-time.sh b/src/extensions/external/postgis/tools/reproducible-time.sh index ab7d409c3..ca1c74a11 100755 --- a/src/extensions/external/postgis/tools/reproducible-time.sh +++ b/src/extensions/external/postgis/tools/reproducible-time.sh @@ -5,6 +5,31 @@ oliphaunt_postgis_time_fail() { return 1 } +oliphaunt_postgis_resolve_executable_path() { + local path="${1:-}" + local directory basename + [ -n "$path" ] || return 1 + case "$path" in + */*) ;; + *) path="$(command -v "$path" || true)" ;; + esac + [ -n "$path" ] || return 1 + directory="$(dirname "$path")" + basename="$(basename "$path")" + ( + unset CDPATH + cd -- "$directory" 2>/dev/null || exit 1 + printf '%s/%s\n' "$(pwd -P)" "$basename" + ) +} + +oliphaunt_postgis_same_executable() { + local left right + left="$(oliphaunt_postgis_resolve_executable_path "${1:-}")" || return 1 + right="$(oliphaunt_postgis_resolve_executable_path "${2:-}")" || return 1 + [ "$left" = "$right" ] +} + oliphaunt_postgis_source_date_epoch() { local repo_root="${1:?missing repository root}" local manifest="$repo_root/src/extensions/external/postgis/source.toml" @@ -34,14 +59,15 @@ oliphaunt_postgis_enable_reproducible_time() { oliphaunt_postgis_time_fail "portable date shim is not executable: $shim" || return 1 SOURCE_DATE_EPOCH="$(oliphaunt_postgis_source_date_epoch "$repo_root")" || return 1 resolved_date="$(command -v date || true)" - if [ "$resolved_date" = "$shim" ]; then + if oliphaunt_postgis_same_executable "$resolved_date" "$shim"; then real_date="${OLIPHAUNT_POSTGIS_REAL_DATE:-}" else # Never trust an inherited override on first enable. Capture the date command # selected by the producer's PATH before installing our scoped shim. real_date="$resolved_date" fi - [ -n "$real_date" ] && [ -x "$real_date" ] && [ "$real_date" != "$shim" ] || + [ -n "$real_date" ] && [ -x "$real_date" ] && + ! oliphaunt_postgis_same_executable "$real_date" "$shim" || oliphaunt_postgis_time_fail "could not capture the host date command before installing the shim" || return 1 OLIPHAUNT_POSTGIS_REAL_DATE="$real_date" case ":$PATH:" in diff --git a/src/extensions/external/postgis/tools/windows/build-sql.sh b/src/extensions/external/postgis/tools/windows/build-sql.sh index 873e9569b..ed936b30a 100644 --- a/src/extensions/external/postgis/tools/windows/build-sql.sh +++ b/src/extensions/external/postgis/tools/windows/build-sql.sh @@ -4,6 +4,7 @@ windows_postgis_build_sql() { local directory="$build_dir/contrib/oliphaunt_external/postgis" local pg="$directory/postgis" sql="$directory/extensions/postgis/sql" raster="$directory/raster/rt_pg" + local raster_sql="$directory/extensions/postgis_raster/sql" local version pg_version version="$(windows_extension_generate postgis-version)" pg_version="$(windows_extension_generate postgres-major)" @@ -14,9 +15,11 @@ windows_postgis_build_sql() { perl "$directory/utils/create_spatial_ref_sys_config_dump.pl" "$directory/spatial_ref_sys.sql" >"$sql/spatial_ref_sys_config_dump.sql" perl "$directory/utils/create_upgrade.pl" "$sql/postgis_for_extension.sql" >"$sql/postgis_upgrade_for_extension.sql.in" perl "$directory/utils/create_uninstall.pl" "$raster/rtpostgis.sql" "$pg_version" >"$raster/uninstall_rtpostgis.sql" + perl "$directory/utils/create_upgrade.pl" "$raster_sql/rtpostgis.sql" >"$raster_sql/rtpostgis_upgrade.sql.in" windows_extension_generate postgis-sql-upgrade perl "$directory/utils/create_extension_unpackage.pl" postgis <"$sql/raster_drop_all.sql" >"$sql/raster_unpackage_body.sql" windows_extension_generate postgis-sql-install perl "$directory/utils/create_unpackaged.pl" postgis <"$sql/postgis--$version.sql" >"$sql/postgis--unpackaged--$version.sql" + perl "$directory/utils/create_unpackaged.pl" postgis_raster <"$raster_sql/postgis_raster--$version.sql" >"$raster_sql/postgis_raster--unpackaged--$version.sql" windows_extension_generate postgis-sql-unpackaged } diff --git a/src/extensions/external/postgis/tools/windows/meson.build.in b/src/extensions/external/postgis/tools/windows/meson.build.in index f4ca28225..4c1b37749 100644 --- a/src/extensions/external/postgis/tools/windows/meson.build.in +++ b/src/extensions/external/postgis/tools/windows/meson.build.in @@ -1,70 +1,4 @@ -postgis = shared_module( - 'postgis-3', - files( - 'postgis/postgis_module.c', - 'postgis/lwgeom_accum.c', - 'postgis/lwgeom_union.c', - 'postgis/lwgeom_spheroid.c', - 'postgis/lwgeom_ogc.c', - 'postgis/lwgeom_functions_analytic.c', - 'postgis/lwgeom_functions_basic.c', - 'postgis/lwgeom_inout.c', - 'postgis/lwgeom_btree.c', - 'postgis/lwgeom_box.c', - 'postgis/lwgeom_box3d.c', - 'postgis/lwgeom_geos.c', - 'postgis/lwgeom_geos_predicates.c', - 'postgis/lwgeom_geos_prepared.c', - 'postgis/lwgeom_geos_clean.c', - 'postgis/lwgeom_geos_relatematch.c', - 'postgis/lwgeom_generate_grid.c', - 'postgis/lwgeom_export.c', - 'postgis/lwgeom_in_gml.c', - 'postgis/lwgeom_in_kml.c', - 'postgis/lwgeom_in_marc21.c', - 'postgis/lwgeom_out_marc21.c', - 'postgis/lwgeom_in_geohash.c', - 'postgis/lwgeom_in_geojson.c', - 'postgis/lwgeom_in_encoded_polyline.c', - 'postgis/lwgeom_triggers.c', - 'postgis/lwgeom_dump.c', - 'postgis/lwgeom_dumppoints.c', - 'postgis/lwgeom_functions_lrs.c', - 'postgis/lwgeom_functions_temporal.c', - 'postgis/lwgeom_rectree.c', - 'postgis/lwgeom_itree.c', - 'postgis/lwgeom_sqlmm.c', - 'postgis/lwgeom_transform.c', - 'postgis/lwgeom_window.c', - 'postgis/gserialized_typmod.c', - 'postgis/gserialized_gist_2d.c', - 'postgis/gserialized_gist_nd.c', - 'postgis/gserialized_supportfn.c', - 'postgis/gserialized_spgist_2d.c', - 'postgis/gserialized_spgist_3d.c', - 'postgis/gserialized_spgist_nd.c', - 'postgis/brin_2d.c', - 'postgis/brin_nd.c', - 'postgis/brin_common.c', - 'postgis/gserialized_estimate.c', - 'postgis/geography_inout.c', - 'postgis/geography_btree.c', - 'postgis/geography_centroid.c', - 'postgis/geography_measurement.c', - 'postgis/geography_measurement_trees.c', - 'postgis/geometry_inout.c', - 'postgis/postgis_libprotobuf.c', - 'postgis/mvt.c', - 'postgis/lwgeom_out_mvt.c', - 'postgis/geobuf.c', - 'postgis/lwgeom_out_geobuf.c', - 'postgis/lwgeom_out_geojson.c', - 'postgis/flatgeobuf.c', - 'postgis/lwgeom_in_flatgeobuf.c', - 'postgis/lwgeom_out_flatgeobuf.c', - 'postgis/lwgeom_remove_irrelevant_points_for_view.c', - 'postgis/lwgeom_remove_small_parts.c', - 'postgis/postgis_legacy.c', +postgis_common_sources = files( 'libpgcommon/gserialized_gist.c', 'libpgcommon/lwgeom_transform.c', 'libpgcommon/lwgeom_cache.c', @@ -152,19 +86,126 @@ postgis = shared_module( 'liblwgeom/lwgeom_remove_irrelevant_points_for_view.c', 'liblwgeom/lwspheroid.c', 'deps/ryu/d2s.c', - ), - c_pch: pch_postgres_h, - kwargs: contrib_mod_args + { - 'c_args': [ +) +postgis_module_args = contrib_mod_args + { + 'c_args': [ @C_ARGS@ - ], - 'link_args': [ + ], + 'link_args': [ @LINK_ARGS@ - ], - }, + ], +} + +postgis = shared_module( + 'postgis-3', + files( + 'postgis/postgis_module.c', + 'postgis/lwgeom_accum.c', + 'postgis/lwgeom_union.c', + 'postgis/lwgeom_spheroid.c', + 'postgis/lwgeom_ogc.c', + 'postgis/lwgeom_functions_analytic.c', + 'postgis/lwgeom_functions_basic.c', + 'postgis/lwgeom_inout.c', + 'postgis/lwgeom_btree.c', + 'postgis/lwgeom_box.c', + 'postgis/lwgeom_box3d.c', + 'postgis/lwgeom_geos.c', + 'postgis/lwgeom_geos_predicates.c', + 'postgis/lwgeom_geos_prepared.c', + 'postgis/lwgeom_geos_clean.c', + 'postgis/lwgeom_geos_relatematch.c', + 'postgis/lwgeom_generate_grid.c', + 'postgis/lwgeom_export.c', + 'postgis/lwgeom_in_gml.c', + 'postgis/lwgeom_in_kml.c', + 'postgis/lwgeom_in_marc21.c', + 'postgis/lwgeom_out_marc21.c', + 'postgis/lwgeom_in_geohash.c', + 'postgis/lwgeom_in_geojson.c', + 'postgis/lwgeom_in_encoded_polyline.c', + 'postgis/lwgeom_triggers.c', + 'postgis/lwgeom_dump.c', + 'postgis/lwgeom_dumppoints.c', + 'postgis/lwgeom_functions_lrs.c', + 'postgis/lwgeom_functions_temporal.c', + 'postgis/lwgeom_rectree.c', + 'postgis/lwgeom_itree.c', + 'postgis/lwgeom_sqlmm.c', + 'postgis/lwgeom_transform.c', + 'postgis/lwgeom_window.c', + 'postgis/gserialized_typmod.c', + 'postgis/gserialized_gist_2d.c', + 'postgis/gserialized_gist_nd.c', + 'postgis/gserialized_supportfn.c', + 'postgis/gserialized_spgist_2d.c', + 'postgis/gserialized_spgist_3d.c', + 'postgis/gserialized_spgist_nd.c', + 'postgis/brin_2d.c', + 'postgis/brin_nd.c', + 'postgis/brin_common.c', + 'postgis/gserialized_estimate.c', + 'postgis/geography_inout.c', + 'postgis/geography_btree.c', + 'postgis/geography_centroid.c', + 'postgis/geography_measurement.c', + 'postgis/geography_measurement_trees.c', + 'postgis/geometry_inout.c', + 'postgis/postgis_libprotobuf.c', + 'postgis/mvt.c', + 'postgis/lwgeom_out_mvt.c', + 'postgis/geobuf.c', + 'postgis/lwgeom_out_geobuf.c', + 'postgis/lwgeom_out_geojson.c', + 'postgis/flatgeobuf.c', + 'postgis/lwgeom_in_flatgeobuf.c', + 'postgis/lwgeom_out_flatgeobuf.c', + 'postgis/lwgeom_remove_irrelevant_points_for_view.c', + 'postgis/lwgeom_remove_small_parts.c', + 'postgis/postgis_legacy.c', + ) + postgis_common_sources, + c_pch: pch_postgres_h, + kwargs: postgis_module_args, ) contrib_targets += postgis +postgis_raster = shared_module( + 'postgis_raster-3', + files( + 'raster/rt_pg/rtpostgis.c', + 'raster/rt_pg/rtpg_internal.c', + 'raster/rt_pg/rtpg_legacy.c', + 'raster/rt_pg/rtpg_spatial_relationship.c', + 'raster/rt_pg/rtpg_mapalgebra.c', + 'raster/rt_pg/rtpg_utility.c', + 'raster/rt_pg/rtpg_inout.c', + 'raster/rt_pg/rtpg_wkb.c', + 'raster/rt_pg/rtpg_geometry.c', + 'raster/rt_pg/rtpg_raster_properties.c', + 'raster/rt_pg/rtpg_band_properties.c', + 'raster/rt_pg/rtpg_pixel.c', + 'raster/rt_pg/rtpg_create.c', + 'raster/rt_pg/rtpg_gdal.c', + 'raster/rt_pg/rtpg_statistics.c', + 'raster/rt_core/rt_util.c', + 'raster/rt_core/rt_spatial_relationship.c', + 'raster/rt_core/rt_mapalgebra.c', + 'raster/rt_core/rt_geometry.c', + 'raster/rt_core/rt_statistics.c', + 'raster/rt_core/rt_pixel.c', + 'raster/rt_core/rt_warp.c', + 'raster/rt_core/rt_gdal.c', + 'raster/rt_core/rt_band.c', + 'raster/rt_core/rt_raster.c', + 'raster/rt_core/rt_serialize.c', + 'raster/rt_core/rt_wkb.c', + 'raster/rt_core/rt_context.c', + ) + postgis_common_sources, + c_pch: pch_postgres_h, + kwargs: postgis_module_args, +) +contrib_targets += postgis_raster + install_data( @EXTENSION_DATA@, kwargs: contrib_data_args, @@ -179,3 +220,9 @@ install_data( 'share/proj/proj.db', install_dir: dir_data / 'proj', ) + +install_subdir( + 'share/gdal', + install_dir: dir_data / 'gdal', + strip_directory: true, +) diff --git a/src/extensions/external/postgis/tools/windows/postgis_config.h.in b/src/extensions/external/postgis/tools/windows/postgis_config.h.in index 9686dff80..5042443e1 100644 --- a/src/extensions/external/postgis/tools/windows/postgis_config.h.in +++ b/src/extensions/external/postgis/tools/windows/postgis_config.h.in @@ -29,7 +29,7 @@ #define PGSQL_LOCALEDIR "@LOCALE_DIR@" #define POSTGIS_BUILD_DATE "@BUILD_DATE@" /* #undef POSTGIS_SFCGAL_VERSION */ -/* #undef POSTGIS_GDAL_VERSION */ +#define POSTGIS_GDAL_VERSION @GDAL_VERSION@ #define POSTGIS_GEOS_VERSION @GEOS_VERSION@ #define POSTGIS_LIBXML2_VERSION "@LIBXML_VERSION@" #define POSTGIS_LIB_VERSION "@VERSION@" diff --git a/src/extensions/external/postgis/upstream-license-data.json b/src/extensions/external/postgis/upstream-license-data.json index 54da3c16b..c2d239d59 100644 --- a/src/extensions/external/postgis/upstream-license-data.json +++ b/src/extensions/external/postgis/upstream-license-data.json @@ -1,21 +1,29 @@ { "schema": "oliphaunt-extension-upstream-license-data-v1", "sources": [ + { + "id": "gdal", + "manifest": "src/extensions/external/postgis/dependencies/gdal/source.toml", + "kind": "git", + "url": "https://github.com/OSGeo/gdal.git", + "branch": "v3.13.3", + "commit": "c8b4c45fca87d3e6fbf80e7a7898b8a661ad0edc" + }, { "id": "geos", "manifest": "src/extensions/external/postgis/dependencies/geos/source.toml", "kind": "git", "url": "https://github.com/libgeos/geos.git", - "branch": "3.14.1", - "commit": "c389f532d25fe6228861d9b19339f9cb57ca4bdb" + "branch": "3.15.0", + "commit": "d0228513abb0c29c185443cf2bfb06c9281024b5" }, { "id": "json-c", "manifest": "src/extensions/external/postgis/dependencies/json-c/source.toml", "kind": "git", "url": "https://github.com/json-c/json-c.git", - "branch": "json-c-0.18-20240915", - "commit": "41a55cfcedb54d9c1874f2f0eb07b504091d7e37" + "branch": "json-c-0.19-20260627", + "commit": "aa716cd8d663c976b99b0f30f102ee1d8ef63146" }, { "id": "libiconv", @@ -30,44 +38,53 @@ "manifest": "src/extensions/external/postgis/dependencies/libxml2/source.toml", "kind": "git", "url": "https://gitlab.gnome.org/GNOME/libxml2.git", - "branch": "v2.14.6", - "commit": "d23960a130c5bb82779c9405fbbf85e65fb3c57c" + "branch": "v2.15.4", + "commit": "96498992efa48d52b0e8b83058bd88dbdaf153c1" }, { "id": "postgis", "manifest": "src/extensions/external/postgis/source.toml", "kind": "git", "url": "https://github.com/postgis/postgis.git", - "branch": "3.6.3", - "commit": "3d12666588a84b23a3147618eaa9b40b0fe5e796" + "branch": "3.6.4", + "commit": "94d984bd083635c1d253db0f87cf80b32548e406" }, { "id": "proj", "manifest": "src/extensions/external/postgis/dependencies/proj/source.toml", "kind": "git", "url": "https://github.com/OSGeo/PROJ.git", - "branch": "9.8.1", - "commit": "f08fa86c478c4bbbf003b1ec751dd84aa6eca486" + "branch": "9.9.0", + "commit": "1df2be61c0e4732aa1326126113776b291ae7657" }, { "id": "sqlite", "manifest": "src/extensions/external/postgis/dependencies/sqlite/source.toml", "kind": "git", "url": "https://github.com/sqlite/sqlite.git", - "branch": "version-3.53.1", - "commit": "ccd445d76a9362c63add000354fac84ba9022176" + "branch": "version-3.54.1", + "commit": "57520227c95396ce39ba6ecaac8d475b78dd3964" } ], "extension": { "sql_name": "postgis", "files": [ + { + "source": "gdal", + "path": "LICENSE.TXT", + "destination": "share/licenses/gdal/LICENSE.TXT", + "role": "license", + "spdx": "MIT", + "license_url": "https://github.com/OSGeo/gdal/blob/c8b4c45fca87d3e6fbf80e7a7898b8a661ad0edc/LICENSE.TXT", + "sha256": "1dae3468e81d00da56e2936f74d33b8b3ad09d726437f19ce209a5dabea41f77" + }, { "source": "geos", "path": "COPYING", "destination": "share/licenses/geos/COPYING", "role": "license", "spdx": "LGPL-2.1-or-later", - "license_url": "https://github.com/libgeos/geos/blob/c389f532d25fe6228861d9b19339f9cb57ca4bdb/COPYING", + "license_url": "https://github.com/libgeos/geos/blob/d0228513abb0c29c185443cf2bfb06c9281024b5/COPYING", "sha256": "dc626520dcd53a22f727af3ee42c770e56c97a64fe3adb063799d8ab032fe551" }, { @@ -76,7 +93,7 @@ "destination": "share/licenses/geos/src/deps/ryu/LICENSE", "role": "notice", "spdx": "Apache-2.0", - "license_url": "https://github.com/libgeos/geos/blob/c389f532d25fe6228861d9b19339f9cb57ca4bdb/src/deps/ryu/LICENSE", + "license_url": "https://github.com/libgeos/geos/blob/d0228513abb0c29c185443cf2bfb06c9281024b5/src/deps/ryu/LICENSE", "sha256": "087edee322a9d2e527453ddc6731ccb4f015a36e55864b30715934f32a4879c1" }, { @@ -85,7 +102,7 @@ "destination": "share/licenses/geos/src/deps/ryu/LICENSE-Apache2", "role": "license", "spdx": "Apache-2.0", - "license_url": "https://github.com/libgeos/geos/blob/c389f532d25fe6228861d9b19339f9cb57ca4bdb/src/deps/ryu/LICENSE-Apache2", + "license_url": "https://github.com/libgeos/geos/blob/d0228513abb0c29c185443cf2bfb06c9281024b5/src/deps/ryu/LICENSE-Apache2", "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" }, { @@ -94,7 +111,7 @@ "destination": "share/licenses/geos/src/deps/ryu/LICENSE-Boost", "role": "notice", "spdx": "Apache-2.0", - "license_url": "https://github.com/libgeos/geos/blob/c389f532d25fe6228861d9b19339f9cb57ca4bdb/src/deps/ryu/LICENSE-Boost", + "license_url": "https://github.com/libgeos/geos/blob/d0228513abb0c29c185443cf2bfb06c9281024b5/src/deps/ryu/LICENSE-Boost", "sha256": "c9bff75738922193e67fa726fa225535870d2aa1059f91452c411736284ad566" }, { @@ -103,7 +120,7 @@ "destination": "share/licenses/json-c/COPYING", "role": "license", "spdx": "MIT", - "license_url": "https://github.com/json-c/json-c/blob/41a55cfcedb54d9c1874f2f0eb07b504091d7e37/COPYING", + "license_url": "https://github.com/json-c/json-c/blob/aa716cd8d663c976b99b0f30f102ee1d8ef63146/COPYING", "sha256": "74c1e6ca5eba76b54d0ad00d4815c8315c1b3bc45ff99de61d103dc92486284c" }, { @@ -130,7 +147,7 @@ "destination": "share/licenses/libxml2/Copyright", "role": "license", "spdx": "MIT", - "license_url": "https://gitlab.gnome.org/GNOME/libxml2/-/blob/d23960a130c5bb82779c9405fbbf85e65fb3c57c/Copyright", + "license_url": "https://gitlab.gnome.org/GNOME/libxml2/-/blob/96498992efa48d52b0e8b83058bd88dbdaf153c1/Copyright", "sha256": "5d4873884a890122a4b9b20ad56ac6f7da1d796a5bfcf04a427970ac96217626" }, { @@ -139,7 +156,7 @@ "destination": "share/licenses/postgis/COPYING", "role": "license", "spdx": "GPL-2.0-or-later", - "license_url": "https://github.com/postgis/postgis/blob/3d12666588a84b23a3147618eaa9b40b0fe5e796/COPYING", + "license_url": "https://github.com/postgis/postgis/blob/94d984bd083635c1d253db0f87cf80b32548e406/COPYING", "sha256": "8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643" }, { @@ -148,7 +165,7 @@ "destination": "share/licenses/postgis/LICENSE.TXT", "role": "notice", "spdx": "GPL-2.0-or-later", - "license_url": "https://github.com/postgis/postgis/blob/3d12666588a84b23a3147618eaa9b40b0fe5e796/LICENSE.TXT", + "license_url": "https://github.com/postgis/postgis/blob/94d984bd083635c1d253db0f87cf80b32548e406/LICENSE.TXT", "sha256": "81749eea887e78402c9b85896cd629b155bc29256927f818f01001f038146391" }, { @@ -157,7 +174,7 @@ "destination": "share/licenses/postgis/deps/flatgeobuf/flatbuffers/LICENSE", "role": "license", "spdx": "Apache-2.0", - "license_url": "https://github.com/postgis/postgis/blob/3d12666588a84b23a3147618eaa9b40b0fe5e796/deps/flatgeobuf/include/flatbuffers/LICENSE", + "license_url": "https://github.com/postgis/postgis/blob/94d984bd083635c1d253db0f87cf80b32548e406/deps/flatgeobuf/include/flatbuffers/LICENSE", "sha256": "e55b7f3aaf3a9db897cc6a63fd6a79ae8fbbcaa32ffbe382ae77ce1f0db33f99" }, { @@ -166,7 +183,7 @@ "destination": "share/licenses/postgis/deps/ryu/LICENSE", "role": "notice", "spdx": "Apache-2.0", - "license_url": "https://github.com/postgis/postgis/blob/3d12666588a84b23a3147618eaa9b40b0fe5e796/deps/ryu/LICENSE", + "license_url": "https://github.com/postgis/postgis/blob/94d984bd083635c1d253db0f87cf80b32548e406/deps/ryu/LICENSE", "sha256": "087edee322a9d2e527453ddc6731ccb4f015a36e55864b30715934f32a4879c1" }, { @@ -175,7 +192,7 @@ "destination": "share/licenses/postgis/deps/ryu/LICENSE-Apache2", "role": "license", "spdx": "Apache-2.0", - "license_url": "https://github.com/postgis/postgis/blob/3d12666588a84b23a3147618eaa9b40b0fe5e796/deps/ryu/LICENSE-Apache2", + "license_url": "https://github.com/postgis/postgis/blob/94d984bd083635c1d253db0f87cf80b32548e406/deps/ryu/LICENSE-Apache2", "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" }, { @@ -184,7 +201,7 @@ "destination": "share/licenses/postgis/deps/ryu/LICENSE-Boost", "role": "notice", "spdx": "Apache-2.0", - "license_url": "https://github.com/postgis/postgis/blob/3d12666588a84b23a3147618eaa9b40b0fe5e796/deps/ryu/LICENSE-Boost", + "license_url": "https://github.com/postgis/postgis/blob/94d984bd083635c1d253db0f87cf80b32548e406/deps/ryu/LICENSE-Boost", "sha256": "c9bff75738922193e67fa726fa225535870d2aa1059f91452c411736284ad566" }, { @@ -193,7 +210,7 @@ "destination": "share/licenses/proj/COPYING", "role": "license", "spdx": "MIT", - "license_url": "https://github.com/OSGeo/PROJ/blob/f08fa86c478c4bbbf003b1ec751dd84aa6eca486/COPYING", + "license_url": "https://github.com/OSGeo/PROJ/blob/1df2be61c0e4732aa1326126113776b291ae7657/COPYING", "sha256": "6a8f30793e877d32e3f88b972f0970a051a3b5a26cd057d3993cb51e21c43319" }, { @@ -202,13 +219,14 @@ "destination": "share/licenses/sqlite/LICENSE.md", "role": "license", "spdx": "blessing", - "license_url": "https://github.com/sqlite/sqlite/blob/ccd445d76a9362c63add000354fac84ba9022176/LICENSE.md", + "license_url": "https://github.com/sqlite/sqlite/blob/57520227c95396ce39ba6ecaac8d475b78dd3964/LICENSE.md", "sha256": "ee6af51062b30d532991face5164136ae6f84e265ecf8abe89dc69dac45ca1e7" } ] }, "blobs": { "087edee322a9d2e527453ddc6731ccb4f015a36e55864b30715934f32a4879c1": "QWxsIHRoZSBDIGNvZGUgaXMgY29weXJpZ2h0ZWQgYnkgVWxmIEFkYW1zIGFuZCBjb250cmlidXRvcnMsIGFuZCBtYXkgYmUgdXNlZCBmcmVlbHkgaW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBBcGFjaGUgMi4wIGxpY2Vuc2UuIEFsdGVybmF0aXZlbHksIGl0IG1heSBiZSB1c2VkIGZyZWVseSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIEJvb3N0IDEuMCBsaWNlbnNlLg==", + "1dae3468e81d00da56e2936f74d33b8b3ad09d726437f19ce209a5dabea41f77": "CkdEQUwvT0dSIExpY2Vuc2luZwo9PT09PT09PT09PT09PT09PT0KClRoaXMgZmlsZSBhdHRlbXB0cyB0byBpbmNsdWRlIGFsbCBsaWNlbnNlcyB0aGF0IGFwcGx5IHdpdGhpbiB0aGUgR0RBTC9PR1IKc291cmNlIHRyZWUsIGluIHBhcnRpY3VsYXIgYW55IHRoYXQgYXJlIHN1cHBvc2VkIHRvIGJlIGV4cG9zZWQgdG8gdGhlIGVuZCB1c2VyCmZvciBjcmVkaXQgcmVxdWlyZW1lbnRzIGZvciBpbnN0YW5jZS4gIFRoZSBjb250ZW50cyBvZiB0aGlzIGZpbGUgY2FuIGJlCmRpc3BsYXllZCBmcm9tIEdEQUwgY29tbWFuZGxpbmUgdXRpbGl0aWVzIHVzaW5nIHRoZSAtLWxpY2Vuc2UgY29tbWFuZGxpbmUKc3dpdGNoLgoKCkdEQUwvT0dSIEdlbmVyYWwKLS0tLS0tLS0tLS0tLS0tLQoKSW4gZ2VuZXJhbCBHREFML09HUiBpcyBsaWNlbnNlZCB1bmRlciBhbiBNSVQgc3R5bGUgbGljZW5zZSB3aXRoIHRoZQpmb2xsb3dpbmcgdGVybXM6CgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYQpjb3B5IG9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLAp0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uCnRoZSByaWdodHMgdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLAphbmQvb3Igc2VsbCBjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUKU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkCmluIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MKT1IgSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMClRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HCkZST00sIE9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIKREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLgoKCnBvcnQvY3BsX2Zsb2F0LmNwcAotLS0tLS0tLS0tLS0tLS0tLS0KCkNvcHlyaWdodCAoYykgMjAwMiwgSW5kdXN0cmlhbCBMaWdodCAmIE1hZ2ljLCBhIGRpdmlzaW9uIG9mIEx1Y2FzCkRpZ2l0YWwgTHRkLiBMTEMKCkFsbCByaWdodHMgcmVzZXJ2ZWQuCgpSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZQptZXQ6CiogICAgICAgUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQKbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLgoqICAgICAgIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUKY29weXJpZ2h0IG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lcgppbiB0aGUgZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlCmRpc3RyaWJ1dGlvbi4KKiAgICAgICBOZWl0aGVyIHRoZSBuYW1lIG9mIEluZHVzdHJpYWwgTGlnaHQgJiBNYWdpYyBub3IgdGhlIG5hbWVzIG9mCml0cyBjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQKZnJvbSB0aGlzIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwoiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVApMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IKQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFQKT1dORVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsClNQRUNJQUwsIEVYRU1QTEFSWSwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIChJTkNMVURJTkcsIEJVVCBOT1QKTElNSVRFRCBUTywgUFJPQ1VSRU1FTlQgT0YgU1VCU1RJVFVURSBHT09EUyBPUiBTRVJWSUNFUzsgTE9TUyBPRiBVU0UsCkRBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUiBDQVVTRUQgQU5EIE9OIEFOWQpUSEVPUlkgT0YgTElBQklMSVRZLCBXSEVUSEVSIElOIENPTlRSQUNULCBTVFJJQ1QgTElBQklMSVRZLCBPUiBUT1JUCihJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBPVEhFUldJU0UpIEFSSVNJTkcgSU4gQU5ZIFdBWSBPVVQgT0YgVEhFIFVTRQpPRiBUSElTIFNPRlRXQVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLgoKCmZybXRzL2hkZjQvaGRmLWVvcy8qCi0tLS0tLS0tLS0tLS0tLS0tLS0tCgogQ29weXJpZ2h0IChDKSAxOTk2IEh1Z2hlcyBhbmQgQXBwbGllZCBSZXNlYXJjaCBDb3Jwb3JhdGlvbgoKIFBlcm1pc3Npb24gdG8gdXNlLCBtb2RpZnksIGFuZCBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgYW5kIGl0cyBkb2N1bWVudGF0aW9uCiBmb3IgYW55IHB1cnBvc2Ugd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQsIHByb3ZpZGVkIHRoYXQgdGhlIGFib3ZlCiBjb3B5cmlnaHQgbm90aWNlIGFwcGVhciBpbiBhbGwgY29waWVzIGFuZCB0aGF0IGJvdGggdGhhdCBjb3B5cmlnaHQgbm90aWNlIGFuZAogdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBhcHBlYXIgaW4gc3VwcG9ydGluZyBkb2N1bWVudGF0aW9uLgoKCmZybXRzL3BjcmFzdGVyL2xpYmNzZgotLS0tLS0tLS0tLS0tLS0tLS0tLS0KCkNvcHlyaWdodCAoYykgMTk5Ny0yMDAzLCBVdHJlY2h0IFVuaXZlcnNpdHkKQWxsIHJpZ2h0cyByZXNlcnZlZC4KClJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dAptb2RpZmljYXRpb24sIGFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnMKYXJlIG1ldDoKCiogUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQKICBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCgoqIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUKICBjb3B5cmlnaHQgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZwogIGRpc2NsYWltZXIgaW4gdGhlIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZAogIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KCiogTmVpdGhlciB0aGUgbmFtZSBvZiBVdHJlY2h0IFVuaXZlcnNpdHkgbm9yIHRoZSBuYW1lcyBvZiBpdHMgY29udHJpYnV0b3JzCiAgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQgZnJvbSB0aGlzIHNvZnR3YXJlCiAgd2l0aG91dCBzcGVjaWZpYyBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uCgpUSElTIFNPRlRXQVJFIElTIFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTCiJBUyBJUyIgQU5EIEFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFOVElFUywgSU5DTFVESU5HLCBCVVQgTk9UCkxJTUlURUQgVE8sIFRIRSBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZIEFORCBGSVRORVNTIEZPUgpBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xBSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVApPV05FUiBPUiBDT05UUklCVVRPUlMgQkUgTElBQkxFIEZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURFTlRBTCwKU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgKElOQ0xVRElORywgQlVUIE5PVApMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJQ0VTOyBMT1NTIE9GIFVTRSwKREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5FU1MgSU5URVJSVVBUSU9OKSBIT1dFVkVSIENBVVNFRCBBTkQgT04gQU5ZClRIRU9SWSBPRiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsIFNUUklDVCBMSUFCSUxJVFksIE9SIFRPUlQKKElOQ0xVRElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBUSEUgVVNFCk9GIFRISVMgU09GVFdBUkUsIEVWRU4gSUYgQURWSVNFRCBPRiBUSEUgUE9TU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuCgpmcm10cy9ncmliL2RlZ3JpYi8qCi0tLS0tLS0tLS0tLS0tLS0tLS0KClRoZSBkZWdyaWIgYW5kIGcyY2xpYiBzb3VyY2UgY29kZSBhcmUgbW9kaWZpZWQgdmVyc2lvbnMgb2YgY29kZSBwcm9kdWNlZApieSBOT0FBIE5XUyBhbmQgYXJlIGluIHRoZSBwdWJsaWMgZG9tYWluIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZwpyZXN0cmljdGlvbnM6CgpodHRwOi8vd3d3LndlYXRoZXIuZ292L2ltL3NvZnRhLmh0bQoKRElTQ0xBSU1FUiBUaGUgVW5pdGVkIFN0YXRlcyBHb3Zlcm5tZW50IG1ha2VzIG5vIHdhcnJhbnR5LCBleHByZXNzZWQgb3IKaW1wbGllZCwgYXMgdG8gdGhlIHVzZWZ1bG5lc3Mgb2YgdGhlIHNvZnR3YXJlIGFuZCBkb2N1bWVudGF0aW9uIGZvciBhbnkKcHVycG9zZS4gVGhlIFUuUy4gR292ZXJubWVudCwgaXRzIGluc3RydW1lbnRhbGl0aWVzLCBvZmZpY2VycywgZW1wbG95ZWVzLAphbmQgYWdlbnRzIGFzc3VtZXMgbm8gcmVzcG9uc2liaWxpdHkgKDEpIGZvciB0aGUgdXNlIG9mIHRoZSBzb2Z0d2FyZSBhbmQKZG9jdW1lbnRhdGlvbiBsaXN0ZWQgYmVsb3csIG9yICgyKSB0byBwcm92aWRlIHRlY2huaWNhbCBzdXBwb3J0IHRvIHVzZXJzLgoKaHR0cDovL3d3dy53ZWF0aGVyLmdvdi9kaXNjbGFpbWVyLnBocAoKIFRoZSBpbmZvcm1hdGlvbiBvbiBnb3Zlcm5tZW50IHNlcnZlcnMgYXJlIGluIHRoZSBwdWJsaWMgZG9tYWluLCB1bmxlc3MKc3BlY2lmaWNhbGx5IGFubm90YXRlZCBvdGhlcndpc2UsIGFuZCBtYXkgYmUgdXNlZCBmcmVlbHkgYnkgdGhlIHB1YmxpYyBzbwpsb25nIGFzIHlvdSBkbyBub3QgMSkgY2xhaW0gaXQgaXMgeW91ciBvd24gKGUuZy4gYnkgY2xhaW1pbmcgY29weXJpZ2h0IGZvcgpOV1MgaW5mb3JtYXRpb24gLS0gc2VlIGJlbG93KSwgMikgdXNlIGl0IGluIGEgbWFubmVyIHRoYXQgaW1wbGllcyBhbgplbmRvcnNlbWVudCBvciBhZmZpbGlhdGlvbiB3aXRoIE5PQUEvTldTLCBvciAzKSBtb2RpZnkgaXQgaW4gY29udGVudCBhbmQKdGhlbiBwcmVzZW50IGl0IGFzIG9mZmljaWFsIGdvdmVybm1lbnQgbWF0ZXJpYWwuIFlvdSBhbHNvIGNhbm5vdCBwcmVzZW50CmluZm9ybWF0aW9uIG9mIHlvdXIgb3duIGluIGEgd2F5IHRoYXQgbWFrZXMgaXQgYXBwZWFyIHRvIGJlIG9mZmljaWFsCmdvdmVybm1lbnQgaW5mb3JtYXRpb24uLgoKIFRoZSB1c2VyIGFzc3VtZXMgdGhlIGVudGlyZSByaXNrIHJlbGF0ZWQgdG8gaXRzIHVzZSBvZiB0aGlzIGRhdGEuIE5XUyBpcwpwcm92aWRpbmcgdGhpcyBkYXRhICJhcyBpcywiIGFuZCBOV1MgZGlzY2xhaW1zIGFueSBhbmQgYWxsIHdhcnJhbnRpZXMsCndoZXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLCBpbmNsdWRpbmcgKHdpdGhvdXQgbGltaXRhdGlvbikgYW55IGltcGxpZWQKd2FycmFudGllcyBvZiBtZXJjaGFudGFiaWxpdHkgb3IgZml0bmVzcyBmb3IgYSBwYXJ0aWN1bGFyIHB1cnBvc2UuIEluIG5vCmV2ZW50IHdpbGwgTldTIGJlIGxpYWJsZSB0byB5b3Ugb3IgdG8gYW55IHRoaXJkIHBhcnR5IGZvciBhbnkgZGlyZWN0LAppbmRpcmVjdCwgaW5jaWRlbnRhbCwgY29uc2VxdWVudGlhbCwgc3BlY2lhbCBvciBleGVtcGxhcnkgZGFtYWdlcyBvciBsb3N0CnByb2ZpdCByZXN1bHRpbmcgZnJvbSBhbnkgdXNlIG9yIG1pc3VzZSBvZiB0aGlzIGRhdGEuCgogQXMgcmVxdWlyZWQgYnkgMTcgVS5TLkMuIDQwMywgdGhpcmQgcGFydGllcyBwcm9kdWNpbmcgY29weXJpZ2h0ZWQgd29ya3MKY29uc2lzdGluZyBwcmVkb21pbmFudGx5IG9mIHRoZSBtYXRlcmlhbCBhcHBlYXJpbmcgaW4gTldTIFdlYiBwYWdlcyBtdXN0CnByb3ZpZGUgbm90aWNlIHdpdGggc3VjaCB3b3JrKHMpIGlkZW50aWZ5aW5nIHRoZSBOV1MgbWF0ZXJpYWwgaW5jb3Jwb3JhdGVkCmFuZCBzdGF0aW5nIHRoYXQgc3VjaCBtYXRlcmlhbCBpcyBub3Qgc3ViamVjdCB0byBjb3B5cmlnaHQgcHJvdGVjdGlvbi4KCnBvcnQvY3BsX21pbml6aXAqCi0tLS0tLS0tLS0tLS0tLS0tCgpUaGlzIGlzIHZlcnNpb24gMjAwNS1GZWItMTAgb2YgdGhlIEluZm8tWklQIGNvcHlyaWdodCBhbmQgbGljZW5zZS4KVGhlIGRlZmluaXRpdmUgdmVyc2lvbiBvZiB0aGlzIGRvY3VtZW50IHNob3VsZCBiZSBhdmFpbGFibGUgYXQKZnRwOi8vZnRwLmluZm8temlwLm9yZy9wdWIvaW5mb3ppcC9saWNlbnNlLmh0bWwgaW5kZWZpbml0ZWx5LgoKCkNvcHlyaWdodCAoYykgMTk5MC0yMDA1IEluZm8tWklQLiAgQWxsIHJpZ2h0cyByZXNlcnZlZC4KCkZvciB0aGUgcHVycG9zZXMgb2YgdGhpcyBjb3B5cmlnaHQgYW5kIGxpY2Vuc2UsICJJbmZvLVpJUCIgaXMgZGVmaW5lZCBhcwp0aGUgZm9sbG93aW5nIHNldCBvZiBpbmRpdmlkdWFsczoKCiAgIE1hcmsgQWRsZXIsIEpvaG4gQnVzaCwgS2FybCBEYXZpcywgSGFyYWxkIERlbmtlciwgSmVhbi1NaWNoZWwgRHVib2lzLAogICBKZWFuLWxvdXAgR2FpbGx5LCBIdW50ZXIgR29hdGxleSwgRWQgR29yZG9uLCBJYW4gR29ybWFuLCBDaHJpcyBIZXJib3J0aCwKICAgRGlyayBIYWFzZSwgR3JlZyBIYXJ0d2lnLCBSb2JlcnQgSGVhdGgsIEpvbmF0aGFuIEh1ZHNvbiwgUGF1bCBLaWVuaXR6LAogICBEYXZpZCBLaXJzY2hiYXVtLCBKb2hubnkgTGVlLCBPbm5vIHZhbiBkZXIgTGluZGVuLCBJZ29yIE1hbmRyaWNoZW5rbywKICAgU3RldmUgUC4gTWlsbGVyLCBTZXJnaW8gTW9uZXNpLCBLZWl0aCBPd2VucywgR2VvcmdlIFBldHJvdiwgR3JlZyBSb2Vsb2ZzLAogICBLYWkgVXdlIFJvbW1lbCwgU3RldmUgU2FsaXNidXJ5LCBEYXZlIFNtaXRoLCBTdGV2ZW4gTS4gU2Nod2VkYSwKICAgQ2hyaXN0aWFuIFNwaWVsZXIsIENvc21pbiBUcnV0YSwgQW50b2luZSBWZXJoZWlqZW4sIFBhdWwgdm9uIEJlaHJlbiwKICAgUmljaCBXYWxlcywgTWlrZSBXaGl0ZQoKVGhpcyBzb2Z0d2FyZSBpcyBwcm92aWRlZCAiYXMgaXMsIiB3aXRob3V0IHdhcnJhbnR5IG9mIGFueSBraW5kLCBleHByZXNzCm9yIGltcGxpZWQuICBJbiBubyBldmVudCBzaGFsbCBJbmZvLVpJUCBvciBpdHMgY29udHJpYnV0b3JzIGJlIGhlbGQgbGlhYmxlCmZvciBhbnkgZGlyZWN0LCBpbmRpcmVjdCwgaW5jaWRlbnRhbCwgc3BlY2lhbCBvciBjb25zZXF1ZW50aWFsIGRhbWFnZXMKYXJpc2luZyBvdXQgb2YgdGhlIHVzZSBvZiBvciBpbmFiaWxpdHkgdG8gdXNlIHRoaXMgc29mdHdhcmUuCgpQZXJtaXNzaW9uIGlzIGdyYW50ZWQgdG8gYW55b25lIHRvIHVzZSB0aGlzIHNvZnR3YXJlIGZvciBhbnkgcHVycG9zZSwKaW5jbHVkaW5nIGNvbW1lcmNpYWwgYXBwbGljYXRpb25zLCBhbmQgdG8gYWx0ZXIgaXQgYW5kIHJlZGlzdHJpYnV0ZSBpdApmcmVlbHksIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyByZXN0cmljdGlvbnM6CgogICAgMS4gUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlLAogICAgICAgZGVmaW5pdGlvbiwgZGlzY2xhaW1lciwgYW5kIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zLgoKICAgIDIuIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSAoY29tcGlsZWQgZXhlY3V0YWJsZXMpIG11c3QgcmVwcm9kdWNlCiAgICAgICB0aGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSwgZGVmaW5pdGlvbiwgZGlzY2xhaW1lciwgYW5kIHRoaXMgbGlzdCBvZgogICAgICAgY29uZGl0aW9ucyBpbiBkb2N1bWVudGF0aW9uIGFuZC9vciBvdGhlciBtYXRlcmlhbHMgcHJvdmlkZWQgd2l0aCB0aGUKICAgICAgIGRpc3RyaWJ1dGlvbi4gIFRoZSBzb2xlIGV4Y2VwdGlvbiB0byB0aGlzIGNvbmRpdGlvbiBpcyByZWRpc3RyaWJ1dGlvbgogICAgICAgb2YgYSBzdGFuZGFyZCBVblppcFNGWCBiaW5hcnkgKGluY2x1ZGluZyBTRlhXaXopIGFzIHBhcnQgb2YgYQogICAgICAgc2VsZi1leHRyYWN0aW5nIGFyY2hpdmU7IHRoYXQgaXMgcGVybWl0dGVkIHdpdGhvdXQgaW5jbHVzaW9uIG9mIHRoaXMKICAgICAgIGxpY2Vuc2UsIGFzIGxvbmcgYXMgdGhlIG5vcm1hbCBTRlggYmFubmVyIGhhcyBub3QgYmVlbiByZW1vdmVkIGZyb20KICAgICAgIHRoZSBiaW5hcnkgb3IgZGlzYWJsZWQuCgogICAgMy4gQWx0ZXJlZCB2ZXJzaW9ucy0taW5jbHVkaW5nLCBidXQgbm90IGxpbWl0ZWQgdG8sIHBvcnRzIHRvIG5ldyBvcGVyYXRpbmcKICAgICAgIHN5c3RlbXMsIGV4aXN0aW5nIHBvcnRzIHdpdGggbmV3IGdyYXBoaWNhbCBpbnRlcmZhY2VzLCBhbmQgZHluYW1pYywKICAgICAgIHNoYXJlZCwgb3Igc3RhdGljIGxpYnJhcnkgdmVyc2lvbnMtLW11c3QgYmUgcGxhaW5seSBtYXJrZWQgYXMgc3VjaAogICAgICAgYW5kIG11c3Qgbm90IGJlIG1pc3JlcHJlc2VudGVkIGFzIGJlaW5nIHRoZSBvcmlnaW5hbCBzb3VyY2UuICBTdWNoCiAgICAgICBhbHRlcmVkIHZlcnNpb25zIGFsc28gbXVzdCBub3QgYmUgbWlzcmVwcmVzZW50ZWQgYXMgYmVpbmcgSW5mby1aSVAKICAgICAgIHJlbGVhc2VzLS1pbmNsdWRpbmcsIGJ1dCBub3QgbGltaXRlZCB0bywgbGFiZWxpbmcgb2YgdGhlIGFsdGVyZWQKICAgICAgIHZlcnNpb25zIHdpdGggdGhlIG5hbWVzICJJbmZvLVpJUCIgKG9yIGFueSB2YXJpYXRpb24gdGhlcmVvZiwgaW5jbHVkaW5nLAogICAgICAgYnV0IG5vdCBsaW1pdGVkIHRvLCBkaWZmZXJlbnQgY2FwaXRhbGl6YXRpb25zKSwgIlBvY2tldCBVblppcCwiICJXaVoiCiAgICAgICBvciAiTWFjWmlwIiB3aXRob3V0IHRoZSBleHBsaWNpdCBwZXJtaXNzaW9uIG9mIEluZm8tWklQLiAgU3VjaCBhbHRlcmVkCiAgICAgICB2ZXJzaW9ucyBhcmUgZnVydGhlciBwcm9oaWJpdGVkIGZyb20gbWlzcmVwcmVzZW50YXRpdmUgdXNlIG9mIHRoZQogICAgICAgWmlwLUJ1Z3Mgb3IgSW5mby1aSVAgZS1tYWlsIGFkZHJlc3NlcyBvciBvZiB0aGUgSW5mby1aSVAgVVJMKHMpLgoKICAgIDQuIEluZm8tWklQIHJldGFpbnMgdGhlIHJpZ2h0IHRvIHVzZSB0aGUgbmFtZXMgIkluZm8tWklQLCIgIlppcCwiICJVblppcCwiCiAgICAgICAiVW5aaXBTRlgsIiAiV2laLCIgIlBvY2tldCBVblppcCwiICJQb2NrZXQgWmlwLCIgYW5kICJNYWNaaXAiIGZvciBpdHMKICAgICAgIG93biBzb3VyY2UgYW5kIGJpbmFyeSByZWxlYXNlcy4KCgpvZ3Ivb2dyc2ZfZnJtdHMvZHhmL2ludHJvbnVyYnMuY3BwCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KClRoaXMgY29kZSBpcyBkZXJpdmVkIGZyb20gdGhlIGNvZGUgYXNzb2NpYXRlZCB3aXRoIHRoZSBib29rICJBbiBJbnRyb2R1Y3Rpb24KdG8gTlVSQlMiIGJ5IERhdmlkIEYuIFJvZ2Vycy4gIE1vcmUgaW5mb3JtYXRpb24gb24gdGhlIGJvb2sgYW5kIHRoZSBjb2RlIGlzCmF2YWlsYWJsZSBhdDoKCiAgaHR0cDovL3d3dy5uYXItYXNzb2NpYXRlcy5jb20vbnVyYnMvCgoKQ29weXJpZ2h0IChjKSAyMDA5LCBEYXZpZCBGLiBSb2dlcnMKQWxsIHJpZ2h0cyByZXNlcnZlZC4KClJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dAptb2RpZmljYXRpb24sIGFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlIG1ldDoKCiAgICAqIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSwKICAgICAgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci4KICAgICogUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlLAogICAgICB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyIGluIHRoZSBkb2N1bWVudGF0aW9uCiAgICAgIGFuZC9vciBvdGhlciBtYXRlcmlhbHMgcHJvdmlkZWQgd2l0aCB0aGUgZGlzdHJpYnV0aW9uLgogICAgKiBOZWl0aGVyIHRoZSBuYW1lIG9mIHRoZSBEYXZpZCBGLiBSb2dlcnMgbm9yIHRoZSBuYW1lcyBvZiBpdHMgY29udHJpYnV0b3JzCiAgICAgIG1heSBiZSB1c2VkIHRvIGVuZG9yc2Ugb3IgcHJvbW90ZSBwcm9kdWN0cyBkZXJpdmVkIGZyb20gdGhpcyBzb2Z0d2FyZQogICAgICB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lvbi4KClRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFORCBDT05UUklCVVRPUlMgIkFTIElTIgpBTkQgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5USUVTLCBJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRFRCBUTywgVEhFCklNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFCkFSRSBESVNDTEFJTUVELiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUIEhPTERFUiBPUiBDT05UUklCVVRPUlMgQkUKTElBQkxFIEZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURFTlRBTCwgU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUgpDT05TRVFVRU5USUFMIERBTUFHRVMgKElOQ0xVRElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRgpTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJQ0VTOyBMT1NTIE9GIFVTRSwgREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5FU1MKSU5URVJSVVBUSU9OKSBIT1dFVkVSIENBVVNFRCBBTkQgT04gQU5ZIFRIRU9SWSBPRiBMSUFCSUxJVFksIFdIRVRIRVIgSU4KQ09OVFJBQ1QsIFNUUklDVCBMSUFCSUxJVFksIE9SIFRPUlQgKElOQ0xVRElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkKQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBUSEUgVVNFIE9GIFRISVMgU09GVFdBUkUsIEVWRU4gSUYgQURWSVNFRCBPRiBUSEUKUE9TU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuCgphbGcvdGhpbnBsYXRlc3BsaW5lLmNwcAotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKSUVFRTc1NCBsb2coKSBjb2RlIGRlcml2ZWQgZnJvbToKQCgjKWVfbG9nLmMgMS4zIDk1LzAxLzE4CgpDb3B5cmlnaHQgKEMpIDE5OTMgYnkgU3VuIE1pY3Jvc3lzdGVtcywgSW5jLiBBbGwgcmlnaHRzIHJlc2VydmVkLgoKRGV2ZWxvcGVkIGF0IFN1blNvZnQsIGEgU3VuIE1pY3Jvc3lzdGVtcywgSW5jLiBidXNpbmVzcy4KUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kIGRpc3RyaWJ1dGUgdGhpcwpzb2Z0d2FyZSBpcyBmcmVlbHkgZ3JhbnRlZCwgcHJvdmlkZWQgdGhhdCB0aGlzIG5vdGljZQppcyBwcmVzZXJ2ZWQuCgoKYWxnL2xpYnFodWxsCi0tLS0tLS0tLS0tLQoKT25seSBhcHBsaWVzIHdoZW4gR0RBTCBpcyBjb21waWxlZCB3aXRoIGludGVybmFsIHFodWxsIHN1cHBvcnQKCgogICAgICAgICAgICAgICAgICAgIFFodWxsLCBDb3B5cmlnaHQgKGMpIDE5OTMtMjAxMgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIEMuQi4gQmFyYmVyCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEFybGluZ3RvbiwgTUEKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbmQKCiAgICAgICBUaGUgTmF0aW9uYWwgU2NpZW5jZSBhbmQgVGVjaG5vbG9neSBSZXNlYXJjaCBDZW50ZXIgZm9yCiAgICAgICAgQ29tcHV0YXRpb24gYW5kIFZpc3VhbGl6YXRpb24gb2YgR2VvbWV0cmljIFN0cnVjdHVyZXMKICAgICAgICAgICAgICAgICAgICAgICAgKFRoZSBHZW9tZXRyeSBDZW50ZXIpCiAgICAgICAgICAgICAgICAgICAgICAgVW5pdmVyc2l0eSBvZiBNaW5uZXNvdGEKCiAgICAgICAgICAgICAgICAgICAgICAgZW1haWw6IHFodWxsQHFodWxsLm9yZwoKVGhpcyBzb2Z0d2FyZSBpbmNsdWRlcyBRaHVsbCBmcm9tIEMuQi4gQmFyYmVyIGFuZCBUaGUgR2VvbWV0cnkgQ2VudGVyLgpRaHVsbCBpcyBjb3B5cmlnaHRlZCBhcyBub3RlZCBhYm92ZS4gIFFodWxsIGlzIGZyZWUgc29mdHdhcmUgYW5kIG1heQpiZSBvYnRhaW5lZCB2aWEgaHR0cCBmcm9tIHd3dy5xaHVsbC5vcmcuICBJdCBtYXkgYmUgZnJlZWx5IGNvcGllZCwgbW9kaWZpZWQsCmFuZCByZWRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKCjEuIEFsbCBjb3B5cmlnaHQgbm90aWNlcyBtdXN0IHJlbWFpbiBpbnRhY3QgaW4gYWxsIGZpbGVzLgoKMi4gQSBjb3B5IG9mIHRoaXMgdGV4dCBmaWxlIG11c3QgYmUgZGlzdHJpYnV0ZWQgYWxvbmcgd2l0aCBhbnkgY29waWVzCiAgIG9mIFFodWxsIHRoYXQgeW91IHJlZGlzdHJpYnV0ZTsgdGhpcyBpbmNsdWRlcyBjb3BpZXMgdGhhdCB5b3UgaGF2ZQogICBtb2RpZmllZCwgb3IgY29waWVzIG9mIHByb2dyYW1zIG9yIG90aGVyIHNvZnR3YXJlIHByb2R1Y3RzIHRoYXQKICAgaW5jbHVkZSBRaHVsbC4KCjMuIElmIHlvdSBtb2RpZnkgUWh1bGwsIHlvdSBtdXN0IGluY2x1ZGUgYSBub3RpY2UgZ2l2aW5nIHRoZQogICBuYW1lIG9mIHRoZSBwZXJzb24gcGVyZm9ybWluZyB0aGUgbW9kaWZpY2F0aW9uLCB0aGUgZGF0ZSBvZgogICBtb2RpZmljYXRpb24sIGFuZCB0aGUgcmVhc29uIGZvciBzdWNoIG1vZGlmaWNhdGlvbi4KCjQuIFdoZW4gZGlzdHJpYnV0aW5nIG1vZGlmaWVkIHZlcnNpb25zIG9mIFFodWxsLCBvciBvdGhlciBzb2Z0d2FyZQogICBwcm9kdWN0cyB0aGF0IGluY2x1ZGUgUWh1bGwsIHlvdSBtdXN0IHByb3ZpZGUgbm90aWNlIHRoYXQgdGhlIG9yaWdpbmFsCiAgIHNvdXJjZSBjb2RlIG1heSBiZSBvYnRhaW5lZCBhcyBub3RlZCBhYm92ZS4KCjUuIFRoZXJlIGlzIG5vIHdhcnJhbnR5IG9yIG90aGVyIGd1YXJhbnRlZSBvZiBmaXRuZXNzIGZvciBRaHVsbCwgaXQgaXMKICAgcHJvdmlkZWQgc29sZWx5ICJhcyBpcyIuICBCdWcgcmVwb3J0cyBvciBmaXhlcyBtYXkgYmUgc2VudCB0bwogICBxaHVsbF9idWdAcWh1bGwub3JnOyB0aGUgYXV0aG9ycyBtYXkgb3IgbWF5IG5vdCBhY3Qgb24gdGhlbSBhcwogICB0aGV5IGRlc2lyZS4KCmZybXRzL3BkZi9wZGZkYXRhc2V0LmNwcCAobWV0aG9kIFBERml1bVJlbmRlclBhZ2VCaXRtYXAoKSkKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKQ29weXJpZ2h0IDIwMTQgUERGaXVtIEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuCgpSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZQptZXQ6CgogICAqIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0Cm5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci4KICAgKiBSZWRpc3RyaWJ1dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlCmNvcHlyaWdodCBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIKaW4gdGhlIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZQpkaXN0cmlidXRpb24uCiAgICogTmVpdGhlciB0aGUgbmFtZSBvZiBHb29nbGUgSW5jLiBub3IgdGhlIG5hbWVzIG9mIGl0cwpjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQgZnJvbQp0aGlzIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwoiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVApMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IKQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFQKT1dORVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsClNQRUNJQUwsIEVYRU1QTEFSWSwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIChJTkNMVURJTkcsIEJVVCBOT1QKTElNSVRFRCBUTywgUFJPQ1VSRU1FTlQgT0YgU1VCU1RJVFVURSBHT09EUyBPUiBTRVJWSUNFUzsgTE9TUyBPRiBVU0UsCkRBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUiBDQVVTRUQgQU5EIE9OIEFOWQpUSEVPUlkgT0YgTElBQklMSVRZLCBXSEVUSEVSIElOIENPTlRSQUNULCBTVFJJQ1QgTElBQklMSVRZLCBPUiBUT1JUCihJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBPVEhFUldJU0UpIEFSSVNJTkcgSU4gQU5ZIFdBWSBPVVQgT0YgVEhFIFVTRQpPRiBUSElTIFNPRlRXQVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLgoKZnJtdHMvbXJmLyogKGFwcGx5IHdoZW4gTVJGIGRyaXZlciBpbmNsdWRlZCBpbiBidWlsZCkKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpDb3B5cmlnaHQgKGMpIDIwMDItMjAxMiwgQ2FsaWZvcm5pYSBJbnN0aXR1dGUgb2YgVGVjaG5vbG9neS4KQWxsIHJpZ2h0cyByZXNlcnZlZC4gIEJhc2VkIG9uIEdvdmVybm1lbnQgU3BvbnNvcmVkIFJlc2VhcmNoIHVuZGVyIGNvbnRyYWN0cyBOQVM3LTE0MDcgYW5kL29yIE5BUzctMDMwMDEuCgpSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQgbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZSBtZXQ6CiAgMS4gUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLgogIDIuIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSwKICAgICB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyIGluIHRoZSBkb2N1bWVudGF0aW9uIGFuZC9vciBvdGhlciBtYXRlcmlhbHMgcHJvdmlkZWQgd2l0aCB0aGUgZGlzdHJpYnV0aW9uLgogIDMuIE5laXRoZXIgdGhlIG5hbWUgb2YgdGhlIENhbGlmb3JuaWEgSW5zdGl0dXRlIG9mIFRlY2hub2xvZ3kgKENhbHRlY2gpLCBpdHMgb3BlcmF0aW5nIGRpdmlzaW9uIHRoZSBKZXQgUHJvcHVsc2lvbiBMYWJvcmF0b3J5IChKUEwpLAogICAgIHRoZSBOYXRpb25hbCBBZXJvbmF1dGljcyBhbmQgU3BhY2UgQWRtaW5pc3RyYXRpb24gKE5BU0EpLCBub3IgdGhlIG5hbWVzIG9mIGl0cyBjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8KICAgICBlbmRvcnNlIG9yIHByb21vdGUgcHJvZHVjdHMgZGVyaXZlZCBmcm9tIHRoaXMgc29mdHdhcmUgd2l0aG91dCBzcGVjaWZpYyBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uCgpUSElTIFNPRlRXQVJFIElTIFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTICJBUyBJUyIgQU5EIEFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFOVElFUywKSU5DTFVESU5HLCBCVVQgTk9UIExJTUlURUQgVE8sIFRIRSBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZIEFORCBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xBSU1FRC4KSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENBTElGT1JOSUEgSU5TVElUVVRFIE9GIFRFQ0hOT0xPR1kgQkUgTElBQkxFIEZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURFTlRBTCwgU1BFQ0lBTCwKRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgKElOQ0xVRElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJQ0VTOwpMT1NTIE9GIFVTRSwgREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5FU1MgSU5URVJSVVBUSU9OKSBIT1dFVkVSIENBVVNFRCBBTkQgT04gQU5ZIFRIRU9SWSBPRiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsClNUUklDVCBMSUFCSUxJVFksIE9SIFRPUlQgKElOQ0xVRElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBUSEUgVVNFIE9GIFRISVMgU09GVFdBUkUsCkVWRU4gSUYgQURWSVNFRCBPRiBUSEUgUE9TU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuCgp+fn5+fn5+fgoKQ29weXJpZ2h0IDIwMTQtMjAxNSBFc3JpCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOwp5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCllvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdApodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQpkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLApXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZApsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCmNtYWtlL21vZHVsZXMvMy4qIChiYWNrcG9ydGVkIENNYWtlIGZpbmQgbW9kdWxlcykKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKQ01ha2UgLSBDcm9zcyBQbGF0Zm9ybSBNYWtlZmlsZSBHZW5lcmF0b3IKQ29weXJpZ2h0IDIwMDAtMjAyMiBLaXR3YXJlLCBJbmMuIGFuZCBDb250cmlidXRvcnMKQWxsIHJpZ2h0cyByZXNlcnZlZC4KClJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dAptb2RpZmljYXRpb24sIGFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnMKYXJlIG1ldDoKCiogUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQKICBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCgoqIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJpZ2h0CiAgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyIGluIHRoZQogIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZSBkaXN0cmlidXRpb24uCgoqIE5laXRoZXIgdGhlIG5hbWUgb2YgS2l0d2FyZSwgSW5jLiBub3IgdGhlIG5hbWVzIG9mIENvbnRyaWJ1dG9ycwogIG1heSBiZSB1c2VkIHRvIGVuZG9yc2Ugb3IgcHJvbW90ZSBwcm9kdWN0cyBkZXJpdmVkIGZyb20gdGhpcwogIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwoiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVApMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IKQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFQKSE9MREVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBESVJFQ1QsIElORElSRUNULCBJTkNJREVOVEFMLApTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLCBCVVQgTk9UCkxJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7IExPU1MgT0YgVVNFLApEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVIgQ0FVU0VEIEFORCBPTiBBTlkKVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVAooSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOIEFOWSBXQVkgT1VUIE9GIFRIRSBVU0UKT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRSBQT1NTSUJJTElUWSBPRiBTVUNIIERBTUFHRS4KCm9nci9vZ3JzZl9mcm10cy9mbGF0Z2VvYnVmCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpGbGF0R2VvYnVmCisrKysrKysrKysKCklTQyBMaWNlbnNlCgpDb3B5cmlnaHQgKGMpIDIwMTgsIEJqb3JuIEhhcnJ0ZWxsCgpQZXJtaXNzaW9uIHRvIHVzZSwgY29weSwgbW9kaWZ5LCBhbmQvb3IgZGlzdHJpYnV0ZSB0aGlzIHNvZnR3YXJlIGZvciBhbnkKcHVycG9zZSB3aXRoIG9yIHdpdGhvdXQgZmVlIGlzIGhlcmVieSBncmFudGVkLCBwcm92aWRlZCB0aGF0IHRoZSBhYm92ZQpjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIGFwcGVhciBpbiBhbGwgY29waWVzLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIgQU5EIFRIRSBBVVRIT1IgRElTQ0xBSU1TIEFMTCBXQVJSQU5USUVTCldJVEggUkVHQVJEIFRPIFRISVMgU09GVFdBUkUgSU5DTFVESU5HIEFMTCBJTVBMSUVEIFdBUlJBTlRJRVMgT0YKTUVSQ0hBTlRBQklMSVRZIEFORCBGSVRORVNTLiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQVVUSE9SIEJFIExJQUJMRSBGT1IKQU5ZIFNQRUNJQUwsIERJUkVDVCwgSU5ESVJFQ1QsIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyBPUiBBTlkgREFNQUdFUwpXSEFUU09FVkVSIFJFU1VMVElORyBGUk9NIExPU1MgT0YgVVNFLCBEQVRBIE9SIFBST0ZJVFMsIFdIRVRIRVIgSU4gQU4KQUNUSU9OIE9GIENPTlRSQUNULCBORUdMSUdFTkNFIE9SIE9USEVSIFRPUlRJT1VTIEFDVElPTiwgQVJJU0lORyBPVVQgT0YKT1IgSU4gQ09OTkVDVElPTiBXSVRIIFRIRSBVU0UgT1IgUEVSRk9STUFOQ0UgT0YgVEhJUyBTT0ZUV0FSRS4KCkZsYXRidXNoCisrKysrKysrCgpJU0MgTGljZW5zZQoKQ29weXJpZ2h0IChjKSAyMDE4LCBWbGFkaW1pciBBZ2Fmb25raW4KClBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueSBwdXJwb3NlCndpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQsIHByb3ZpZGVkIHRoYXQgdGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UKYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2UgYXBwZWFyIGluIGFsbCBjb3BpZXMuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiBBTkQgVEhFIEFVVEhPUiBESVNDTEFJTVMgQUxMIFdBUlJBTlRJRVMgV0lUSApSRUdBUkQgVE8gVEhJUyBTT0ZUV0FSRSBJTkNMVURJTkcgQUxMIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5ECkZJVE5FU1MuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1IgQkUgTElBQkxFIEZPUiBBTlkgU1BFQ0lBTCwgRElSRUNULApJTkRJUkVDVCwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIE9SIEFOWSBEQU1BR0VTIFdIQVRTT0VWRVIgUkVTVUxUSU5HIEZST00gTE9TUwpPRiBVU0UsIERBVEEgT1IgUFJPRklUUywgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIE5FR0xJR0VOQ0UgT1IgT1RIRVIKVE9SVElPVVMgQUNUSU9OLCBBUklTSU5HIE9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFVTRSBPUiBQRVJGT1JNQU5DRSBPRgpUSElTIFNPRlRXQVJFLgoKb2dyL29ncnNmX2ZybXRzL2ZsYXRnZW9idWYvZmxhdGJ1ZmZlcnMKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCkNvcHlyaWdodCAyMDIxIEdvb2dsZSBJbmMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuCgpMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLgpZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKClVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmUKZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuClNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmQKbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCg==", "20e50fe7aae3e56378ebf0417d9de904f55a0e61e4df315333e632a4d3555d95": "ICAgICAgICAgICAgICAgICAgR05VIExFU1NFUiBHRU5FUkFMIFBVQkxJQyBMSUNFTlNFCiAgICAgICAgICAgICAgICAgICAgICAgVmVyc2lvbiAyLjEsIEZlYnJ1YXJ5IDE5OTkKCiBDb3B5cmlnaHQgKEMpIDE5OTEsIDE5OTkgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBJbmMuCiA8aHR0cHM6Ly9mc2Yub3JnLz4KIEV2ZXJ5b25lIGlzIHBlcm1pdHRlZCB0byBjb3B5IGFuZCBkaXN0cmlidXRlIHZlcmJhdGltIGNvcGllcwogb2YgdGhpcyBsaWNlbnNlIGRvY3VtZW50LCBidXQgY2hhbmdpbmcgaXQgaXMgbm90IGFsbG93ZWQuCgpbVGhpcyBpcyB0aGUgZmlyc3QgcmVsZWFzZWQgdmVyc2lvbiBvZiB0aGUgTGVzc2VyIEdQTC4gIEl0IGFsc28gY291bnRzCiBhcyB0aGUgc3VjY2Vzc29yIG9mIHRoZSBHTlUgTGlicmFyeSBQdWJsaWMgTGljZW5zZSwgdmVyc2lvbiAyLCBoZW5jZQogdGhlIHZlcnNpb24gbnVtYmVyIDIuMS5dCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlYW1ibGUKCiAgVGhlIGxpY2Vuc2VzIGZvciBtb3N0IHNvZnR3YXJlIGFyZSBkZXNpZ25lZCB0byB0YWtlIGF3YXkgeW91cgpmcmVlZG9tIHRvIHNoYXJlIGFuZCBjaGFuZ2UgaXQuICBCeSBjb250cmFzdCwgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYwpMaWNlbnNlcyBhcmUgaW50ZW5kZWQgdG8gZ3VhcmFudGVlIHlvdXIgZnJlZWRvbSB0byBzaGFyZSBhbmQgY2hhbmdlCmZyZWUgc29mdHdhcmUtLXRvIG1ha2Ugc3VyZSB0aGUgc29mdHdhcmUgaXMgZnJlZSBmb3IgYWxsIGl0cyB1c2Vycy4KCiAgVGhpcyBsaWNlbnNlLCB0aGUgTGVzc2VyIEdlbmVyYWwgUHVibGljIExpY2Vuc2UsIGFwcGxpZXMgdG8gc29tZQpzcGVjaWFsbHkgZGVzaWduYXRlZCBzb2Z0d2FyZSBwYWNrYWdlcy0tdHlwaWNhbGx5IGxpYnJhcmllcy0tb2YgdGhlCkZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiBhbmQgb3RoZXIgYXV0aG9ycyB3aG8gZGVjaWRlIHRvIHVzZSBpdC4gIFlvdQpjYW4gdXNlIGl0IHRvbywgYnV0IHdlIHN1Z2dlc3QgeW91IGZpcnN0IHRoaW5rIGNhcmVmdWxseSBhYm91dCB3aGV0aGVyCnRoaXMgbGljZW5zZSBvciB0aGUgb3JkaW5hcnkgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBpcyB0aGUgYmV0dGVyCnN0cmF0ZWd5IHRvIHVzZSBpbiBhbnkgcGFydGljdWxhciBjYXNlLCBiYXNlZCBvbiB0aGUgZXhwbGFuYXRpb25zIGJlbG93LgoKICBXaGVuIHdlIHNwZWFrIG9mIGZyZWUgc29mdHdhcmUsIHdlIGFyZSByZWZlcnJpbmcgdG8gZnJlZWRvbSBvZiB1c2UsCm5vdCBwcmljZS4gIE91ciBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlcyBhcmUgZGVzaWduZWQgdG8gbWFrZSBzdXJlIHRoYXQKeW91IGhhdmUgdGhlIGZyZWVkb20gdG8gZGlzdHJpYnV0ZSBjb3BpZXMgb2YgZnJlZSBzb2Z0d2FyZSAoYW5kIGNoYXJnZQpmb3IgdGhpcyBzZXJ2aWNlIGlmIHlvdSB3aXNoKTsgdGhhdCB5b3UgcmVjZWl2ZSBzb3VyY2UgY29kZSBvciBjYW4gZ2V0Cml0IGlmIHlvdSB3YW50IGl0OyB0aGF0IHlvdSBjYW4gY2hhbmdlIHRoZSBzb2Z0d2FyZSBhbmQgdXNlIHBpZWNlcyBvZgppdCBpbiBuZXcgZnJlZSBwcm9ncmFtczsgYW5kIHRoYXQgeW91IGFyZSBpbmZvcm1lZCB0aGF0IHlvdSBjYW4gZG8KdGhlc2UgdGhpbmdzLgoKICBUbyBwcm90ZWN0IHlvdXIgcmlnaHRzLCB3ZSBuZWVkIHRvIG1ha2UgcmVzdHJpY3Rpb25zIHRoYXQgZm9yYmlkCmRpc3RyaWJ1dG9ycyB0byBkZW55IHlvdSB0aGVzZSByaWdodHMgb3IgdG8gYXNrIHlvdSB0byBzdXJyZW5kZXIgdGhlc2UKcmlnaHRzLiAgVGhlc2UgcmVzdHJpY3Rpb25zIHRyYW5zbGF0ZSB0byBjZXJ0YWluIHJlc3BvbnNpYmlsaXRpZXMgZm9yCnlvdSBpZiB5b3UgZGlzdHJpYnV0ZSBjb3BpZXMgb2YgdGhlIGxpYnJhcnkgb3IgaWYgeW91IG1vZGlmeSBpdC4KCiAgRm9yIGV4YW1wbGUsIGlmIHlvdSBkaXN0cmlidXRlIGNvcGllcyBvZiB0aGUgbGlicmFyeSwgd2hldGhlciBncmF0aXMKb3IgZm9yIGEgZmVlLCB5b3UgbXVzdCBnaXZlIHRoZSByZWNpcGllbnRzIGFsbCB0aGUgcmlnaHRzIHRoYXQgd2UgZ2F2ZQp5b3UuICBZb3UgbXVzdCBtYWtlIHN1cmUgdGhhdCB0aGV5LCB0b28sIHJlY2VpdmUgb3IgY2FuIGdldCB0aGUgc291cmNlCmNvZGUuICBJZiB5b3UgbGluayBvdGhlciBjb2RlIHdpdGggdGhlIGxpYnJhcnksIHlvdSBtdXN0IHByb3ZpZGUKY29tcGxldGUgb2JqZWN0IGZpbGVzIHRvIHRoZSByZWNpcGllbnRzLCBzbyB0aGF0IHRoZXkgY2FuIHJlbGluayB0aGVtCndpdGggdGhlIGxpYnJhcnkgYWZ0ZXIgbWFraW5nIGNoYW5nZXMgdG8gdGhlIGxpYnJhcnkgYW5kIHJlY29tcGlsaW5nCml0LiAgQW5kIHlvdSBtdXN0IHNob3cgdGhlbSB0aGVzZSB0ZXJtcyBzbyB0aGV5IGtub3cgdGhlaXIgcmlnaHRzLgoKICBXZSBwcm90ZWN0IHlvdXIgcmlnaHRzIHdpdGggYSB0d28tc3RlcCBtZXRob2Q6ICgxKSB3ZSBjb3B5cmlnaHQgdGhlCmxpYnJhcnksIGFuZCAoMikgd2Ugb2ZmZXIgeW91IHRoaXMgbGljZW5zZSwgd2hpY2ggZ2l2ZXMgeW91IGxlZ2FsCnBlcm1pc3Npb24gdG8gY29weSwgZGlzdHJpYnV0ZSBhbmQvb3IgbW9kaWZ5IHRoZSBsaWJyYXJ5LgoKICBUbyBwcm90ZWN0IGVhY2ggZGlzdHJpYnV0b3IsIHdlIHdhbnQgdG8gbWFrZSBpdCB2ZXJ5IGNsZWFyIHRoYXQKdGhlcmUgaXMgbm8gd2FycmFudHkgZm9yIHRoZSBmcmVlIGxpYnJhcnkuICBBbHNvLCBpZiB0aGUgbGlicmFyeSBpcwptb2RpZmllZCBieSBzb21lb25lIGVsc2UgYW5kIHBhc3NlZCBvbiwgdGhlIHJlY2lwaWVudHMgc2hvdWxkIGtub3cKdGhhdCB3aGF0IHRoZXkgaGF2ZSBpcyBub3QgdGhlIG9yaWdpbmFsIHZlcnNpb24sIHNvIHRoYXQgdGhlIG9yaWdpbmFsCmF1dGhvcidzIHJlcHV0YXRpb24gd2lsbCBub3QgYmUgYWZmZWN0ZWQgYnkgcHJvYmxlbXMgdGhhdCBtaWdodCBiZQppbnRyb2R1Y2VkIGJ5IG90aGVycy4KDAogIEZpbmFsbHksIHNvZnR3YXJlIHBhdGVudHMgcG9zZSBhIGNvbnN0YW50IHRocmVhdCB0byB0aGUgZXhpc3RlbmNlIG9mCmFueSBmcmVlIHByb2dyYW0uICBXZSB3aXNoIHRvIG1ha2Ugc3VyZSB0aGF0IGEgY29tcGFueSBjYW5ub3QKZWZmZWN0aXZlbHkgcmVzdHJpY3QgdGhlIHVzZXJzIG9mIGEgZnJlZSBwcm9ncmFtIGJ5IG9idGFpbmluZyBhCnJlc3RyaWN0aXZlIGxpY2Vuc2UgZnJvbSBhIHBhdGVudCBob2xkZXIuICBUaGVyZWZvcmUsIHdlIGluc2lzdCB0aGF0CmFueSBwYXRlbnQgbGljZW5zZSBvYnRhaW5lZCBmb3IgYSB2ZXJzaW9uIG9mIHRoZSBsaWJyYXJ5IG11c3QgYmUKY29uc2lzdGVudCB3aXRoIHRoZSBmdWxsIGZyZWVkb20gb2YgdXNlIHNwZWNpZmllZCBpbiB0aGlzIGxpY2Vuc2UuCgogIE1vc3QgR05VIHNvZnR3YXJlLCBpbmNsdWRpbmcgc29tZSBsaWJyYXJpZXMsIGlzIGNvdmVyZWQgYnkgdGhlCm9yZGluYXJ5IEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlLiAgVGhpcyBsaWNlbnNlLCB0aGUgR05VIExlc3NlcgpHZW5lcmFsIFB1YmxpYyBMaWNlbnNlLCBhcHBsaWVzIHRvIGNlcnRhaW4gZGVzaWduYXRlZCBsaWJyYXJpZXMsIGFuZAppcyBxdWl0ZSBkaWZmZXJlbnQgZnJvbSB0aGUgb3JkaW5hcnkgR2VuZXJhbCBQdWJsaWMgTGljZW5zZS4gIFdlIHVzZQp0aGlzIGxpY2Vuc2UgZm9yIGNlcnRhaW4gbGlicmFyaWVzIGluIG9yZGVyIHRvIHBlcm1pdCBsaW5raW5nIHRob3NlCmxpYnJhcmllcyBpbnRvIG5vbi1mcmVlIHByb2dyYW1zLgoKICBXaGVuIGEgcHJvZ3JhbSBpcyBsaW5rZWQgd2l0aCBhIGxpYnJhcnksIHdoZXRoZXIgc3RhdGljYWxseSBvciB1c2luZwphIHNoYXJlZCBsaWJyYXJ5LCB0aGUgY29tYmluYXRpb24gb2YgdGhlIHR3byBpcyBsZWdhbGx5IHNwZWFraW5nIGEKY29tYmluZWQgd29yaywgYSBkZXJpdmF0aXZlIG9mIHRoZSBvcmlnaW5hbCBsaWJyYXJ5LiAgVGhlIG9yZGluYXJ5CkdlbmVyYWwgUHVibGljIExpY2Vuc2UgdGhlcmVmb3JlIHBlcm1pdHMgc3VjaCBsaW5raW5nIG9ubHkgaWYgdGhlCmVudGlyZSBjb21iaW5hdGlvbiBmaXRzIGl0cyBjcml0ZXJpYSBvZiBmcmVlZG9tLiAgVGhlIExlc3NlciBHZW5lcmFsClB1YmxpYyBMaWNlbnNlIHBlcm1pdHMgbW9yZSBsYXggY3JpdGVyaWEgZm9yIGxpbmtpbmcgb3RoZXIgY29kZSB3aXRoCnRoZSBsaWJyYXJ5LgoKICBXZSBjYWxsIHRoaXMgbGljZW5zZSB0aGUgIkxlc3NlciIgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBiZWNhdXNlIGl0CmRvZXMgTGVzcyB0byBwcm90ZWN0IHRoZSB1c2VyJ3MgZnJlZWRvbSB0aGFuIHRoZSBvcmRpbmFyeSBHZW5lcmFsClB1YmxpYyBMaWNlbnNlLiAgSXQgYWxzbyBwcm92aWRlcyBvdGhlciBmcmVlIHNvZnR3YXJlIGRldmVsb3BlcnMgTGVzcwpvZiBhbiBhZHZhbnRhZ2Ugb3ZlciBjb21wZXRpbmcgbm9uLWZyZWUgcHJvZ3JhbXMuICBUaGVzZSBkaXNhZHZhbnRhZ2VzCmFyZSB0aGUgcmVhc29uIHdlIHVzZSB0aGUgb3JkaW5hcnkgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbWFueQpsaWJyYXJpZXMuICBIb3dldmVyLCB0aGUgTGVzc2VyIGxpY2Vuc2UgcHJvdmlkZXMgYWR2YW50YWdlcyBpbiBjZXJ0YWluCnNwZWNpYWwgY2lyY3Vtc3RhbmNlcy4KCiAgRm9yIGV4YW1wbGUsIG9uIHJhcmUgb2NjYXNpb25zLCB0aGVyZSBtYXkgYmUgYSBzcGVjaWFsIG5lZWQgdG8KZW5jb3VyYWdlIHRoZSB3aWRlc3QgcG9zc2libGUgdXNlIG9mIGEgY2VydGFpbiBsaWJyYXJ5LCBzbyB0aGF0IGl0IGJlY29tZXMKYSBkZS1mYWN0byBzdGFuZGFyZC4gIFRvIGFjaGlldmUgdGhpcywgbm9uLWZyZWUgcHJvZ3JhbXMgbXVzdCBiZQphbGxvd2VkIHRvIHVzZSB0aGUgbGlicmFyeS4gIEEgbW9yZSBmcmVxdWVudCBjYXNlIGlzIHRoYXQgYSBmcmVlCmxpYnJhcnkgZG9lcyB0aGUgc2FtZSBqb2IgYXMgd2lkZWx5IHVzZWQgbm9uLWZyZWUgbGlicmFyaWVzLiAgSW4gdGhpcwpjYXNlLCB0aGVyZSBpcyBsaXR0bGUgdG8gZ2FpbiBieSBsaW1pdGluZyB0aGUgZnJlZSBsaWJyYXJ5IHRvIGZyZWUKc29mdHdhcmUgb25seSwgc28gd2UgdXNlIHRoZSBMZXNzZXIgR2VuZXJhbCBQdWJsaWMgTGljZW5zZS4KCiAgSW4gb3RoZXIgY2FzZXMsIHBlcm1pc3Npb24gdG8gdXNlIGEgcGFydGljdWxhciBsaWJyYXJ5IGluIG5vbi1mcmVlCnByb2dyYW1zIGVuYWJsZXMgYSBncmVhdGVyIG51bWJlciBvZiBwZW9wbGUgdG8gdXNlIGEgbGFyZ2UgYm9keSBvZgpmcmVlIHNvZnR3YXJlLiAgRm9yIGV4YW1wbGUsIHBlcm1pc3Npb24gdG8gdXNlIHRoZSBHTlUgQyBMaWJyYXJ5IGluCm5vbi1mcmVlIHByb2dyYW1zIGVuYWJsZXMgbWFueSBtb3JlIHBlb3BsZSB0byB1c2UgdGhlIHdob2xlIEdOVQpvcGVyYXRpbmcgc3lzdGVtLCBhcyB3ZWxsIGFzIGl0cyB2YXJpYW50LCB0aGUgR05VL0xpbnV4IG9wZXJhdGluZwpzeXN0ZW0uCgogIEFsdGhvdWdoIHRoZSBMZXNzZXIgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBpcyBMZXNzIHByb3RlY3RpdmUgb2YgdGhlCnVzZXJzJyBmcmVlZG9tLCBpdCBkb2VzIGVuc3VyZSB0aGF0IHRoZSB1c2VyIG9mIGEgcHJvZ3JhbSB0aGF0IGlzCmxpbmtlZCB3aXRoIHRoZSBMaWJyYXJ5IGhhcyB0aGUgZnJlZWRvbSBhbmQgdGhlIHdoZXJld2l0aGFsIHRvIHJ1bgp0aGF0IHByb2dyYW0gdXNpbmcgYSBtb2RpZmllZCB2ZXJzaW9uIG9mIHRoZSBMaWJyYXJ5LgoKICBUaGUgcHJlY2lzZSB0ZXJtcyBhbmQgY29uZGl0aW9ucyBmb3IgY29weWluZywgZGlzdHJpYnV0aW9uIGFuZAptb2RpZmljYXRpb24gZm9sbG93LiAgUGF5IGNsb3NlIGF0dGVudGlvbiB0byB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGEKIndvcmsgYmFzZWQgb24gdGhlIGxpYnJhcnkiIGFuZCBhICJ3b3JrIHRoYXQgdXNlcyB0aGUgbGlicmFyeSIuICBUaGUKZm9ybWVyIGNvbnRhaW5zIGNvZGUgZGVyaXZlZCBmcm9tIHRoZSBsaWJyYXJ5LCB3aGVyZWFzIHRoZSBsYXR0ZXIgbXVzdApiZSBjb21iaW5lZCB3aXRoIHRoZSBsaWJyYXJ5IGluIG9yZGVyIHRvIHJ1bi4KDAogICAgICAgICAgICAgICAgICBHTlUgTEVTU0VSIEdFTkVSQUwgUFVCTElDIExJQ0VOU0UKICAgVEVSTVMgQU5EIENPTkRJVElPTlMgRk9SIENPUFlJTkcsIERJU1RSSUJVVElPTiBBTkQgTU9ESUZJQ0FUSU9OCgogIDAuIFRoaXMgTGljZW5zZSBBZ3JlZW1lbnQgYXBwbGllcyB0byBhbnkgc29mdHdhcmUgbGlicmFyeSBvciBvdGhlcgpwcm9ncmFtIHdoaWNoIGNvbnRhaW5zIGEgbm90aWNlIHBsYWNlZCBieSB0aGUgY29weXJpZ2h0IGhvbGRlciBvcgpvdGhlciBhdXRob3JpemVkIHBhcnR5IHNheWluZyBpdCBtYXkgYmUgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIHRlcm1zIG9mCnRoaXMgTGVzc2VyIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgKGFsc28gY2FsbGVkICJ0aGlzIExpY2Vuc2UiKS4KRWFjaCBsaWNlbnNlZSBpcyBhZGRyZXNzZWQgYXMgInlvdSIuCgogIEEgImxpYnJhcnkiIG1lYW5zIGEgY29sbGVjdGlvbiBvZiBzb2Z0d2FyZSBmdW5jdGlvbnMgYW5kL29yIGRhdGEKcHJlcGFyZWQgc28gYXMgdG8gYmUgY29udmVuaWVudGx5IGxpbmtlZCB3aXRoIGFwcGxpY2F0aW9uIHByb2dyYW1zCih3aGljaCB1c2Ugc29tZSBvZiB0aG9zZSBmdW5jdGlvbnMgYW5kIGRhdGEpIHRvIGZvcm0gZXhlY3V0YWJsZXMuCgogIFRoZSAiTGlicmFyeSIsIGJlbG93LCByZWZlcnMgdG8gYW55IHN1Y2ggc29mdHdhcmUgbGlicmFyeSBvciB3b3JrCndoaWNoIGhhcyBiZWVuIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZXNlIHRlcm1zLiAgQSAid29yayBiYXNlZCBvbiB0aGUKTGlicmFyeSIgbWVhbnMgZWl0aGVyIHRoZSBMaWJyYXJ5IG9yIGFueSBkZXJpdmF0aXZlIHdvcmsgdW5kZXIKY29weXJpZ2h0IGxhdzogdGhhdCBpcyB0byBzYXksIGEgd29yayBjb250YWluaW5nIHRoZSBMaWJyYXJ5IG9yIGEKcG9ydGlvbiBvZiBpdCwgZWl0aGVyIHZlcmJhdGltIG9yIHdpdGggbW9kaWZpY2F0aW9ucyBhbmQvb3IgdHJhbnNsYXRlZApzdHJhaWdodGZvcndhcmRseSBpbnRvIGFub3RoZXIgbGFuZ3VhZ2UuICAoSGVyZWluYWZ0ZXIsIHRyYW5zbGF0aW9uIGlzCmluY2x1ZGVkIHdpdGhvdXQgbGltaXRhdGlvbiBpbiB0aGUgdGVybSAibW9kaWZpY2F0aW9uIi4pCgogICJTb3VyY2UgY29kZSIgZm9yIGEgd29yayBtZWFucyB0aGUgcHJlZmVycmVkIGZvcm0gb2YgdGhlIHdvcmsgZm9yCm1ha2luZyBtb2RpZmljYXRpb25zIHRvIGl0LiAgRm9yIGEgbGlicmFyeSwgY29tcGxldGUgc291cmNlIGNvZGUgbWVhbnMKYWxsIHRoZSBzb3VyY2UgY29kZSBmb3IgYWxsIG1vZHVsZXMgaXQgY29udGFpbnMsIHBsdXMgYW55IGFzc29jaWF0ZWQKaW50ZXJmYWNlIGRlZmluaXRpb24gZmlsZXMsIHBsdXMgdGhlIHNjcmlwdHMgdXNlZCB0byBjb250cm9sIGNvbXBpbGF0aW9uCmFuZCBpbnN0YWxsYXRpb24gb2YgdGhlIGxpYnJhcnkuCgogIEFjdGl2aXRpZXMgb3RoZXIgdGhhbiBjb3B5aW5nLCBkaXN0cmlidXRpb24gYW5kIG1vZGlmaWNhdGlvbiBhcmUgbm90CmNvdmVyZWQgYnkgdGhpcyBMaWNlbnNlOyB0aGV5IGFyZSBvdXRzaWRlIGl0cyBzY29wZS4gIFRoZSBhY3Qgb2YKcnVubmluZyBhIHByb2dyYW0gdXNpbmcgdGhlIExpYnJhcnkgaXMgbm90IHJlc3RyaWN0ZWQsIGFuZCBvdXRwdXQgZnJvbQpzdWNoIGEgcHJvZ3JhbSBpcyBjb3ZlcmVkIG9ubHkgaWYgaXRzIGNvbnRlbnRzIGNvbnN0aXR1dGUgYSB3b3JrIGJhc2VkCm9uIHRoZSBMaWJyYXJ5IChpbmRlcGVuZGVudCBvZiB0aGUgdXNlIG9mIHRoZSBMaWJyYXJ5IGluIGEgdG9vbCBmb3IKd3JpdGluZyBpdCkuICBXaGV0aGVyIHRoYXQgaXMgdHJ1ZSBkZXBlbmRzIG9uIHdoYXQgdGhlIExpYnJhcnkgZG9lcwphbmQgd2hhdCB0aGUgcHJvZ3JhbSB0aGF0IHVzZXMgdGhlIExpYnJhcnkgZG9lcy4KCiAgMS4gWW91IG1heSBjb3B5IGFuZCBkaXN0cmlidXRlIHZlcmJhdGltIGNvcGllcyBvZiB0aGUgTGlicmFyeSdzCmNvbXBsZXRlIHNvdXJjZSBjb2RlIGFzIHlvdSByZWNlaXZlIGl0LCBpbiBhbnkgbWVkaXVtLCBwcm92aWRlZCB0aGF0CnlvdSBjb25zcGljdW91c2x5IGFuZCBhcHByb3ByaWF0ZWx5IHB1Ymxpc2ggb24gZWFjaCBjb3B5IGFuCmFwcHJvcHJpYXRlIGNvcHlyaWdodCBub3RpY2UgYW5kIGRpc2NsYWltZXIgb2Ygd2FycmFudHk7IGtlZXAgaW50YWN0CmFsbCB0aGUgbm90aWNlcyB0aGF0IHJlZmVyIHRvIHRoaXMgTGljZW5zZSBhbmQgdG8gdGhlIGFic2VuY2Ugb2YgYW55CndhcnJhbnR5OyBhbmQgZGlzdHJpYnV0ZSBhIGNvcHkgb2YgdGhpcyBMaWNlbnNlIGFsb25nIHdpdGggdGhlCkxpYnJhcnkuCgogIFlvdSBtYXkgY2hhcmdlIGEgZmVlIGZvciB0aGUgcGh5c2ljYWwgYWN0IG9mIHRyYW5zZmVycmluZyBhIGNvcHksCmFuZCB5b3UgbWF5IGF0IHlvdXIgb3B0aW9uIG9mZmVyIHdhcnJhbnR5IHByb3RlY3Rpb24gaW4gZXhjaGFuZ2UgZm9yIGEKZmVlLgoMCiAgMi4gWW91IG1heSBtb2RpZnkgeW91ciBjb3B5IG9yIGNvcGllcyBvZiB0aGUgTGlicmFyeSBvciBhbnkgcG9ydGlvbgpvZiBpdCwgdGh1cyBmb3JtaW5nIGEgd29yayBiYXNlZCBvbiB0aGUgTGlicmFyeSwgYW5kIGNvcHkgYW5kCmRpc3RyaWJ1dGUgc3VjaCBtb2RpZmljYXRpb25zIG9yIHdvcmsgdW5kZXIgdGhlIHRlcm1zIG9mIFNlY3Rpb24gMQphYm92ZSwgcHJvdmlkZWQgdGhhdCB5b3UgYWxzbyBtZWV0IGFsbCBvZiB0aGVzZSBjb25kaXRpb25zOgoKICAgIGEpIFRoZSBtb2RpZmllZCB3b3JrIG11c3QgaXRzZWxmIGJlIGEgc29mdHdhcmUgbGlicmFyeS4KCiAgICBiKSBZb3UgbXVzdCBjYXVzZSB0aGUgZmlsZXMgbW9kaWZpZWQgdG8gY2FycnkgcHJvbWluZW50IG5vdGljZXMKICAgIHN0YXRpbmcgdGhhdCB5b3UgY2hhbmdlZCB0aGUgZmlsZXMgYW5kIHRoZSBkYXRlIG9mIGFueSBjaGFuZ2UuCgogICAgYykgWW91IG11c3QgY2F1c2UgdGhlIHdob2xlIG9mIHRoZSB3b3JrIHRvIGJlIGxpY2Vuc2VkIGF0IG5vCiAgICBjaGFyZ2UgdG8gYWxsIHRoaXJkIHBhcnRpZXMgdW5kZXIgdGhlIHRlcm1zIG9mIHRoaXMgTGljZW5zZS4KCiAgICBkKSBJZiBhIGZhY2lsaXR5IGluIHRoZSBtb2RpZmllZCBMaWJyYXJ5IHJlZmVycyB0byBhIGZ1bmN0aW9uIG9yIGEKICAgIHRhYmxlIG9mIGRhdGEgdG8gYmUgc3VwcGxpZWQgYnkgYW4gYXBwbGljYXRpb24gcHJvZ3JhbSB0aGF0IHVzZXMKICAgIHRoZSBmYWNpbGl0eSwgb3RoZXIgdGhhbiBhcyBhbiBhcmd1bWVudCBwYXNzZWQgd2hlbiB0aGUgZmFjaWxpdHkKICAgIGlzIGludm9rZWQsIHRoZW4geW91IG11c3QgbWFrZSBhIGdvb2QgZmFpdGggZWZmb3J0IHRvIGVuc3VyZSB0aGF0LAogICAgaW4gdGhlIGV2ZW50IGFuIGFwcGxpY2F0aW9uIGRvZXMgbm90IHN1cHBseSBzdWNoIGZ1bmN0aW9uIG9yCiAgICB0YWJsZSwgdGhlIGZhY2lsaXR5IHN0aWxsIG9wZXJhdGVzLCBhbmQgcGVyZm9ybXMgd2hhdGV2ZXIgcGFydCBvZgogICAgaXRzIHB1cnBvc2UgcmVtYWlucyBtZWFuaW5nZnVsLgoKICAgIChGb3IgZXhhbXBsZSwgYSBmdW5jdGlvbiBpbiBhIGxpYnJhcnkgdG8gY29tcHV0ZSBzcXVhcmUgcm9vdHMgaGFzCiAgICBhIHB1cnBvc2UgdGhhdCBpcyBlbnRpcmVseSB3ZWxsLWRlZmluZWQgaW5kZXBlbmRlbnQgb2YgdGhlCiAgICBhcHBsaWNhdGlvbi4gIFRoZXJlZm9yZSwgU3Vic2VjdGlvbiAyZCByZXF1aXJlcyB0aGF0IGFueQogICAgYXBwbGljYXRpb24tc3VwcGxpZWQgZnVuY3Rpb24gb3IgdGFibGUgdXNlZCBieSB0aGlzIGZ1bmN0aW9uIG11c3QKICAgIGJlIG9wdGlvbmFsOiBpZiB0aGUgYXBwbGljYXRpb24gZG9lcyBub3Qgc3VwcGx5IGl0LCB0aGUgc3F1YXJlCiAgICByb290IGZ1bmN0aW9uIG11c3Qgc3RpbGwgY29tcHV0ZSBzcXVhcmUgcm9vdHMuKQoKVGhlc2UgcmVxdWlyZW1lbnRzIGFwcGx5IHRvIHRoZSBtb2RpZmllZCB3b3JrIGFzIGEgd2hvbGUuICBJZgppZGVudGlmaWFibGUgc2VjdGlvbnMgb2YgdGhhdCB3b3JrIGFyZSBub3QgZGVyaXZlZCBmcm9tIHRoZSBMaWJyYXJ5LAphbmQgY2FuIGJlIHJlYXNvbmFibHkgY29uc2lkZXJlZCBpbmRlcGVuZGVudCBhbmQgc2VwYXJhdGUgd29ya3MgaW4KdGhlbXNlbHZlcywgdGhlbiB0aGlzIExpY2Vuc2UsIGFuZCBpdHMgdGVybXMsIGRvIG5vdCBhcHBseSB0byB0aG9zZQpzZWN0aW9ucyB3aGVuIHlvdSBkaXN0cmlidXRlIHRoZW0gYXMgc2VwYXJhdGUgd29ya3MuICBCdXQgd2hlbiB5b3UKZGlzdHJpYnV0ZSB0aGUgc2FtZSBzZWN0aW9ucyBhcyBwYXJ0IG9mIGEgd2hvbGUgd2hpY2ggaXMgYSB3b3JrIGJhc2VkCm9uIHRoZSBMaWJyYXJ5LCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSB3aG9sZSBtdXN0IGJlIG9uIHRoZSB0ZXJtcyBvZgp0aGlzIExpY2Vuc2UsIHdob3NlIHBlcm1pc3Npb25zIGZvciBvdGhlciBsaWNlbnNlZXMgZXh0ZW5kIHRvIHRoZQplbnRpcmUgd2hvbGUsIGFuZCB0aHVzIHRvIGVhY2ggYW5kIGV2ZXJ5IHBhcnQgcmVnYXJkbGVzcyBvZiB3aG8gd3JvdGUKaXQuCgpUaHVzLCBpdCBpcyBub3QgdGhlIGludGVudCBvZiB0aGlzIHNlY3Rpb24gdG8gY2xhaW0gcmlnaHRzIG9yIGNvbnRlc3QKeW91ciByaWdodHMgdG8gd29yayB3cml0dGVuIGVudGlyZWx5IGJ5IHlvdTsgcmF0aGVyLCB0aGUgaW50ZW50IGlzIHRvCmV4ZXJjaXNlIHRoZSByaWdodCB0byBjb250cm9sIHRoZSBkaXN0cmlidXRpb24gb2YgZGVyaXZhdGl2ZSBvcgpjb2xsZWN0aXZlIHdvcmtzIGJhc2VkIG9uIHRoZSBMaWJyYXJ5LgoKSW4gYWRkaXRpb24sIG1lcmUgYWdncmVnYXRpb24gb2YgYW5vdGhlciB3b3JrIG5vdCBiYXNlZCBvbiB0aGUgTGlicmFyeQp3aXRoIHRoZSBMaWJyYXJ5IChvciB3aXRoIGEgd29yayBiYXNlZCBvbiB0aGUgTGlicmFyeSkgb24gYSB2b2x1bWUgb2YKYSBzdG9yYWdlIG9yIGRpc3RyaWJ1dGlvbiBtZWRpdW0gZG9lcyBub3QgYnJpbmcgdGhlIG90aGVyIHdvcmsgdW5kZXIKdGhlIHNjb3BlIG9mIHRoaXMgTGljZW5zZS4KCiAgMy4gWW91IG1heSBvcHQgdG8gYXBwbHkgdGhlIHRlcm1zIG9mIHRoZSBvcmRpbmFyeSBHTlUgR2VuZXJhbCBQdWJsaWMKTGljZW5zZSBpbnN0ZWFkIG9mIHRoaXMgTGljZW5zZSB0byBhIGdpdmVuIGNvcHkgb2YgdGhlIExpYnJhcnkuICBUbyBkbwp0aGlzLCB5b3UgbXVzdCBhbHRlciBhbGwgdGhlIG5vdGljZXMgdGhhdCByZWZlciB0byB0aGlzIExpY2Vuc2UsIHNvCnRoYXQgdGhleSByZWZlciB0byB0aGUgb3JkaW5hcnkgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UsIHZlcnNpb24gMiwKaW5zdGVhZCBvZiB0byB0aGlzIExpY2Vuc2UuICAoSWYgYSBuZXdlciB2ZXJzaW9uIHRoYW4gdmVyc2lvbiAyIG9mIHRoZQpvcmRpbmFyeSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBoYXMgYXBwZWFyZWQsIHRoZW4geW91IGNhbiBzcGVjaWZ5CnRoYXQgdmVyc2lvbiBpbnN0ZWFkIGlmIHlvdSB3aXNoLikgIERvIG5vdCBtYWtlIGFueSBvdGhlciBjaGFuZ2UgaW4KdGhlc2Ugbm90aWNlcy4KDAogIE9uY2UgdGhpcyBjaGFuZ2UgaXMgbWFkZSBpbiBhIGdpdmVuIGNvcHksIGl0IGlzIGlycmV2ZXJzaWJsZSBmb3IKdGhhdCBjb3B5LCBzbyB0aGUgb3JkaW5hcnkgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgYXBwbGllcyB0byBhbGwKc3Vic2VxdWVudCBjb3BpZXMgYW5kIGRlcml2YXRpdmUgd29ya3MgbWFkZSBmcm9tIHRoYXQgY29weS4KCiAgVGhpcyBvcHRpb24gaXMgdXNlZnVsIHdoZW4geW91IHdpc2ggdG8gY29weSBwYXJ0IG9mIHRoZSBjb2RlIG9mCnRoZSBMaWJyYXJ5IGludG8gYSBwcm9ncmFtIHRoYXQgaXMgbm90IGEgbGlicmFyeS4KCiAgNC4gWW91IG1heSBjb3B5IGFuZCBkaXN0cmlidXRlIHRoZSBMaWJyYXJ5IChvciBhIHBvcnRpb24gb3IKZGVyaXZhdGl2ZSBvZiBpdCwgdW5kZXIgU2VjdGlvbiAyKSBpbiBvYmplY3QgY29kZSBvciBleGVjdXRhYmxlIGZvcm0KdW5kZXIgdGhlIHRlcm1zIG9mIFNlY3Rpb25zIDEgYW5kIDIgYWJvdmUgcHJvdmlkZWQgdGhhdCB5b3UgYWNjb21wYW55Cml0IHdpdGggdGhlIGNvbXBsZXRlIGNvcnJlc3BvbmRpbmcgbWFjaGluZS1yZWFkYWJsZSBzb3VyY2UgY29kZSwgd2hpY2gKbXVzdCBiZSBkaXN0cmlidXRlZCB1bmRlciB0aGUgdGVybXMgb2YgU2VjdGlvbnMgMSBhbmQgMiBhYm92ZSBvbiBhCm1lZGl1bSBjdXN0b21hcmlseSB1c2VkIGZvciBzb2Z0d2FyZSBpbnRlcmNoYW5nZS4KCiAgSWYgZGlzdHJpYnV0aW9uIG9mIG9iamVjdCBjb2RlIGlzIG1hZGUgYnkgb2ZmZXJpbmcgYWNjZXNzIHRvIGNvcHkKZnJvbSBhIGRlc2lnbmF0ZWQgcGxhY2UsIHRoZW4gb2ZmZXJpbmcgZXF1aXZhbGVudCBhY2Nlc3MgdG8gY29weSB0aGUKc291cmNlIGNvZGUgZnJvbSB0aGUgc2FtZSBwbGFjZSBzYXRpc2ZpZXMgdGhlIHJlcXVpcmVtZW50IHRvCmRpc3RyaWJ1dGUgdGhlIHNvdXJjZSBjb2RlLCBldmVuIHRob3VnaCB0aGlyZCBwYXJ0aWVzIGFyZSBub3QKY29tcGVsbGVkIHRvIGNvcHkgdGhlIHNvdXJjZSBhbG9uZyB3aXRoIHRoZSBvYmplY3QgY29kZS4KCiAgNS4gQSBwcm9ncmFtIHRoYXQgY29udGFpbnMgbm8gZGVyaXZhdGl2ZSBvZiBhbnkgcG9ydGlvbiBvZiB0aGUKTGlicmFyeSwgYnV0IGlzIGRlc2lnbmVkIHRvIHdvcmsgd2l0aCB0aGUgTGlicmFyeSBieSBiZWluZyBjb21waWxlZCBvcgpsaW5rZWQgd2l0aCBpdCwgaXMgY2FsbGVkIGEgIndvcmsgdGhhdCB1c2VzIHRoZSBMaWJyYXJ5Ii4gIFN1Y2ggYQp3b3JrLCBpbiBpc29sYXRpb24sIGlzIG5vdCBhIGRlcml2YXRpdmUgd29yayBvZiB0aGUgTGlicmFyeSwgYW5kCnRoZXJlZm9yZSBmYWxscyBvdXRzaWRlIHRoZSBzY29wZSBvZiB0aGlzIExpY2Vuc2UuCgogIEhvd2V2ZXIsIGxpbmtpbmcgYSAid29yayB0aGF0IHVzZXMgdGhlIExpYnJhcnkiIHdpdGggdGhlIExpYnJhcnkKY3JlYXRlcyBhbiBleGVjdXRhYmxlIHRoYXQgaXMgYSBkZXJpdmF0aXZlIG9mIHRoZSBMaWJyYXJ5IChiZWNhdXNlIGl0CmNvbnRhaW5zIHBvcnRpb25zIG9mIHRoZSBMaWJyYXJ5KSwgcmF0aGVyIHRoYW4gYSAid29yayB0aGF0IHVzZXMgdGhlCmxpYnJhcnkiLiAgVGhlIGV4ZWN1dGFibGUgaXMgdGhlcmVmb3JlIGNvdmVyZWQgYnkgdGhpcyBMaWNlbnNlLgpTZWN0aW9uIDYgc3RhdGVzIHRlcm1zIGZvciBkaXN0cmlidXRpb24gb2Ygc3VjaCBleGVjdXRhYmxlcy4KCiAgV2hlbiBhICJ3b3JrIHRoYXQgdXNlcyB0aGUgTGlicmFyeSIgdXNlcyBtYXRlcmlhbCBmcm9tIGEgaGVhZGVyIGZpbGUKdGhhdCBpcyBwYXJ0IG9mIHRoZSBMaWJyYXJ5LCB0aGUgb2JqZWN0IGNvZGUgZm9yIHRoZSB3b3JrIG1heSBiZSBhCmRlcml2YXRpdmUgd29yayBvZiB0aGUgTGlicmFyeSBldmVuIHRob3VnaCB0aGUgc291cmNlIGNvZGUgaXMgbm90LgpXaGV0aGVyIHRoaXMgaXMgdHJ1ZSBpcyBlc3BlY2lhbGx5IHNpZ25pZmljYW50IGlmIHRoZSB3b3JrIGNhbiBiZQpsaW5rZWQgd2l0aG91dCB0aGUgTGlicmFyeSwgb3IgaWYgdGhlIHdvcmsgaXMgaXRzZWxmIGEgbGlicmFyeS4gIFRoZQp0aHJlc2hvbGQgZm9yIHRoaXMgdG8gYmUgdHJ1ZSBpcyBub3QgcHJlY2lzZWx5IGRlZmluZWQgYnkgbGF3LgoKICBJZiBzdWNoIGFuIG9iamVjdCBmaWxlIHVzZXMgb25seSBudW1lcmljYWwgcGFyYW1ldGVycywgZGF0YQpzdHJ1Y3R1cmUgbGF5b3V0cyBhbmQgYWNjZXNzb3JzLCBhbmQgc21hbGwgbWFjcm9zIGFuZCBzbWFsbCBpbmxpbmUKZnVuY3Rpb25zICh0ZW4gbGluZXMgb3IgbGVzcyBpbiBsZW5ndGgpLCB0aGVuIHRoZSB1c2Ugb2YgdGhlIG9iamVjdApmaWxlIGlzIHVucmVzdHJpY3RlZCwgcmVnYXJkbGVzcyBvZiB3aGV0aGVyIGl0IGlzIGxlZ2FsbHkgYSBkZXJpdmF0aXZlCndvcmsuICAoRXhlY3V0YWJsZXMgY29udGFpbmluZyB0aGlzIG9iamVjdCBjb2RlIHBsdXMgcG9ydGlvbnMgb2YgdGhlCkxpYnJhcnkgd2lsbCBzdGlsbCBmYWxsIHVuZGVyIFNlY3Rpb24gNi4pCgogIE90aGVyd2lzZSwgaWYgdGhlIHdvcmsgaXMgYSBkZXJpdmF0aXZlIG9mIHRoZSBMaWJyYXJ5LCB5b3UgbWF5CmRpc3RyaWJ1dGUgdGhlIG9iamVjdCBjb2RlIGZvciB0aGUgd29yayB1bmRlciB0aGUgdGVybXMgb2YgU2VjdGlvbiA2LgpBbnkgZXhlY3V0YWJsZXMgY29udGFpbmluZyB0aGF0IHdvcmsgYWxzbyBmYWxsIHVuZGVyIFNlY3Rpb24gNiwKd2hldGhlciBvciBub3QgdGhleSBhcmUgbGlua2VkIGRpcmVjdGx5IHdpdGggdGhlIExpYnJhcnkgaXRzZWxmLgoMCiAgNi4gQXMgYW4gZXhjZXB0aW9uIHRvIHRoZSBTZWN0aW9ucyBhYm92ZSwgeW91IG1heSBhbHNvIGNvbWJpbmUgb3IKbGluayBhICJ3b3JrIHRoYXQgdXNlcyB0aGUgTGlicmFyeSIgd2l0aCB0aGUgTGlicmFyeSB0byBwcm9kdWNlIGEKd29yayBjb250YWluaW5nIHBvcnRpb25zIG9mIHRoZSBMaWJyYXJ5LCBhbmQgZGlzdHJpYnV0ZSB0aGF0IHdvcmsKdW5kZXIgdGVybXMgb2YgeW91ciBjaG9pY2UsIHByb3ZpZGVkIHRoYXQgdGhlIHRlcm1zIHBlcm1pdAptb2RpZmljYXRpb24gb2YgdGhlIHdvcmsgZm9yIHRoZSBjdXN0b21lcidzIG93biB1c2UgYW5kIHJldmVyc2UKZW5naW5lZXJpbmcgZm9yIGRlYnVnZ2luZyBzdWNoIG1vZGlmaWNhdGlvbnMuCgogIFlvdSBtdXN0IGdpdmUgcHJvbWluZW50IG5vdGljZSB3aXRoIGVhY2ggY29weSBvZiB0aGUgd29yayB0aGF0IHRoZQpMaWJyYXJ5IGlzIHVzZWQgaW4gaXQgYW5kIHRoYXQgdGhlIExpYnJhcnkgYW5kIGl0cyB1c2UgYXJlIGNvdmVyZWQgYnkKdGhpcyBMaWNlbnNlLiAgWW91IG11c3Qgc3VwcGx5IGEgY29weSBvZiB0aGlzIExpY2Vuc2UuICBJZiB0aGUgd29yawpkdXJpbmcgZXhlY3V0aW9uIGRpc3BsYXlzIGNvcHlyaWdodCBub3RpY2VzLCB5b3UgbXVzdCBpbmNsdWRlIHRoZQpjb3B5cmlnaHQgbm90aWNlIGZvciB0aGUgTGlicmFyeSBhbW9uZyB0aGVtLCBhcyB3ZWxsIGFzIGEgcmVmZXJlbmNlCmRpcmVjdGluZyB0aGUgdXNlciB0byB0aGUgY29weSBvZiB0aGlzIExpY2Vuc2UuICBBbHNvLCB5b3UgbXVzdCBkbyBvbmUKb2YgdGhlc2UgdGhpbmdzOgoKICAgIGEpIEFjY29tcGFueSB0aGUgd29yayB3aXRoIHRoZSBjb21wbGV0ZSBjb3JyZXNwb25kaW5nCiAgICBtYWNoaW5lLXJlYWRhYmxlIHNvdXJjZSBjb2RlIGZvciB0aGUgTGlicmFyeSBpbmNsdWRpbmcgd2hhdGV2ZXIKICAgIGNoYW5nZXMgd2VyZSB1c2VkIGluIHRoZSB3b3JrICh3aGljaCBtdXN0IGJlIGRpc3RyaWJ1dGVkIHVuZGVyCiAgICBTZWN0aW9ucyAxIGFuZCAyIGFib3ZlKTsgYW5kLCBpZiB0aGUgd29yayBpcyBhbiBleGVjdXRhYmxlIGxpbmtlZAogICAgd2l0aCB0aGUgTGlicmFyeSwgd2l0aCB0aGUgY29tcGxldGUgbWFjaGluZS1yZWFkYWJsZSAid29yayB0aGF0CiAgICB1c2VzIHRoZSBMaWJyYXJ5IiwgYXMgb2JqZWN0IGNvZGUgYW5kL29yIHNvdXJjZSBjb2RlLCBzbyB0aGF0IHRoZQogICAgdXNlciBjYW4gbW9kaWZ5IHRoZSBMaWJyYXJ5IGFuZCB0aGVuIHJlbGluayB0byBwcm9kdWNlIGEgbW9kaWZpZWQKICAgIGV4ZWN1dGFibGUgY29udGFpbmluZyB0aGUgbW9kaWZpZWQgTGlicmFyeS4gIChJdCBpcyB1bmRlcnN0b29kCiAgICB0aGF0IHRoZSB1c2VyIHdobyBjaGFuZ2VzIHRoZSBjb250ZW50cyBvZiBkZWZpbml0aW9ucyBmaWxlcyBpbiB0aGUKICAgIExpYnJhcnkgd2lsbCBub3QgbmVjZXNzYXJpbHkgYmUgYWJsZSB0byByZWNvbXBpbGUgdGhlIGFwcGxpY2F0aW9uCiAgICB0byB1c2UgdGhlIG1vZGlmaWVkIGRlZmluaXRpb25zLikKCiAgICBiKSBVc2UgYSBzdWl0YWJsZSBzaGFyZWQgbGlicmFyeSBtZWNoYW5pc20gZm9yIGxpbmtpbmcgd2l0aCB0aGUKICAgIExpYnJhcnkuICBBIHN1aXRhYmxlIG1lY2hhbmlzbSBpcyBvbmUgdGhhdCAoMSkgdXNlcyBhdCBydW4gdGltZSBhCiAgICBjb3B5IG9mIHRoZSBsaWJyYXJ5IGFscmVhZHkgcHJlc2VudCBvbiB0aGUgdXNlcidzIGNvbXB1dGVyIHN5c3RlbSwKICAgIHJhdGhlciB0aGFuIGNvcHlpbmcgbGlicmFyeSBmdW5jdGlvbnMgaW50byB0aGUgZXhlY3V0YWJsZSwgYW5kICgyKQogICAgd2lsbCBvcGVyYXRlIHByb3Blcmx5IHdpdGggYSBtb2RpZmllZCB2ZXJzaW9uIG9mIHRoZSBsaWJyYXJ5LCBpZgogICAgdGhlIHVzZXIgaW5zdGFsbHMgb25lLCBhcyBsb25nIGFzIHRoZSBtb2RpZmllZCB2ZXJzaW9uIGlzCiAgICBpbnRlcmZhY2UtY29tcGF0aWJsZSB3aXRoIHRoZSB2ZXJzaW9uIHRoYXQgdGhlIHdvcmsgd2FzIG1hZGUgd2l0aC4KCiAgICBjKSBBY2NvbXBhbnkgdGhlIHdvcmsgd2l0aCBhIHdyaXR0ZW4gb2ZmZXIsIHZhbGlkIGZvciBhdAogICAgbGVhc3QgdGhyZWUgeWVhcnMsIHRvIGdpdmUgdGhlIHNhbWUgdXNlciB0aGUgbWF0ZXJpYWxzCiAgICBzcGVjaWZpZWQgaW4gU3Vic2VjdGlvbiA2YSwgYWJvdmUsIGZvciBhIGNoYXJnZSBubyBtb3JlCiAgICB0aGFuIHRoZSBjb3N0IG9mIHBlcmZvcm1pbmcgdGhpcyBkaXN0cmlidXRpb24uCgogICAgZCkgSWYgZGlzdHJpYnV0aW9uIG9mIHRoZSB3b3JrIGlzIG1hZGUgYnkgb2ZmZXJpbmcgYWNjZXNzIHRvIGNvcHkKICAgIGZyb20gYSBkZXNpZ25hdGVkIHBsYWNlLCBvZmZlciBlcXVpdmFsZW50IGFjY2VzcyB0byBjb3B5IHRoZSBhYm92ZQogICAgc3BlY2lmaWVkIG1hdGVyaWFscyBmcm9tIHRoZSBzYW1lIHBsYWNlLgoKICAgIGUpIFZlcmlmeSB0aGF0IHRoZSB1c2VyIGhhcyBhbHJlYWR5IHJlY2VpdmVkIGEgY29weSBvZiB0aGVzZQogICAgbWF0ZXJpYWxzIG9yIHRoYXQgeW91IGhhdmUgYWxyZWFkeSBzZW50IHRoaXMgdXNlciBhIGNvcHkuCgogIEZvciBhbiBleGVjdXRhYmxlLCB0aGUgcmVxdWlyZWQgZm9ybSBvZiB0aGUgIndvcmsgdGhhdCB1c2VzIHRoZQpMaWJyYXJ5IiBtdXN0IGluY2x1ZGUgYW55IGRhdGEgYW5kIHV0aWxpdHkgcHJvZ3JhbXMgbmVlZGVkIGZvcgpyZXByb2R1Y2luZyB0aGUgZXhlY3V0YWJsZSBmcm9tIGl0LiAgSG93ZXZlciwgYXMgYSBzcGVjaWFsIGV4Y2VwdGlvbiwKdGhlIG1hdGVyaWFscyB0byBiZSBkaXN0cmlidXRlZCBuZWVkIG5vdCBpbmNsdWRlIGFueXRoaW5nIHRoYXQgaXMKbm9ybWFsbHkgZGlzdHJpYnV0ZWQgKGluIGVpdGhlciBzb3VyY2Ugb3IgYmluYXJ5IGZvcm0pIHdpdGggdGhlIG1ham9yCmNvbXBvbmVudHMgKGNvbXBpbGVyLCBrZXJuZWwsIGFuZCBzbyBvbikgb2YgdGhlIG9wZXJhdGluZyBzeXN0ZW0gb24Kd2hpY2ggdGhlIGV4ZWN1dGFibGUgcnVucywgdW5sZXNzIHRoYXQgY29tcG9uZW50IGl0c2VsZiBhY2NvbXBhbmllcwp0aGUgZXhlY3V0YWJsZS4KCiAgSXQgbWF5IGhhcHBlbiB0aGF0IHRoaXMgcmVxdWlyZW1lbnQgY29udHJhZGljdHMgdGhlIGxpY2Vuc2UKcmVzdHJpY3Rpb25zIG9mIG90aGVyIHByb3ByaWV0YXJ5IGxpYnJhcmllcyB0aGF0IGRvIG5vdCBub3JtYWxseQphY2NvbXBhbnkgdGhlIG9wZXJhdGluZyBzeXN0ZW0uICBTdWNoIGEgY29udHJhZGljdGlvbiBtZWFucyB5b3UgY2Fubm90CnVzZSBib3RoIHRoZW0gYW5kIHRoZSBMaWJyYXJ5IHRvZ2V0aGVyIGluIGFuIGV4ZWN1dGFibGUgdGhhdCB5b3UKZGlzdHJpYnV0ZS4KDAogIDcuIFlvdSBtYXkgcGxhY2UgbGlicmFyeSBmYWNpbGl0aWVzIHRoYXQgYXJlIGEgd29yayBiYXNlZCBvbiB0aGUKTGlicmFyeSBzaWRlLWJ5LXNpZGUgaW4gYSBzaW5nbGUgbGlicmFyeSB0b2dldGhlciB3aXRoIG90aGVyIGxpYnJhcnkKZmFjaWxpdGllcyBub3QgY292ZXJlZCBieSB0aGlzIExpY2Vuc2UsIGFuZCBkaXN0cmlidXRlIHN1Y2ggYSBjb21iaW5lZApsaWJyYXJ5LCBwcm92aWRlZCB0aGF0IHRoZSBzZXBhcmF0ZSBkaXN0cmlidXRpb24gb2YgdGhlIHdvcmsgYmFzZWQgb24KdGhlIExpYnJhcnkgYW5kIG9mIHRoZSBvdGhlciBsaWJyYXJ5IGZhY2lsaXRpZXMgaXMgb3RoZXJ3aXNlCnBlcm1pdHRlZCwgYW5kIHByb3ZpZGVkIHRoYXQgeW91IGRvIHRoZXNlIHR3byB0aGluZ3M6CgogICAgYSkgQWNjb21wYW55IHRoZSBjb21iaW5lZCBsaWJyYXJ5IHdpdGggYSBjb3B5IG9mIHRoZSBzYW1lIHdvcmsKICAgIGJhc2VkIG9uIHRoZSBMaWJyYXJ5LCB1bmNvbWJpbmVkIHdpdGggYW55IG90aGVyIGxpYnJhcnkKICAgIGZhY2lsaXRpZXMuICBUaGlzIG11c3QgYmUgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZQogICAgU2VjdGlvbnMgYWJvdmUuCgogICAgYikgR2l2ZSBwcm9taW5lbnQgbm90aWNlIHdpdGggdGhlIGNvbWJpbmVkIGxpYnJhcnkgb2YgdGhlIGZhY3QKICAgIHRoYXQgcGFydCBvZiBpdCBpcyBhIHdvcmsgYmFzZWQgb24gdGhlIExpYnJhcnksIGFuZCBleHBsYWluaW5nCiAgICB3aGVyZSB0byBmaW5kIHRoZSBhY2NvbXBhbnlpbmcgdW5jb21iaW5lZCBmb3JtIG9mIHRoZSBzYW1lIHdvcmsuCgogIDguIFlvdSBtYXkgbm90IGNvcHksIG1vZGlmeSwgc3VibGljZW5zZSwgbGluayB3aXRoLCBvciBkaXN0cmlidXRlCnRoZSBMaWJyYXJ5IGV4Y2VwdCBhcyBleHByZXNzbHkgcHJvdmlkZWQgdW5kZXIgdGhpcyBMaWNlbnNlLiAgQW55CmF0dGVtcHQgb3RoZXJ3aXNlIHRvIGNvcHksIG1vZGlmeSwgc3VibGljZW5zZSwgbGluayB3aXRoLCBvcgpkaXN0cmlidXRlIHRoZSBMaWJyYXJ5IGlzIHZvaWQsIGFuZCB3aWxsIGF1dG9tYXRpY2FsbHkgdGVybWluYXRlIHlvdXIKcmlnaHRzIHVuZGVyIHRoaXMgTGljZW5zZS4gIEhvd2V2ZXIsIHBhcnRpZXMgd2hvIGhhdmUgcmVjZWl2ZWQgY29waWVzLApvciByaWdodHMsIGZyb20geW91IHVuZGVyIHRoaXMgTGljZW5zZSB3aWxsIG5vdCBoYXZlIHRoZWlyIGxpY2Vuc2VzCnRlcm1pbmF0ZWQgc28gbG9uZyBhcyBzdWNoIHBhcnRpZXMgcmVtYWluIGluIGZ1bGwgY29tcGxpYW5jZS4KCiAgOS4gWW91IGFyZSBub3QgcmVxdWlyZWQgdG8gYWNjZXB0IHRoaXMgTGljZW5zZSwgc2luY2UgeW91IGhhdmUgbm90CnNpZ25lZCBpdC4gIEhvd2V2ZXIsIG5vdGhpbmcgZWxzZSBncmFudHMgeW91IHBlcm1pc3Npb24gdG8gbW9kaWZ5IG9yCmRpc3RyaWJ1dGUgdGhlIExpYnJhcnkgb3IgaXRzIGRlcml2YXRpdmUgd29ya3MuICBUaGVzZSBhY3Rpb25zIGFyZQpwcm9oaWJpdGVkIGJ5IGxhdyBpZiB5b3UgZG8gbm90IGFjY2VwdCB0aGlzIExpY2Vuc2UuICBUaGVyZWZvcmUsIGJ5Cm1vZGlmeWluZyBvciBkaXN0cmlidXRpbmcgdGhlIExpYnJhcnkgKG9yIGFueSB3b3JrIGJhc2VkIG9uIHRoZQpMaWJyYXJ5KSwgeW91IGluZGljYXRlIHlvdXIgYWNjZXB0YW5jZSBvZiB0aGlzIExpY2Vuc2UgdG8gZG8gc28sIGFuZAphbGwgaXRzIHRlcm1zIGFuZCBjb25kaXRpb25zIGZvciBjb3B5aW5nLCBkaXN0cmlidXRpbmcgb3IgbW9kaWZ5aW5nCnRoZSBMaWJyYXJ5IG9yIHdvcmtzIGJhc2VkIG9uIGl0LgoKICAxMC4gRWFjaCB0aW1lIHlvdSByZWRpc3RyaWJ1dGUgdGhlIExpYnJhcnkgKG9yIGFueSB3b3JrIGJhc2VkIG9uIHRoZQpMaWJyYXJ5KSwgdGhlIHJlY2lwaWVudCBhdXRvbWF0aWNhbGx5IHJlY2VpdmVzIGEgbGljZW5zZSBmcm9tIHRoZQpvcmlnaW5hbCBsaWNlbnNvciB0byBjb3B5LCBkaXN0cmlidXRlLCBsaW5rIHdpdGggb3IgbW9kaWZ5IHRoZSBMaWJyYXJ5CnN1YmplY3QgdG8gdGhlc2UgdGVybXMgYW5kIGNvbmRpdGlvbnMuICBZb3UgbWF5IG5vdCBpbXBvc2UgYW55IGZ1cnRoZXIKcmVzdHJpY3Rpb25zIG9uIHRoZSByZWNpcGllbnRzJyBleGVyY2lzZSBvZiB0aGUgcmlnaHRzIGdyYW50ZWQgaGVyZWluLgpZb3UgYXJlIG5vdCByZXNwb25zaWJsZSBmb3IgZW5mb3JjaW5nIGNvbXBsaWFuY2UgYnkgdGhpcmQgcGFydGllcyB3aXRoCnRoaXMgTGljZW5zZS4KDAogIDExLiBJZiwgYXMgYSBjb25zZXF1ZW5jZSBvZiBhIGNvdXJ0IGp1ZGdtZW50IG9yIGFsbGVnYXRpb24gb2YgcGF0ZW50CmluZnJpbmdlbWVudCBvciBmb3IgYW55IG90aGVyIHJlYXNvbiAobm90IGxpbWl0ZWQgdG8gcGF0ZW50IGlzc3VlcyksCmNvbmRpdGlvbnMgYXJlIGltcG9zZWQgb24geW91ICh3aGV0aGVyIGJ5IGNvdXJ0IG9yZGVyLCBhZ3JlZW1lbnQgb3IKb3RoZXJ3aXNlKSB0aGF0IGNvbnRyYWRpY3QgdGhlIGNvbmRpdGlvbnMgb2YgdGhpcyBMaWNlbnNlLCB0aGV5IGRvIG5vdApleGN1c2UgeW91IGZyb20gdGhlIGNvbmRpdGlvbnMgb2YgdGhpcyBMaWNlbnNlLiAgSWYgeW91IGNhbm5vdApkaXN0cmlidXRlIHNvIGFzIHRvIHNhdGlzZnkgc2ltdWx0YW5lb3VzbHkgeW91ciBvYmxpZ2F0aW9ucyB1bmRlciB0aGlzCkxpY2Vuc2UgYW5kIGFueSBvdGhlciBwZXJ0aW5lbnQgb2JsaWdhdGlvbnMsIHRoZW4gYXMgYSBjb25zZXF1ZW5jZSB5b3UKbWF5IG5vdCBkaXN0cmlidXRlIHRoZSBMaWJyYXJ5IGF0IGFsbC4gIEZvciBleGFtcGxlLCBpZiBhIHBhdGVudApsaWNlbnNlIHdvdWxkIG5vdCBwZXJtaXQgcm95YWx0eS1mcmVlIHJlZGlzdHJpYnV0aW9uIG9mIHRoZSBMaWJyYXJ5IGJ5CmFsbCB0aG9zZSB3aG8gcmVjZWl2ZSBjb3BpZXMgZGlyZWN0bHkgb3IgaW5kaXJlY3RseSB0aHJvdWdoIHlvdSwgdGhlbgp0aGUgb25seSB3YXkgeW91IGNvdWxkIHNhdGlzZnkgYm90aCBpdCBhbmQgdGhpcyBMaWNlbnNlIHdvdWxkIGJlIHRvCnJlZnJhaW4gZW50aXJlbHkgZnJvbSBkaXN0cmlidXRpb24gb2YgdGhlIExpYnJhcnkuCgpJZiBhbnkgcG9ydGlvbiBvZiB0aGlzIHNlY3Rpb24gaXMgaGVsZCBpbnZhbGlkIG9yIHVuZW5mb3JjZWFibGUgdW5kZXIgYW55CnBhcnRpY3VsYXIgY2lyY3Vtc3RhbmNlLCB0aGUgYmFsYW5jZSBvZiB0aGUgc2VjdGlvbiBpcyBpbnRlbmRlZCB0byBhcHBseSwKYW5kIHRoZSBzZWN0aW9uIGFzIGEgd2hvbGUgaXMgaW50ZW5kZWQgdG8gYXBwbHkgaW4gb3RoZXIgY2lyY3Vtc3RhbmNlcy4KCkl0IGlzIG5vdCB0aGUgcHVycG9zZSBvZiB0aGlzIHNlY3Rpb24gdG8gaW5kdWNlIHlvdSB0byBpbmZyaW5nZSBhbnkKcGF0ZW50cyBvciBvdGhlciBwcm9wZXJ0eSByaWdodCBjbGFpbXMgb3IgdG8gY29udGVzdCB2YWxpZGl0eSBvZiBhbnkKc3VjaCBjbGFpbXM7IHRoaXMgc2VjdGlvbiBoYXMgdGhlIHNvbGUgcHVycG9zZSBvZiBwcm90ZWN0aW5nIHRoZQppbnRlZ3JpdHkgb2YgdGhlIGZyZWUgc29mdHdhcmUgZGlzdHJpYnV0aW9uIHN5c3RlbSB3aGljaCBpcwppbXBsZW1lbnRlZCBieSBwdWJsaWMgbGljZW5zZSBwcmFjdGljZXMuICBNYW55IHBlb3BsZSBoYXZlIG1hZGUKZ2VuZXJvdXMgY29udHJpYnV0aW9ucyB0byB0aGUgd2lkZSByYW5nZSBvZiBzb2Z0d2FyZSBkaXN0cmlidXRlZAp0aHJvdWdoIHRoYXQgc3lzdGVtIGluIHJlbGlhbmNlIG9uIGNvbnNpc3RlbnQgYXBwbGljYXRpb24gb2YgdGhhdApzeXN0ZW07IGl0IGlzIHVwIHRvIHRoZSBhdXRob3IvZG9ub3IgdG8gZGVjaWRlIGlmIGhlIG9yIHNoZSBpcyB3aWxsaW5nCnRvIGRpc3RyaWJ1dGUgc29mdHdhcmUgdGhyb3VnaCBhbnkgb3RoZXIgc3lzdGVtIGFuZCBhIGxpY2Vuc2VlIGNhbm5vdAppbXBvc2UgdGhhdCBjaG9pY2UuCgpUaGlzIHNlY3Rpb24gaXMgaW50ZW5kZWQgdG8gbWFrZSB0aG9yb3VnaGx5IGNsZWFyIHdoYXQgaXMgYmVsaWV2ZWQgdG8KYmUgYSBjb25zZXF1ZW5jZSBvZiB0aGUgcmVzdCBvZiB0aGlzIExpY2Vuc2UuCgogIDEyLiBJZiB0aGUgZGlzdHJpYnV0aW9uIGFuZC9vciB1c2Ugb2YgdGhlIExpYnJhcnkgaXMgcmVzdHJpY3RlZCBpbgpjZXJ0YWluIGNvdW50cmllcyBlaXRoZXIgYnkgcGF0ZW50cyBvciBieSBjb3B5cmlnaHRlZCBpbnRlcmZhY2VzLCB0aGUKb3JpZ2luYWwgY29weXJpZ2h0IGhvbGRlciB3aG8gcGxhY2VzIHRoZSBMaWJyYXJ5IHVuZGVyIHRoaXMgTGljZW5zZSBtYXkgYWRkCmFuIGV4cGxpY2l0IGdlb2dyYXBoaWNhbCBkaXN0cmlidXRpb24gbGltaXRhdGlvbiBleGNsdWRpbmcgdGhvc2UgY291bnRyaWVzLApzbyB0aGF0IGRpc3RyaWJ1dGlvbiBpcyBwZXJtaXR0ZWQgb25seSBpbiBvciBhbW9uZyBjb3VudHJpZXMgbm90IHRodXMKZXhjbHVkZWQuICBJbiBzdWNoIGNhc2UsIHRoaXMgTGljZW5zZSBpbmNvcnBvcmF0ZXMgdGhlIGxpbWl0YXRpb24gYXMgaWYKd3JpdHRlbiBpbiB0aGUgYm9keSBvZiB0aGlzIExpY2Vuc2UuCgogIDEzLiBUaGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uIG1heSBwdWJsaXNoIHJldmlzZWQgYW5kL29yIG5ldwp2ZXJzaW9ucyBvZiB0aGUgTGVzc2VyIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgZnJvbSB0aW1lIHRvIHRpbWUuClN1Y2ggbmV3IHZlcnNpb25zIHdpbGwgYmUgc2ltaWxhciBpbiBzcGlyaXQgdG8gdGhlIHByZXNlbnQgdmVyc2lvbiwKYnV0IG1heSBkaWZmZXIgaW4gZGV0YWlsIHRvIGFkZHJlc3MgbmV3IHByb2JsZW1zIG9yIGNvbmNlcm5zLgoKRWFjaCB2ZXJzaW9uIGlzIGdpdmVuIGEgZGlzdGluZ3Vpc2hpbmcgdmVyc2lvbiBudW1iZXIuICBJZiB0aGUgTGlicmFyeQpzcGVjaWZpZXMgYSB2ZXJzaW9uIG51bWJlciBvZiB0aGlzIExpY2Vuc2Ugd2hpY2ggYXBwbGllcyB0byBpdCBhbmQKImFueSBsYXRlciB2ZXJzaW9uIiwgeW91IGhhdmUgdGhlIG9wdGlvbiBvZiBmb2xsb3dpbmcgdGhlIHRlcm1zIGFuZApjb25kaXRpb25zIGVpdGhlciBvZiB0aGF0IHZlcnNpb24gb3Igb2YgYW55IGxhdGVyIHZlcnNpb24gcHVibGlzaGVkIGJ5CnRoZSBGcmVlIFNvZnR3YXJlIEZvdW5kYXRpb24uICBJZiB0aGUgTGlicmFyeSBkb2VzIG5vdCBzcGVjaWZ5IGEKbGljZW5zZSB2ZXJzaW9uIG51bWJlciwgeW91IG1heSBjaG9vc2UgYW55IHZlcnNpb24gZXZlciBwdWJsaXNoZWQgYnkKdGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbi4KDAogIDE0LiBJZiB5b3Ugd2lzaCB0byBpbmNvcnBvcmF0ZSBwYXJ0cyBvZiB0aGUgTGlicmFyeSBpbnRvIG90aGVyIGZyZWUKcHJvZ3JhbXMgd2hvc2UgZGlzdHJpYnV0aW9uIGNvbmRpdGlvbnMgYXJlIGluY29tcGF0aWJsZSB3aXRoIHRoZXNlLAp3cml0ZSB0byB0aGUgYXV0aG9yIHRvIGFzayBmb3IgcGVybWlzc2lvbi4gIEZvciBzb2Z0d2FyZSB3aGljaCBpcwpjb3B5cmlnaHRlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCB3cml0ZSB0byB0aGUgRnJlZQpTb2Z0d2FyZSBGb3VuZGF0aW9uOyB3ZSBzb21ldGltZXMgbWFrZSBleGNlcHRpb25zIGZvciB0aGlzLiAgT3VyCmRlY2lzaW9uIHdpbGwgYmUgZ3VpZGVkIGJ5IHRoZSB0d28gZ29hbHMgb2YgcHJlc2VydmluZyB0aGUgZnJlZSBzdGF0dXMKb2YgYWxsIGRlcml2YXRpdmVzIG9mIG91ciBmcmVlIHNvZnR3YXJlIGFuZCBvZiBwcm9tb3RpbmcgdGhlIHNoYXJpbmcKYW5kIHJldXNlIG9mIHNvZnR3YXJlIGdlbmVyYWxseS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBOTyBXQVJSQU5UWQoKICAxNS4gQkVDQVVTRSBUSEUgTElCUkFSWSBJUyBMSUNFTlNFRCBGUkVFIE9GIENIQVJHRSwgVEhFUkUgSVMgTk8KV0FSUkFOVFkgRk9SIFRIRSBMSUJSQVJZLCBUTyBUSEUgRVhURU5UIFBFUk1JVFRFRCBCWSBBUFBMSUNBQkxFIExBVy4KRVhDRVBUIFdIRU4gT1RIRVJXSVNFIFNUQVRFRCBJTiBXUklUSU5HIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQvT1IKT1RIRVIgUEFSVElFUyBQUk9WSURFIFRIRSBMSUJSQVJZICJBUyBJUyIgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkKS0lORCwgRUlUSEVSIEVYUFJFU1NFRCBPUiBJTVBMSUVELCBJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRFRCBUTywgVEhFCklNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUgpQVVJQT1NFLiAgVEhFIEVOVElSRSBSSVNLIEFTIFRPIFRIRSBRVUFMSVRZIEFORCBQRVJGT1JNQU5DRSBPRiBUSEUKTElCUkFSWSBJUyBXSVRIIFlPVS4gIFNIT1VMRCBUSEUgTElCUkFSWSBQUk9WRSBERUZFQ1RJVkUsIFlPVSBBU1NVTUUKVEhFIENPU1QgT0YgQUxMIE5FQ0VTU0FSWSBTRVJWSUNJTkcsIFJFUEFJUiBPUiBDT1JSRUNUSU9OLgoKICAxNi4gSU4gTk8gRVZFTlQgVU5MRVNTIFJFUVVJUkVEIEJZIEFQUExJQ0FCTEUgTEFXIE9SIEFHUkVFRCBUTyBJTgpXUklUSU5HIFdJTEwgQU5ZIENPUFlSSUdIVCBIT0xERVIsIE9SIEFOWSBPVEhFUiBQQVJUWSBXSE8gTUFZIE1PRElGWQpBTkQvT1IgUkVESVNUUklCVVRFIFRIRSBMSUJSQVJZIEFTIFBFUk1JVFRFRCBBQk9WRSwgQkUgTElBQkxFIFRPIFlPVQpGT1IgREFNQUdFUywgSU5DTFVESU5HIEFOWSBHRU5FUkFMLCBTUEVDSUFMLCBJTkNJREVOVEFMIE9SCkNPTlNFUVVFTlRJQUwgREFNQUdFUyBBUklTSU5HIE9VVCBPRiBUSEUgVVNFIE9SIElOQUJJTElUWSBUTyBVU0UgVEhFCkxJQlJBUlkgKElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gTE9TUyBPRiBEQVRBIE9SIERBVEEgQkVJTkcKUkVOREVSRUQgSU5BQ0NVUkFURSBPUiBMT1NTRVMgU1VTVEFJTkVEIEJZIFlPVSBPUiBUSElSRCBQQVJUSUVTIE9SIEEKRkFJTFVSRSBPRiBUSEUgTElCUkFSWSBUTyBPUEVSQVRFIFdJVEggQU5ZIE9USEVSIFNPRlRXQVJFKSwgRVZFTiBJRgpTVUNIIEhPTERFUiBPUiBPVEhFUiBQQVJUWSBIQVMgQkVFTiBBRFZJU0VEIE9GIFRIRSBQT1NTSUJJTElUWSBPRiBTVUNICkRBTUFHRVMuCgogICAgICAgICAgICAgICAgICAgICBFTkQgT0YgVEVSTVMgQU5EIENPTkRJVElPTlMKDAogICAgICAgICAgIEhvdyB0byBBcHBseSBUaGVzZSBUZXJtcyB0byBZb3VyIE5ldyBMaWJyYXJpZXMKCiAgSWYgeW91IGRldmVsb3AgYSBuZXcgbGlicmFyeSwgYW5kIHlvdSB3YW50IGl0IHRvIGJlIG9mIHRoZSBncmVhdGVzdApwb3NzaWJsZSB1c2UgdG8gdGhlIHB1YmxpYywgd2UgcmVjb21tZW5kIG1ha2luZyBpdCBmcmVlIHNvZnR3YXJlIHRoYXQKZXZlcnlvbmUgY2FuIHJlZGlzdHJpYnV0ZSBhbmQgY2hhbmdlLiAgWW91IGNhbiBkbyBzbyBieSBwZXJtaXR0aW5nCnJlZGlzdHJpYnV0aW9uIHVuZGVyIHRoZXNlIHRlcm1zIChvciwgYWx0ZXJuYXRpdmVseSwgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZQpvcmRpbmFyeSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlKS4KCiAgVG8gYXBwbHkgdGhlc2UgdGVybXMsIGF0dGFjaCB0aGUgZm9sbG93aW5nIG5vdGljZXMgdG8gdGhlIGxpYnJhcnkuICBJdCBpcwpzYWZlc3QgdG8gYXR0YWNoIHRoZW0gdG8gdGhlIHN0YXJ0IG9mIGVhY2ggc291cmNlIGZpbGUgdG8gbW9zdCBlZmZlY3RpdmVseQpjb252ZXkgdGhlIGV4Y2x1c2lvbiBvZiB3YXJyYW50eTsgYW5kIGVhY2ggZmlsZSBzaG91bGQgaGF2ZSBhdCBsZWFzdCB0aGUKImNvcHlyaWdodCIgbGluZSBhbmQgYSBwb2ludGVyIHRvIHdoZXJlIHRoZSBmdWxsIG5vdGljZSBpcyBmb3VuZC4KCiAgICA8b25lIGxpbmUgdG8gZ2l2ZSB0aGUgbGlicmFyeSdzIG5hbWUgYW5kIGEgYnJpZWYgaWRlYSBvZiB3aGF0IGl0IGRvZXMuPgogICAgQ29weXJpZ2h0IChDKSA8eWVhcj4gIDxuYW1lIG9mIGF1dGhvcj4KCiAgICBUaGlzIGxpYnJhcnkgaXMgZnJlZSBzb2Z0d2FyZTsgeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yCiAgICBtb2RpZnkgaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgTGVzc2VyIEdlbmVyYWwgUHVibGljCiAgICBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uOyBlaXRoZXIKICAgIHZlcnNpb24gMi4xIG9mIHRoZSBMaWNlbnNlLCBvciAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLgoKICAgIFRoaXMgbGlicmFyeSBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLAogICAgYnV0IFdJVEhPVVQgQU5ZIFdBUlJBTlRZOyB3aXRob3V0IGV2ZW4gdGhlIGltcGxpZWQgd2FycmFudHkgb2YKICAgIE1FUkNIQU5UQUJJTElUWSBvciBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUgR05VCiAgICBMZXNzZXIgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLgoKICAgIFlvdSBzaG91bGQgaGF2ZSByZWNlaXZlZCBhIGNvcHkgb2YgdGhlIEdOVSBMZXNzZXIgR2VuZXJhbCBQdWJsaWMKICAgIExpY2Vuc2UgYWxvbmcgd2l0aCB0aGlzIGxpYnJhcnk7IGlmIG5vdCwgc2VlIDxodHRwczovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uCgpBbHNvIGFkZCBpbmZvcm1hdGlvbiBvbiBob3cgdG8gY29udGFjdCB5b3UgYnkgZWxlY3Ryb25pYyBhbmQgcGFwZXIgbWFpbC4KCllvdSBzaG91bGQgYWxzbyBnZXQgeW91ciBlbXBsb3llciAoaWYgeW91IHdvcmsgYXMgYSBwcm9ncmFtbWVyKSBvciB5b3VyCnNjaG9vbCwgaWYgYW55LCB0byBzaWduIGEgImNvcHlyaWdodCBkaXNjbGFpbWVyIiBmb3IgdGhlIGxpYnJhcnksIGlmCm5lY2Vzc2FyeS4gIEhlcmUgaXMgYSBzYW1wbGU7IGFsdGVyIHRoZSBuYW1lczoKCiAgWW95b2R5bmUsIEluYy4sIGhlcmVieSBkaXNjbGFpbXMgYWxsIGNvcHlyaWdodCBpbnRlcmVzdCBpbiB0aGUKICBsaWJyYXJ5IGBGcm9iJyAoYSBsaWJyYXJ5IGZvciB0d2Vha2luZyBrbm9icykgd3JpdHRlbiBieSBKYW1lcyBSYW5kb20gSGFja2VyLgoKICA8c2lnbmF0dXJlIG9mIE1vZSBHaG91bD4sIDEgQXByaWwgMTk5MAogIE1vZSBHaG91bCwgUHJlc2lkZW50IG9mIFZpY2UKClRoYXQncyBhbGwgdGhlcmUgaXMgdG8gaXQhCg==", "5d4873884a890122a4b9b20ad56ac6f7da1d796a5bfcf04a427970ac96217626": "RXhjZXB0IHdoZXJlIG90aGVyd2lzZSBub3RlZCBpbiB0aGUgc291cmNlIGNvZGUgKGUuZy4gdGhlIGZpbGVzIGRpY3QuYyBhbmQKbGlzdC5jLCB3aGljaCBhcmUgY292ZXJlZCBieSBhIHNpbWlsYXIgbGljZW5jZSBidXQgd2l0aCBkaWZmZXJlbnQgQ29weXJpZ2h0Cm5vdGljZXMpIGFsbCB0aGUgZmlsZXMgYXJlOgoKIENvcHlyaWdodCAoQykgMTk5OC0yMDEyIERhbmllbCBWZWlsbGFyZC4gIEFsbCBSaWdodHMgUmVzZXJ2ZWQuCiBDb3B5cmlnaHQgKEMpIFRoZSBMaWJ4bWwyIENvbnRyaWJ1dG9ycy4KClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcyBmdXItCm5pc2hlZCB0byBkbyBzbywgc3ViamVjdCB0byB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnM6CgpUaGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBzaGFsbCBiZSBpbmNsdWRlZCBpbgphbGwgY29waWVzIG9yIHN1YnN0YW50aWFsIHBvcnRpb25zIG9mIHRoZSBTb2Z0d2FyZS4KClRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCAiQVMgSVMiLCBXSVRIT1VUIFdBUlJBTlRZIE9GIEFOWSBLSU5ELCBFWFBSRVNTIE9SCklNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZLCBGSVQtCk5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuICBJTiBOTyBFVkVOVCBTSEFMTCBUSEUKQVVUSE9SUyBPUiBDT1BZUklHSFQgSE9MREVSUyBCRSBMSUFCTEUgRk9SIEFOWSBDTEFJTSwgREFNQUdFUyBPUiBPVEhFUgpMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SIE9USEVSV0lTRSwgQVJJU0lORyBGUk9NLApPVVQgT0YgT1IgSU4gQ09OTkVDVElPTiBXSVRIIFRIRSBTT0ZUV0FSRSBPUiBUSEUgVVNFIE9SIE9USEVSIERFQUxJTkdTIElOClRIRSBTT0ZUV0FSRS4K", "6a8f30793e877d32e3f88b972f0970a051a3b5a26cd057d3993cb51e21c43319": "CkFsbCBzb3VyY2UsIGRhdGEgZmlsZXMgYW5kIG90aGVyIGNvbnRlbnRzIG9mIHRoZSBQUk9KIHBhY2thZ2UgYXJlIAphdmFpbGFibGUgdW5kZXIgdGhlIGZvbGxvd2luZyB0ZXJtcy4gIE5vdGUgdGhhdCB0aGUgUFJPSiA0LjMgYW5kIGVhcmxpZXIKd2FzICJwdWJsaWMgZG9tYWluIiBhcyBpcyBjb21tb24gd2l0aCBVUyBnb3Zlcm5tZW50IHdvcmssIGJ1dCBhcHBhcmVudGx5CnRoaXMgaXMgbm90IGEgd2VsbCBkZWZpbmVkIGxlZ2FsIHRlcm0gaW4gbWFueSBjb3VudHJpZXMuIEZyYW5rIFdhcm1lcmRhbSBwbGFjZWQKZXZlcnl0aGluZyB1bmRlciB0aGUgZm9sbG93aW5nIE1JVCBzdHlsZSBsaWNlbnNlIGJlY2F1c2UgaGUgYmVsaWV2ZWQgaXQgaXMKZWZmZWN0aXZlbHkgdGhlIHNhbWUgYXMgcHVibGljIGRvbWFpbiwgYWxsb3dpbmcgYW55b25lIHRvIHVzZSB0aGUgY29kZSBhcwp0aGV5IHdpc2gsIGluY2x1ZGluZyBtYWtpbmcgcHJvcHJpZXRhcnkgZGVyaXZhdGl2ZXMuCgpJbml0aWFsIFBST0ogNC4zIHB1YmxpYyBkb21haW4gY29kZSB3YXMgcHV0IGFzIEZyYW5rIFdhcm1lcmRhbSBhcyBjb3B5cmlnaHQKaG9sZGVyLCBidXQgaGUgZGlkbid0IG1lYW4gdG8gaW1wbHkgaGUgZGlkIHRoZSB3b3JrLiBFc3NlbnRpYWxseSBhbGwgd29yayB3YXMKZG9uZSBieSBHZXJhbGQgRXZlbmRlbi4KCkNvcHlyaWdodCBpbmZvcm1hdGlvbiBjYW4gYmUgZm91bmQgaW4gc291cmNlIGZpbGVzLgoKIC0tLS0tLS0tLS0tLS0tCgogUGVybWlzc2lvbiBpcyBoZXJlYnkgZ3JhbnRlZCwgZnJlZSBvZiBjaGFyZ2UsIHRvIGFueSBwZXJzb24gb2J0YWluaW5nIGEKIGNvcHkgb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksCiB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uCiB0aGUgcmlnaHRzIHRvIHVzZSwgY29weSwgbW9kaWZ5LCBtZXJnZSwgcHVibGlzaCwgZGlzdHJpYnV0ZSwgc3VibGljZW5zZSwKIGFuZC9vciBzZWxsIGNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZQogU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKCiBUaGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBzaGFsbCBiZSBpbmNsdWRlZAogaW4gYWxsIGNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgogVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MKIE9SIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZLAogRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwKIFRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SIE9USEVSV0lTRSwgQVJJU0lORwogRlJPTSwgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUgogREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLgo=", diff --git a/src/extensions/generated/docs/extension-evidence.json b/src/extensions/generated/docs/extension-evidence.json index 7eec97f1b..b5896e7ad 100644 --- a/src/extensions/generated/docs/extension-evidence.json +++ b/src/extensions/generated/docs/extension-evidence.json @@ -1362,6 +1362,7 @@ "src/extensions/external/pg_textsearch/tests/upgrade/source.toml", "src/extensions/external/pg_uuidv7/source.toml", "src/extensions/external/pgtap/source.toml", + "src/extensions/external/postgis/dependencies/gdal/source.toml", "src/extensions/external/postgis/dependencies/geos/source.toml", "src/extensions/external/postgis/dependencies/json-c/source.toml", "src/extensions/external/postgis/dependencies/libiconv/source.toml", diff --git a/src/extensions/generated/extensions.catalog.json b/src/extensions/generated/extensions.catalog.json index 24b8230d0..65675d0bc 100644 --- a/src/extensions/generated/extensions.catalog.json +++ b/src/extensions/generated/extensions.catalog.json @@ -1172,7 +1172,7 @@ "bundle-size": 8551161, "control-file": "target/oliphaunt-sources/checkouts/postgis/extensions/postgis/postgis.control.in", "control": { - "default-version": "3.6.3", + "default-version": "3.6.4", "module-pathname": "@MODULEPATH@", "requires": [], "relocatable": "false" diff --git a/src/extensions/generated/mobile/static-extensions.tsv b/src/extensions/generated/mobile/static-extensions.tsv index 18310e567..137a250ad 100644 --- a/src/extensions/generated/mobile/static-extensions.tsv +++ b/src/extensions/generated/mobile/static-extensions.tsv @@ -29,7 +29,7 @@ pg_uuidv7 pg_uuidv7 external target/oliphaunt-sources/checkouts/pg_uuidv7 pg_visibility pg_visibility contrib contrib/pg_visibility pg_walinspect pg_walinspect contrib contrib/pg_walinspect pgcrypto pgcrypto contrib contrib/pgcrypto openssl openssl openssl openssl openssl openssl openssl -postgis postgis-3 external target/oliphaunt-sources/checkouts/postgis geos,geos-c,json-c,libcharset,libiconv,libxml2,proj,sqlite geos,geos-c,json-c,libxml2,proj,sqlite geos,geos-c,json-c,libcharset,libiconv,libxml2,proj,sqlite geos,json-c,libiconv,libxml2,proj,sqlite geos,json-c,libiconv,libxml2,proj,sqlite geos,json-c,libxml2,proj,sqlite geos,json-c,libiconv,libxml2,proj,sqlite +postgis postgis-3 external target/oliphaunt-sources/checkouts/postgis gdal,geos,geos-c,json-c,libcharset,libiconv,libxml2,proj,sqlite gdal,geos,geos-c,json-c,libxml2,proj,sqlite gdal,geos,geos-c,json-c,libcharset,libiconv,libxml2,proj,sqlite gdal,geos,json-c,libiconv,libxml2,proj,sqlite gdal,geos,json-c,libiconv,libxml2,proj,sqlite gdal,geos,json-c,libxml2,proj,sqlite gdal,geos,json-c,libiconv,libxml2,proj,sqlite seg seg contrib contrib/seg tablefunc tablefunc contrib contrib/tablefunc tcn tcn contrib contrib/tcn diff --git a/src/extensions/generated/mobile/static-registry.json b/src/extensions/generated/mobile/static-registry.json index 2a4cdcf26..b271c7fbe 100644 --- a/src/extensions/generated/mobile/static-registry.json +++ b/src/extensions/generated/mobile/static-registry.json @@ -226,6 +226,32 @@ "share/postgresql/contrib/postgis-3.6/spatial_ref_sys.sql", "share/postgresql/contrib/postgis-3.6/uninstall_legacy.sql", "share/postgresql/contrib/postgis-3.6/uninstall_postgis.sql", + "share/postgresql/gdal/GDALLogoBW.svg", + "share/postgresql/gdal/GDALLogoColor.svg", + "share/postgresql/gdal/GDALLogoGS.svg", + "share/postgresql/gdal/LICENSE.TXT", + "share/postgresql/gdal/cubewerx_extra.wkt", + "share/postgresql/gdal/ecw_cs.wkt", + "share/postgresql/gdal/epsg.wkt", + "share/postgresql/gdal/esri_StatePlane_extra.wkt", + "share/postgresql/gdal/gdal_algorithm.schema.json", + "share/postgresql/gdal/gdalicon.png", + "share/postgresql/gdal/gdalinfo_output.schema.json", + "share/postgresql/gdal/gdalmdiminfo_output.schema.json", + "share/postgresql/gdal/gdalvrt.xsd", + "share/postgresql/gdal/leaflet_template.html", + "share/postgresql/gdal/ogr_fields_override.schema.json", + "share/postgresql/gdal/ogrinfo_output.schema.json", + "share/postgresql/gdal/ozi_datum.csv", + "share/postgresql/gdal/ozi_ellips.csv", + "share/postgresql/gdal/pci_datum.txt", + "share/postgresql/gdal/pci_ellips.txt", + "share/postgresql/gdal/stateplane.csv", + "share/postgresql/gdal/template_tiles.mapml", + "share/postgresql/gdal/tms_LINZAntarticaMapTileGrid.json", + "share/postgresql/gdal/tms_MapML_APSTILE.json", + "share/postgresql/gdal/tms_MapML_CBMTILE.json", + "share/postgresql/gdal/tms_NZTM2000.json", "share/postgresql/proj/proj.db" ], "id": "postgis", diff --git a/src/extensions/generated/sdk/extensions.json b/src/extensions/generated/sdk/extensions.json index c8a047409..0bc985f91 100644 --- a/src/extensions/generated/sdk/extensions.json +++ b/src/extensions/generated/sdk/extensions.json @@ -1,5 +1,5 @@ { - "extension-catalog-sha256": "c1d2e09905d7ecc0172173b34b9e4104dad58987503dd3dab7af4ae78890d9f3", + "extension-catalog-sha256": "26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a", "extensions": [ { "artifact-product": "oliphaunt-extension-contrib-pg18", @@ -910,6 +910,32 @@ "share/postgresql/contrib/postgis-3.6/spatial_ref_sys.sql", "share/postgresql/contrib/postgis-3.6/uninstall_legacy.sql", "share/postgresql/contrib/postgis-3.6/uninstall_postgis.sql", + "share/postgresql/gdal/GDALLogoBW.svg", + "share/postgresql/gdal/GDALLogoColor.svg", + "share/postgresql/gdal/GDALLogoGS.svg", + "share/postgresql/gdal/LICENSE.TXT", + "share/postgresql/gdal/cubewerx_extra.wkt", + "share/postgresql/gdal/ecw_cs.wkt", + "share/postgresql/gdal/epsg.wkt", + "share/postgresql/gdal/esri_StatePlane_extra.wkt", + "share/postgresql/gdal/gdal_algorithm.schema.json", + "share/postgresql/gdal/gdalicon.png", + "share/postgresql/gdal/gdalinfo_output.schema.json", + "share/postgresql/gdal/gdalmdiminfo_output.schema.json", + "share/postgresql/gdal/gdalvrt.xsd", + "share/postgresql/gdal/leaflet_template.html", + "share/postgresql/gdal/ogr_fields_override.schema.json", + "share/postgresql/gdal/ogrinfo_output.schema.json", + "share/postgresql/gdal/ozi_datum.csv", + "share/postgresql/gdal/ozi_ellips.csv", + "share/postgresql/gdal/pci_datum.txt", + "share/postgresql/gdal/pci_ellips.txt", + "share/postgresql/gdal/stateplane.csv", + "share/postgresql/gdal/template_tiles.mapml", + "share/postgresql/gdal/tms_LINZAntarticaMapTileGrid.json", + "share/postgresql/gdal/tms_MapML_APSTILE.json", + "share/postgresql/gdal/tms_MapML_CBMTILE.json", + "share/postgresql/gdal/tms_NZTM2000.json", "share/postgresql/proj/proj.db" ], "dependencies": [], @@ -920,6 +946,7 @@ "extension-sql-file-prefixes": [ "postgis_comments", "postgis_proc_set_search_path", + "postgis_raster", "rtpostgis" ], "id": "postgis", @@ -932,11 +959,13 @@ "sqlite", "proj", "libxml2", - "json-c" + "json-c", + "gdal" ], "family": "native", "kind": "native-dynamic", "link-units": [ + "gdal", "geos-c", "geos", "proj", @@ -945,6 +974,7 @@ "json-c" ], "runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "target": "linux-arm64-gnu" @@ -955,11 +985,13 @@ "sqlite", "proj", "libxml2", - "json-c" + "json-c", + "gdal" ], "family": "native", "kind": "native-dynamic", "link-units": [ + "gdal", "geos-c", "geos", "proj", @@ -968,6 +1000,7 @@ "json-c" ], "runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "target": "linux-x64-gnu" @@ -978,11 +1011,13 @@ "sqlite", "proj", "libxml2", - "json-c" + "json-c", + "gdal" ], "family": "native", "kind": "native-dynamic", "link-units": [ + "gdal", "geos-c", "geos", "proj", @@ -991,6 +1026,7 @@ "json-c" ], "runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "target": "macos-arm64" @@ -1001,11 +1037,13 @@ "sqlite", "proj", "libxml2", - "json-c" + "json-c", + "gdal" ], "family": "native", "kind": "native-dynamic", "link-units": [ + "gdal", "geos-c", "geos", "proj", @@ -1014,58 +1052,65 @@ "json-c" ], "runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "target": "windows-x64-msvc" }, { "components": [ + "libiconv", "geos", "sqlite", "proj", "libxml2", "json-c", - "libiconv" + "gdal" ], "family": "native", "kind": "native-static-registry", "link-units": [ + "libiconv", + "libcharset", + "gdal", "geos-c", "geos", "proj", "sqlite", "libxml2", - "json-c", - "libiconv", - "libcharset" + "json-c" ], "runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "target": "android-arm64-v8a" }, { "components": [ + "libiconv", "geos", "sqlite", "proj", "libxml2", "json-c", - "libiconv" + "gdal" ], "family": "native", "kind": "native-static-registry", "link-units": [ + "libiconv", + "libcharset", + "gdal", "geos-c", "geos", "proj", "sqlite", "libxml2", - "json-c", - "libiconv", - "libcharset" + "json-c" ], "runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "target": "android-x86_64" @@ -1076,11 +1121,13 @@ "sqlite", "proj", "libxml2", - "json-c" + "json-c", + "gdal" ], "family": "native", "kind": "native-static-registry", "link-units": [ + "gdal", "geos-c", "geos", "proj", @@ -1089,38 +1136,43 @@ "json-c" ], "runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "target": "ios-xcframework" }, { "components": [ + "libiconv", "geos", "sqlite", "proj", "libxml2", "json-c", - "libiconv" + "gdal" ], "family": "wasix", "kind": "wasix-runtime", "link-units": [ + "libiconv", + "libcharset", + "gdal", "geos-c", "geos", "proj", "sqlite", "libxml2", - "json-c", - "libiconv", - "libcharset" + "json-c" ], "runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "target": "wasix-portable" } ], "native-components": [ + "gdal", "geos", "json-c", "libiconv", @@ -1134,6 +1186,11 @@ "release-product": "oliphaunt-extension-postgis", "runtime-bound": false, "runtime-environment": [ + { + "name": "GDAL_DATA", + "path": "share/postgresql/gdal", + "required_file": "gdalvrt.xsd" + }, { "name": "PROJ_DATA", "path": "share/postgresql/proj", @@ -1149,6 +1206,32 @@ "contrib/postgis-3.6/spatial_ref_sys.sql", "contrib/postgis-3.6/uninstall_legacy.sql", "contrib/postgis-3.6/uninstall_postgis.sql", + "gdal/GDALLogoBW.svg", + "gdal/GDALLogoColor.svg", + "gdal/GDALLogoGS.svg", + "gdal/LICENSE.TXT", + "gdal/cubewerx_extra.wkt", + "gdal/ecw_cs.wkt", + "gdal/epsg.wkt", + "gdal/esri_StatePlane_extra.wkt", + "gdal/gdal_algorithm.schema.json", + "gdal/gdalicon.png", + "gdal/gdalinfo_output.schema.json", + "gdal/gdalmdiminfo_output.schema.json", + "gdal/gdalvrt.xsd", + "gdal/leaflet_template.html", + "gdal/ogr_fields_override.schema.json", + "gdal/ogrinfo_output.schema.json", + "gdal/ozi_datum.csv", + "gdal/ozi_ellips.csv", + "gdal/pci_datum.txt", + "gdal/pci_ellips.txt", + "gdal/stateplane.csv", + "gdal/template_tiles.mapml", + "gdal/tms_LINZAntarticaMapTileGrid.json", + "gdal/tms_MapML_APSTILE.json", + "gdal/tms_MapML_CBMTILE.json", + "gdal/tms_NZTM2000.json", "proj/proj.db" ], "selected-extension-dependencies": [], diff --git a/src/extensions/generated/sdk/ios-static-dependencies.json b/src/extensions/generated/sdk/ios-static-dependencies.json index c5e2909f1..7e37d65e4 100644 --- a/src/extensions/generated/sdk/ios-static-dependencies.json +++ b/src/extensions/generated/sdk/ios-static-dependencies.json @@ -9,6 +9,7 @@ { "sql-name": "postgis", "static-dependencies": [ + "gdal", "geos", "geos-c", "json-c", diff --git a/src/extensions/generated/wasix/extensions.json b/src/extensions/generated/wasix/extensions.json index 8103c602e..9b95e1b0f 100644 --- a/src/extensions/generated/wasix/extensions.json +++ b/src/extensions/generated/wasix/extensions.json @@ -690,25 +690,28 @@ "lib/postgresql/postgis-3.so" ], "native-components": [ + "libiconv", "geos", "sqlite", "proj", "libxml2", "json-c", - "libiconv" + "gdal" ], "native-link-units": [ + "libiconv", + "libcharset", + "gdal", "geos-c", "geos", "proj", "sqlite", "libxml2", - "json-c", - "libiconv", - "libcharset" + "json-c" ], "native-module-file": "postgis-3.so", "native-runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "native-support-modules": [ @@ -717,6 +720,12 @@ "build-path": "postgis/postgis/liboliphaunt_postgis_deps.so", "name": "postgis_deps", "runtime-path": "lib/postgresql/liboliphaunt_postgis_deps.so" + }, + { + "aot-file": "postgis_raster-llvm-opta.bin.zst", + "build-path": "postgis/raster/rt_pg/postgis_raster-3.so", + "name": "postgis_raster", + "runtime-path": "lib/postgresql/postgis_raster-3.so" } ], "sql-name": "postgis" diff --git a/src/extensions/tools/extension-upstream-licenses.test.mts b/src/extensions/tools/extension-upstream-licenses.test.mts index e412a37a2..04a716efd 100644 --- a/src/extensions/tools/extension-upstream-licenses.test.mts +++ b/src/extensions/tools/extension-upstream-licenses.test.mts @@ -135,7 +135,7 @@ test("the contract retains dependency licenses and pgtap's complete upstream gra const postgis = rows.find((row) => row.sqlName === 'postgis'); assert.deepEqual( [...new Set(postgis.files.map((file) => file.checkout))], - ['geos', 'json-c', 'libiconv', 'libxml2', 'postgis', 'proj', 'sqlite'], + ['gdal', 'geos', 'json-c', 'libiconv', 'libxml2', 'postgis', 'proj', 'sqlite'], ); assert.deepEqual( postgis.files.filter((file) => file.checkout === 'libiconv').map((file) => file.path), @@ -231,6 +231,7 @@ test('carrier legal roles derive exact contrib and external payload closure', () assert.equal(postgis.profile, 'external-native'); assert.deepEqual(postgis.upstreamMembers, ['postgis']); assert.deepEqual(postgis.licenseFiles, [ + 'share/licenses/gdal/LICENSE.TXT', 'share/licenses/geos/COPYING', 'share/licenses/geos/src/deps/ryu/LICENSE', 'share/licenses/geos/src/deps/ryu/LICENSE-Apache2', diff --git a/src/extensions/tools/native-component-contract.test.mts b/src/extensions/tools/native-component-contract.test.mts index 4fd4beda5..0861301d6 100644 --- a/src/extensions/tools/native-component-contract.test.mts +++ b/src/extensions/tools/native-component-contract.test.mts @@ -15,9 +15,17 @@ function resolve(extension, family, kind, target) { test('PROJ brings SQLite and its runtime database into every PostGIS closure', () => { const ios = resolve('postgis', 'native', 'native-static-registry', 'ios-xcframework'); - assert.deepEqual(ios.components, ['geos', 'sqlite', 'proj', 'libxml2', 'json-c']); - assert.deepEqual(ios.runtimeFiles, ['proj/proj.db']); - assert.deepEqual(ios.linkUnits, ['geos-c', 'geos', 'proj', 'sqlite', 'libxml2', 'json-c']); + assert.deepEqual(ios.components, ['geos', 'sqlite', 'proj', 'libxml2', 'json-c', 'gdal']); + assert.deepEqual(ios.runtimeFiles, ['gdal/gdalvrt.xsd', 'proj/proj.db']); + assert.deepEqual(ios.linkUnits, [ + 'gdal', + 'geos-c', + 'geos', + 'proj', + 'sqlite', + 'libxml2', + 'json-c', + ]); }); test('PostGIS target differences are explicit', () => { @@ -25,9 +33,10 @@ test('PostGIS target differences are explicit', () => { const android = resolve('postgis', 'native', 'native-static-registry', 'android-arm64-v8a'); const wasix = resolve('postgis', 'wasix', 'wasix-runtime', 'wasix-portable'); assert.equal(ios.components.includes('libiconv'), false); - assert.deepEqual(android.components.slice(-1), ['libiconv']); + assert(android.components.indexOf('libiconv') < android.components.indexOf('gdal')); + assert.equal(android.components.at(-1), 'gdal'); assert.deepEqual(wasix.components, android.components); - assert.deepEqual(android.linkUnits.slice(-2), ['libiconv', 'libcharset']); + assert.deepEqual(android.linkUnits.slice(0, 2), ['libiconv', 'libcharset']); }); test('contrib native dependencies share the same cross-platform contract', () => { diff --git a/src/native/runtime/bin/build-postgres18-linux.sh b/src/native/runtime/bin/build-postgres18-linux.sh index daf31cd91..79606457a 100755 --- a/src/native/runtime/bin/build-postgres18-linux.sh +++ b/src/native/runtime/bin/build-postgres18-linux.sh @@ -154,6 +154,7 @@ required_extension_controls=( pg_ivm pgcrypto postgis + postgis_raster pg_surgery pg_textsearch pg_trgm @@ -196,6 +197,7 @@ required_extension_modules=( pg_ivm pgcrypto postgis-3 + postgis_raster-3 pg_surgery pg_textsearch pg_trgm @@ -260,6 +262,8 @@ filter_native_extension_selection() { postgis) selected_external+=(postgis) module_file="postgis-3.so" + selected_controls+=(postgis_raster) + selected_modules+=(postgis_raster-3) ;; *) external_id="$(awk -F '\t' -v sql="$sql" 'NR > 1 && $2 == sql { print $1; found = 1; exit } END { exit found ? 0 : 1 }' "$pgxs_plan" || true)" @@ -448,9 +452,17 @@ hash_extension_source_tree() { -name "meson_options.txt" \ \) -print | LC_ALL=C sort | - while IFS= read -r file; do - shasum -a 256 "$file" - done + ( + set -- + while IFS= read -r file; do + set -- "$@" "$file" + if [ "$#" -ge 256 ]; then + shasum -a 256 "$@" + set -- + fi + done + [ "$#" -eq 0 ] || shasum -a 256 "$@" + ) } exact_checkout_identity() { @@ -1165,6 +1177,7 @@ build_pgxs_extension() { native_postgis_dependency_root="${OLIPHAUNT_NATIVE_POSTGIS_DEPENDENCY_ROOT:-$work_root/postgis-native-dependencies}" native_postgis_dependency_build_roots=( + "$work_root/gdal-native-build" "$work_root/json-c-native-build" "$work_root/sqlite-native-build" "$work_root/geos-native-build" @@ -1172,6 +1185,9 @@ native_postgis_dependency_build_roots=( "$work_root/proj-native-build" ) native_postgis_dependency_required_outputs=( + "$native_postgis_dependency_root/gdal/bin/gdal-config" + "$native_postgis_dependency_root/gdal/include/gdal.h" + "$native_postgis_dependency_root/gdal/lib/libgdal.a" "$native_postgis_dependency_root/json-c/lib/libjson-c.a" "$native_postgis_dependency_root/sqlite/lib/libsqlite3.a" "$native_postgis_dependency_root/geos/lib/libgeos_c.a" @@ -1366,6 +1382,62 @@ build_native_postgis_proj_dependency() { [ -f "$dependency_dir/share/proj/proj.db" ] || fail "PROJ build did not produce proj.db" } +build_native_postgis_gdal_dependency() { + local source_dir="$repo_root/target/oliphaunt-sources/checkouts/gdal" + local dependency_dir="$native_postgis_dependency_root/gdal" + local geos_dir="$native_postgis_dependency_root/geos" + local jsonc_dir="$native_postgis_dependency_root/json-c" + local libxml2_dir="$native_postgis_dependency_root/libxml2" + local proj_dir="$native_postgis_dependency_root/proj" + local sqlite_dir="$native_postgis_dependency_root/sqlite" + local build_root="$work_root/gdal-native-build" + local archive="$dependency_dir/lib/libgdal.a" + [ -f "$archive" ] && [ -f "$dependency_dir/include/gdal.h" ] && [ -x "$dependency_dir/bin/gdal-config" ] && return 0 + [ -f "$source_dir/CMakeLists.txt" ] || fail "missing GDAL checkout: $source_dir" + [ -f "$proj_dir/lib/libproj.a" ] || fail "GDAL dependency requires PROJ first" + rm -rf "$build_root" "$dependency_dir" + native_postgis_cmake_install "$source_dir" "$build_root" "$dependency_dir" \ + -DCMAKE_BUILD_TYPE=Release \ + -DBASH_COMPLETIONS_DIR= \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_APPS=OFF \ + -DBUILD_CSHARP_BINDINGS=OFF \ + -DBUILD_JAVA_BINDINGS=OFF \ + -DBUILD_PYTHON_BINDINGS=OFF \ + -DBUILD_TESTING=OFF \ + -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF \ + -DGDAL_OBJECT_LIBRARIES_POSITION_INDEPENDENT_CODE=ON \ + -DOGR_BUILD_OPTIONAL_DRIVERS=OFF \ + -DGDAL_ENABLE_DRIVER_GTIFF=ON \ + -DGDAL_ENABLE_DRIVER_VRT=ON \ + -DOGR_ENABLE_DRIVER_GEOJSON=ON \ + -DOGR_ENABLE_DRIVER_SHAPE=ON \ + -DGDAL_USE_EXTERNAL_LIBS=OFF \ + -DGDAL_USE_INTERNAL_LIBS=ON \ + -DGDAL_USE_OPENMP=OFF \ + -DGDAL_USE_CURL=OFF \ + -DGDAL_USE_OPENSSL=OFF \ + -DGDAL_USE_GEOS=ON \ + "-DGEOS_INCLUDE_DIR=$geos_dir/include" \ + "-DGEOS_LIBRARY=$geos_dir/lib/libgeos_c.a" \ + -DGDAL_USE_JSONC=ON \ + -DGDAL_USE_JSONC_INTERNAL=OFF \ + "-DJSONC_INCLUDE_DIR=$jsonc_dir/include/json-c" \ + "-DJSONC_LIBRARY=$jsonc_dir/lib/libjson-c.a" \ + -DGDAL_USE_LIBXML2=ON \ + "-DLIBXML2_INCLUDE_DIR=$libxml2_dir/include/libxml2" \ + "-DLIBXML2_LIBRARY=$libxml2_dir/lib/libxml2.a" \ + -DGDAL_USE_PROJ=ON \ + "-DPROJ_INCLUDE_DIR=$proj_dir/include" \ + "-DPROJ_LIBRARY=$proj_dir/lib/libproj.a" \ + -DGDAL_USE_SQLITE3=ON \ + "-DSQLite3_INCLUDE_DIR=$sqlite_dir/include" \ + "-DSQLite3_LIBRARY=$sqlite_dir/lib/libsqlite3.a" \ + -DACCEPT_MISSING_SQLITE3_MUTEX_ALLOC=ON + [ -f "$archive" ] || fail "GDAL build did not produce $archive" + [ -x "$dependency_dir/bin/gdal-config" ] || fail "GDAL build did not produce gdal-config" +} + build_native_postgis_dependencies() { native_postgis_require_tools local wanted @@ -1384,6 +1456,7 @@ build_native_postgis_dependencies() { proj) build_native_postgis_proj_dependency ;; libxml2) build_native_postgis_libxml2_dependency ;; json-c) build_native_postgis_jsonc_dependency ;; + gdal) build_native_postgis_gdal_dependency ;; "") ;; *) fail "unsupported native PostGIS component for $target_id: $component" ;; esac @@ -1402,13 +1475,31 @@ set -eu case "\${1:-}" in --clibs|--libs) echo "-L$native_postgis_dependency_root/geos/lib -lgeos_c -lgeos -lstdc++ -lm" ;; --cflags) echo "-I$native_postgis_dependency_root/geos/include" ;; - --version) echo "3.14.0dev" ;; + --version) echo "3.15.0" ;; *) exec "$native_postgis_dependency_root/geos/bin/geos-config" "\$@" ;; esac EOF chmod +x "$path" } +native_postgis_gdal_config_script() { + local path="$1" + local real_config="$native_postgis_dependency_root/gdal/bin/gdal-config" + local real_libs + real_libs="$("$real_config" --libs)" + cat > "$path" < "$path" <> "$make_log" 2>&1 make -C doc CC="$cc_string" "${postgis_make_args[@]}" comments-install >> "$make_log" 2>&1 make -j"$jobs" -C postgis CC="$cc_string" "${postgis_make_args[@]}" install >> "$make_log" 2>&1 + make -j"$jobs" -C raster/rt_core CC="$cc_string" "${postgis_make_args[@]}" all >> "$make_log" 2>&1 + make -j"$jobs" -C raster/rt_pg CC="$cc_string" BE_DLLLIBS="$normal_module_be_dllibs" "${postgis_make_args[@]}" install >> "$make_log" 2>&1 make -j1 -C extensions CC="$cc_string" "${postgis_make_args[@]}" all >> "$make_log" 2>&1 make -j1 -C extensions CC="$cc_string" "${postgis_make_args[@]}" install >> "$make_log" 2>&1 make -C postgis clean >> "$make_log" 2>&1 || true make postgis_revision.h >> "$make_log" 2>&1 make -j"$jobs" -C postgis CC="$cc_string" CFLAGS="$native_cflags" BE_DLLLIBS="$embedded_module_be_dllibs" "${postgis_make_args[@]}" all >> "$make_log" 2>&1 + make -C raster/rt_pg clean >> "$make_log" 2>&1 || true + make -j"$jobs" -C raster/rt_pg CC="$cc_string" CFLAGS="$native_cflags" BE_DLLLIBS="$embedded_module_be_dllibs" "${postgis_make_args[@]}" all >> "$make_log" 2>&1 ) - copy_embedded_postgis_module "$postgis_build_dir/postgis" + copy_embedded_postgis_module "$postgis_build_dir/postgis" postgis-3 + copy_embedded_postgis_module "$postgis_build_dir/raster/rt_pg" postgis_raster-3 stage_postgis_data_files "$postgis_build_dir" } @@ -1713,7 +1816,8 @@ case "$script_mode" in configure_source install_runtime prune_base_runtime_optional_extensions - if artifact_ready && + if [ "${OLIPHAUNT_FORCE_EXTENSION_REBUILD:-0}" != "1" ] && + artifact_ready && (cd "$build_dir" && backend_objects_ready && plpgsql_objects_ready && jit_objects_ready) && [ -f "$stamp" ] && [ "$(cat "$stamp")" = "$(desired_hash)" ]; then diff --git a/src/native/runtime/bin/build-postgres18-macos.sh b/src/native/runtime/bin/build-postgres18-macos.sh index f067404ba..91c667f77 100755 --- a/src/native/runtime/bin/build-postgres18-macos.sh +++ b/src/native/runtime/bin/build-postgres18-macos.sh @@ -138,6 +138,7 @@ required_extension_controls=( pg_ivm pgcrypto postgis + postgis_raster pg_surgery pg_textsearch pg_trgm @@ -180,6 +181,7 @@ required_extension_modules=( pg_ivm pgcrypto postgis-3 + postgis_raster-3 pg_surgery pg_textsearch pg_trgm @@ -244,6 +246,8 @@ filter_native_extension_selection() { postgis) selected_external+=(postgis) module_file="postgis-3.so" + selected_controls+=(postgis_raster) + selected_modules+=(postgis_raster-3) ;; *) external_id="$(awk -F '\t' -v sql="$sql" 'NR > 1 && $2 == sql { print $1; found = 1; exit } END { exit found ? 0 : 1 }' "$pgxs_plan" || true)" @@ -846,9 +850,17 @@ hash_extension_source_tree() { -name "meson_options.txt" \ \) -print | LC_ALL=C sort | - while IFS= read -r file; do - shasum -a 256 "$file" - done + ( + set -- + while IFS= read -r file; do + set -- "$@" "$file" + if [ "$#" -ge 256 ]; then + shasum -a 256 "$@" + set -- + fi + done + [ "$#" -eq 0 ] || shasum -a 256 "$@" + ) } native_extension_component_field() { @@ -1405,6 +1417,7 @@ audit_packaged_extension_modules() { native_postgis_dependency_root="${OLIPHAUNT_NATIVE_POSTGIS_DEPENDENCY_ROOT:-$work_root/postgis-native-dependencies}" native_postgis_dependency_build_roots=( + "$work_root/gdal-native-build" "$work_root/json-c-native-build" "$work_root/sqlite-native-build" "$work_root/geos-native-build" @@ -1412,6 +1425,9 @@ native_postgis_dependency_build_roots=( "$work_root/proj-native-build" ) native_postgis_dependency_required_outputs=( + "$native_postgis_dependency_root/gdal/bin/gdal-config" + "$native_postgis_dependency_root/gdal/include/gdal.h" + "$native_postgis_dependency_root/gdal/lib/libgdal.a" "$native_postgis_dependency_root/json-c/lib/libjson-c.a" "$native_postgis_dependency_root/sqlite/lib/libsqlite3.a" "$native_postgis_dependency_root/geos/lib/libgeos_c.a" @@ -1625,6 +1641,66 @@ build_native_postgis_proj_dependency() { native_postgis_dependency_archive proj "$archive" } +build_native_postgis_gdal_dependency() { + local source_dir="$repo_root/target/oliphaunt-sources/checkouts/gdal" + local dependency_dir="$native_postgis_dependency_root/gdal" + local geos_dir="$native_postgis_dependency_root/geos" + local jsonc_dir="$native_postgis_dependency_root/json-c" + local libxml2_dir="$native_postgis_dependency_root/libxml2" + local proj_dir="$native_postgis_dependency_root/proj" + local sqlite_dir="$native_postgis_dependency_root/sqlite" + local build_root="$work_root/gdal-native-build" + local archive="$dependency_dir/lib/libgdal.a" + if [ -f "$archive" ] && [ -f "$dependency_dir/include/gdal.h" ] && [ -x "$dependency_dir/bin/gdal-config" ]; then + native_postgis_dependency_archive gdal "$archive" + return 0 + fi + [ -f "$source_dir/CMakeLists.txt" ] || native_postgis_fail "missing GDAL checkout: $source_dir" + [ -f "$proj_dir/lib/libproj.a" ] || native_postgis_fail "GDAL dependency requires PROJ first" + rm -rf "$build_root" "$dependency_dir" + native_postgis_cmake_install "$source_dir" "$build_root" "$dependency_dir" \ + -DCMAKE_BUILD_TYPE=Release \ + -DBASH_COMPLETIONS_DIR= \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_APPS=OFF \ + -DBUILD_CSHARP_BINDINGS=OFF \ + -DBUILD_JAVA_BINDINGS=OFF \ + -DBUILD_PYTHON_BINDINGS=OFF \ + -DBUILD_TESTING=OFF \ + -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF \ + -DGDAL_OBJECT_LIBRARIES_POSITION_INDEPENDENT_CODE=ON \ + -DOGR_BUILD_OPTIONAL_DRIVERS=OFF \ + -DGDAL_ENABLE_DRIVER_GTIFF=ON \ + -DGDAL_ENABLE_DRIVER_VRT=ON \ + -DOGR_ENABLE_DRIVER_GEOJSON=ON \ + -DOGR_ENABLE_DRIVER_SHAPE=ON \ + -DGDAL_USE_EXTERNAL_LIBS=OFF \ + -DGDAL_USE_INTERNAL_LIBS=ON \ + -DGDAL_USE_OPENMP=OFF \ + -DGDAL_USE_CURL=OFF \ + -DGDAL_USE_OPENSSL=OFF \ + -DGDAL_USE_GEOS=ON \ + "-DGEOS_INCLUDE_DIR=$geos_dir/include" \ + "-DGEOS_LIBRARY=$geos_dir/lib/libgeos_c.a" \ + -DGDAL_USE_JSONC=ON \ + -DGDAL_USE_JSONC_INTERNAL=OFF \ + "-DJSONC_INCLUDE_DIR=$jsonc_dir/include/json-c" \ + "-DJSONC_LIBRARY=$jsonc_dir/lib/libjson-c.a" \ + -DGDAL_USE_LIBXML2=ON \ + "-DLIBXML2_INCLUDE_DIR=$libxml2_dir/include/libxml2" \ + "-DLIBXML2_LIBRARY=$libxml2_dir/lib/libxml2.a" \ + -DGDAL_USE_PROJ=ON \ + "-DPROJ_INCLUDE_DIR=$proj_dir/include" \ + "-DPROJ_LIBRARY=$proj_dir/lib/libproj.a" \ + -DGDAL_USE_SQLITE3=ON \ + "-DSQLite3_INCLUDE_DIR=$sqlite_dir/include" \ + "-DSQLite3_LIBRARY=$sqlite_dir/lib/libsqlite3.a" \ + -DACCEPT_MISSING_SQLITE3_MUTEX_ALLOC=ON + [ -f "$archive" ] || native_postgis_fail "GDAL build did not produce $archive" + [ -x "$dependency_dir/bin/gdal-config" ] || native_postgis_fail "GDAL build did not produce gdal-config" + native_postgis_dependency_archive gdal "$archive" +} + build_native_postgis_dependencies() { native_postgis_require_tools local wanted @@ -1643,6 +1719,7 @@ build_native_postgis_dependencies() { proj) build_native_postgis_proj_dependency ;; libxml2) build_native_postgis_libxml2_dependency ;; json-c) build_native_postgis_jsonc_dependency ;; + gdal) build_native_postgis_gdal_dependency ;; "") ;; *) native_postgis_fail "unsupported native PostGIS component for macos-arm64: $component" ;; esac @@ -1666,7 +1743,7 @@ case "\${1:-}" in echo "-I$native_postgis_dependency_root/geos/include" ;; --version) - echo "3.14.0dev" + echo "3.15.0" ;; *) exec "$native_postgis_dependency_root/geos/bin/geos-config" "\$@" @@ -1676,6 +1753,24 @@ EOF chmod +x "$path" } +native_postgis_gdal_config_script() { + local path="$1" + local real_config="$native_postgis_dependency_root/gdal/bin/gdal-config" + local real_libs + real_libs="$("$real_config" --libs)" + cat > "$path" < "$path" <&2 + cp -p "$candidate" "$embedded_modules_dir/$stem.dylib" + if ! module_depends_on_liboliphaunt "$embedded_modules_dir/$stem.dylib"; then + echo "embedded PostGIS is not linked against liboliphaunt: $embedded_modules_dir/$stem.dylib" >&2 exit 1 fi return @@ -2052,6 +2150,11 @@ stage_postgis_data_files() { fi mkdir -p "$install_dir/share/postgresql/proj" cp -p "$proj_db" "$install_dir/share/postgresql/proj/proj.db" + local gdal_data="$native_postgis_dependency_root/gdal/share/gdal" + [ -f "$gdal_data/gdalvrt.xsd" ] || native_postgis_fail "PostGIS requires the pinned GDAL runtime data" + rm -rf "$install_dir/share/postgresql/gdal" + mkdir -p "$install_dir/share/postgresql/gdal" + cp -a "$gdal_data/." "$install_dir/share/postgresql/gdal/" } patch_postgis_generated_makefiles() { @@ -2114,7 +2217,6 @@ build_postgis_extension() { --with-pgconfig="$install_dir/bin/pg_config" \ "${postgis_configure_args[@]}" \ --without-protobuf \ - --without-raster \ --without-topology \ --without-sfcgal \ --without-address-standardizer \ @@ -2130,17 +2232,23 @@ build_postgis_extension() { make postgis_revision.h make -C doc CC="$postgis_cc_string" "${postgis_make_args[@]}" comments-install make -j"$jobs" -C postgis CC="$postgis_cc_string" BE_DLLLIBS="$normal_module_be_dllibs" "${postgis_make_args[@]}" install - # PostGIS extension SQL generation has shared raster helper outputs even - # when raster support is disabled, so keep this packaging phase serial. + make -j"$jobs" -C raster/rt_core CC="$postgis_cc_string" "${postgis_make_args[@]}" all + make -j"$jobs" -C raster/rt_pg CC="$postgis_cc_string" BE_DLLLIBS="$normal_module_be_dllibs" "${postgis_make_args[@]}" install + # PostGIS extension SQL generation shares raster helper outputs, so keep + # this packaging phase serial. make -j1 -C extensions CC="$postgis_cc_string" "${postgis_make_args[@]}" all make -j1 -C extensions CC="$postgis_cc_string" "${postgis_make_args[@]}" install make -C postgis clean || true make postgis_revision.h make -j"$jobs" -C postgis CC="$postgis_cc_string" CFLAGS="$native_cflags" BE_DLLLIBS="$embedded_module_be_dllibs" "${embedded_postgis_make_args[@]}" all + make -C raster/rt_pg clean || true + make -j"$jobs" -C raster/rt_pg CC="$postgis_cc_string" CFLAGS="$native_cflags" BE_DLLLIBS="$embedded_module_be_dllibs" "${embedded_postgis_make_args[@]}" all ) normalize_installed_module_suffix postgis-3 - copy_embedded_postgis_module "$postgis_build_dir/postgis" + normalize_installed_module_suffix postgis_raster-3 + copy_embedded_postgis_module "$postgis_build_dir/postgis" postgis-3 + copy_embedded_postgis_module "$postgis_build_dir/raster/rt_pg" postgis_raster-3 stage_postgis_data_files "$postgis_build_dir" } diff --git a/src/native/runtime/bin/mobile-postgis-extensions.sh b/src/native/runtime/bin/mobile-postgis-extensions.sh index e0ef77666..b257aaee1 100644 --- a/src/native/runtime/bin/mobile-postgis-extensions.sh +++ b/src/native/runtime/bin/mobile-postgis-extensions.sh @@ -43,23 +43,27 @@ oliphaunt_postgis_output_fingerprint() { local alias_file="$4" local archive="$5" local control_file="$install_dir/share/postgresql/extension/postgis.control" + local raster_control_file="$install_dir/share/postgresql/extension/postgis_raster.control" local proj_database="$install_dir/share/postgresql/proj/proj.db" - local sql_source_root="$postgis_build/extensions/postgis/sql" + local gdal_schema="$install_dir/share/postgresql/gdal/gdalvrt.xsd" + local sql_source_root local object sql_source installed_sql dependency local sql_count=0 local -a outputs - outputs=("$archive" "$objects_file" "$alias_file" "$control_file" "$proj_database") + outputs=("$archive" "$objects_file" "$alias_file" "$control_file" "$raster_control_file" "$proj_database" "$gdal_schema") while IFS= read -r object; do [ -n "$object" ] || continue outputs+=("$object") done < "$objects_file" - while IFS= read -r sql_source; do - [ -n "$sql_source" ] || continue - installed_sql="$install_dir/share/postgresql/extension/$(basename "$sql_source")" - outputs+=("$installed_sql") - sql_count=$((sql_count + 1)) - done < <(find "$sql_source_root" -maxdepth 1 -type f -name '*.sql' -print 2>/dev/null | LC_ALL=C sort) + for sql_source_root in "$postgis_build/extensions/postgis/sql" "$postgis_build/extensions/postgis_raster/sql"; do + while IFS= read -r sql_source; do + [ -n "$sql_source" ] || continue + installed_sql="$install_dir/share/postgresql/extension/$(basename "$sql_source")" + outputs+=("$installed_sql") + sql_count=$((sql_count + 1)) + done < <(find "$sql_source_root" -maxdepth 1 -type f -name '*.sql' -print 2>/dev/null | LC_ALL=C sort) + done while IFS= read -r dependency; do [ -n "$dependency" ] || continue outputs+=("$dependency") @@ -300,6 +304,83 @@ build_postgis_proj_dependency() { oliphaunt_postgis_dependency_archive proj "$archive" } +build_postgis_gdal_dependency() { + oliphaunt_postgis_selected || return 0 + local source_dir="$repo_root/target/oliphaunt-sources/checkouts/gdal" + local dependency_dir="$mobile_static_dependency_root/gdal" + local geos_dir="$mobile_static_dependency_root/geos" + local jsonc_dir="$mobile_static_dependency_root/json-c" + local libxml2_dir="$mobile_static_dependency_root/libxml2" + local proj_dir="$mobile_static_dependency_root/proj" + local sqlite_dir="$mobile_static_dependency_root/sqlite" + local -a cmake_args + local build_root="$work_root/gdal-$oliphaunt_mobile_target-build" + local archive="$dependency_dir/lib/libgdal.a" + if [ -f "$archive" ] && [ -f "$dependency_dir/include/gdal.h" ] && [ -x "$dependency_dir/bin/gdal-config" ]; then + oliphaunt_postgis_dependency_archive gdal "$archive" + return 0 + fi + [ -f "$source_dir/CMakeLists.txt" ] || oliphaunt_postgis_fail "missing GDAL checkout: $source_dir" + [ -f "$proj_dir/lib/libproj.a" ] || oliphaunt_postgis_fail "GDAL dependency requires PROJ first" + cmake_args=( + -DCMAKE_BUILD_TYPE=Release + -DBASH_COMPLETIONS_DIR= + -DBUILD_SHARED_LIBS=OFF + -DBUILD_APPS=OFF + -DBUILD_CSHARP_BINDINGS=OFF + -DBUILD_JAVA_BINDINGS=OFF + -DBUILD_PYTHON_BINDINGS=OFF + -DBUILD_PYTHON_BINDINGS_OLD_VAL=OFF + -DBUILD_TESTING=OFF + -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF + -DGDAL_OBJECT_LIBRARIES_POSITION_INDEPENDENT_CODE=ON + -DOGR_BUILD_OPTIONAL_DRIVERS=OFF + -DGDAL_ENABLE_DRIVER_GTIFF=ON + -DGDAL_ENABLE_DRIVER_VRT=ON + -DOGR_ENABLE_DRIVER_GEOJSON=ON + -DOGR_ENABLE_DRIVER_SHAPE=ON + -DGDAL_USE_EXTERNAL_LIBS=OFF + -DGDAL_USE_INTERNAL_LIBS=ON + -DGDAL_USE_OPENMP=OFF + -DGDAL_USE_CURL=OFF + -DGDAL_USE_OPENSSL=OFF + -DGDAL_USE_GEOS=ON + "-DGEOS_INCLUDE_DIR=$geos_dir/include" + "-DGEOS_LIBRARY=$geos_dir/lib/libgeos_c.a" + -DGDAL_USE_JSONC=ON + -DGDAL_USE_JSONC_INTERNAL=OFF + "-DJSONC_INCLUDE_DIR=$jsonc_dir/include/json-c" + "-DJSONC_LIBRARY=$jsonc_dir/lib/libjson-c.a" + -DGDAL_USE_LIBXML2=ON + "-DLIBXML2_INCLUDE_DIR=$libxml2_dir/include/libxml2" + "-DLIBXML2_LIBRARY=$libxml2_dir/lib/libxml2.a" + -DGDAL_USE_PROJ=ON + "-DPROJ_INCLUDE_DIR=$proj_dir/include" + "-DPROJ_LIBRARY=$proj_dir/lib/libproj.a" + -DGDAL_USE_SQLITE3=ON + "-DSQLite3_INCLUDE_DIR=$sqlite_dir/include" + "-DSQLite3_LIBRARY=$sqlite_dir/lib/libsqlite3.a" + -DACCEPT_MISSING_SQLITE3_MUTEX_ALLOC=ON + ) + case "$oliphaunt_mobile_target" in + android-arm64 | android-x86_64) + [ -f "$mobile_static_dependency_root/libiconv/lib/libiconv.a" ] || + oliphaunt_postgis_fail "GDAL dependency requires libiconv first on Android" + cmake_args+=( + -DGDAL_USE_ICONV=ON + "-DIconv_INCLUDE_DIR=$mobile_static_dependency_root/libiconv/include" + "-DIconv_LIBRARY=$mobile_static_dependency_root/libiconv/lib/libiconv.a" + ) + ;; + esac + rm -rf "$build_root" "$dependency_dir" + oliphaunt_postgis_cmake_install "$source_dir" "$build_root" "$dependency_dir" \ + "${cmake_args[@]}" + [ -f "$archive" ] || oliphaunt_postgis_fail "GDAL build did not produce $archive" + [ -x "$dependency_dir/bin/gdal-config" ] || oliphaunt_postgis_fail "GDAL build did not produce gdal-config" + oliphaunt_postgis_dependency_archive gdal "$archive" +} + build_postgis_libiconv_dependency() { oliphaunt_postgis_selected || return 0 case "$oliphaunt_mobile_target" in @@ -354,6 +435,7 @@ build_postgis_mobile_static_dependencies() { libxml2) build_postgis_libxml2_dependency ;; json-c) build_postgis_jsonc_dependency ;; libiconv) build_postgis_libiconv_dependency ;; + gdal) build_postgis_gdal_dependency ;; "") ;; *) oliphaunt_postgis_fail "unsupported native component in contract: $component" ;; esac @@ -374,10 +456,10 @@ oliphaunt_postgis_host_alias() { oliphaunt_postgis_extra_ldflags() { case "$oliphaunt_mobile_target" in ios-simulator | ios-device | macos-arm64) - printf '%s\n' "-isysroot $sdk_path -L$mobile_static_dependency_root/geos/lib -L$mobile_static_dependency_root/proj/lib -L$mobile_static_dependency_root/sqlite/lib -L$mobile_static_dependency_root/json-c/lib -L$mobile_static_dependency_root/libxml2/lib -lc++" + printf '%s\n' "-isysroot $sdk_path -L$mobile_static_dependency_root/gdal/lib -L$mobile_static_dependency_root/geos/lib -L$mobile_static_dependency_root/proj/lib -L$mobile_static_dependency_root/sqlite/lib -L$mobile_static_dependency_root/json-c/lib -L$mobile_static_dependency_root/libxml2/lib -lc++" ;; android-arm64 | android-x86_64) - printf '%s\n' "-L$mobile_static_dependency_root/geos/lib -L$mobile_static_dependency_root/proj/lib -L$mobile_static_dependency_root/sqlite/lib -L$mobile_static_dependency_root/json-c/lib -L$mobile_static_dependency_root/libxml2/lib -L$mobile_static_dependency_root/libiconv/lib -L$toolchain_dir/sysroot/usr/lib/$android_host -lc++_static -lc++abi" + printf '%s\n' "-L$mobile_static_dependency_root/gdal/lib -L$mobile_static_dependency_root/geos/lib -L$mobile_static_dependency_root/proj/lib -L$mobile_static_dependency_root/sqlite/lib -L$mobile_static_dependency_root/json-c/lib -L$mobile_static_dependency_root/libxml2/lib -L$mobile_static_dependency_root/libiconv/lib -L$toolchain_dir/sysroot/usr/lib/$android_host -lc++_static -lc++abi" ;; esac } @@ -389,6 +471,18 @@ oliphaunt_postgis_geos_config_libs() { esac } +oliphaunt_postgis_gdal_config_libs() { + local base="-L$mobile_static_dependency_root/gdal/lib -lgdal -L$mobile_static_dependency_root/geos/lib -lgeos_c -lgeos -L$mobile_static_dependency_root/proj/lib -lproj -L$mobile_static_dependency_root/sqlite/lib -lsqlite3 -L$mobile_static_dependency_root/json-c/lib -ljson-c -L$mobile_static_dependency_root/libxml2/lib -lxml2" + case "$oliphaunt_mobile_target" in + ios-simulator | ios-device | macos-arm64) + printf '%s\n' "$base -lc++" + ;; + android-arm64 | android-x86_64) + printf '%s\n' "$base -L$mobile_static_dependency_root/libiconv/lib -liconv -lcharset -lc++_static -lc++abi" + ;; + esac +} + oliphaunt_postgis_pkg_config_script() { local path="$1" local proj_cxx_libs @@ -410,7 +504,7 @@ case "\$pkg" in proj) case " \$* " in *" --exists "*) exit 0 ;; - *" --modversion "*) echo "9.8.1"; exit 0 ;; + *" --modversion "*) echo "9.9.0"; exit 0 ;; *" --cflags "*) echo "-I$mobile_static_dependency_root/proj/include"; exit 0 ;; *" --libs "*) echo "-L$mobile_static_dependency_root/proj/lib -lproj -L$mobile_static_dependency_root/sqlite/lib -lsqlite3 $proj_cxx_libs"; exit 0 ;; esac @@ -487,7 +581,7 @@ case "\${1:-}" in echo "-I$mobile_static_dependency_root/geos/include" ;; --version) - echo "3.14.0dev" + echo "3.15.0" ;; *) exec "$mobile_static_dependency_root/geos/bin/geos-config" "\$@" @@ -497,6 +591,24 @@ EOF chmod +x "$path" } +oliphaunt_postgis_gdal_config_script() { + local path="$1" + local gdal_libs + gdal_libs="$(oliphaunt_postgis_gdal_config_libs)" + cat > "$path" <&2; exit 1 ;; +esac +EOF + chmod +x "$path" +} + oliphaunt_postgis_copy_archive_objects() { local archive="$1" local name="$2" @@ -534,13 +646,26 @@ oliphaunt_postgis_stage_object() { oliphaunt_postgis_stage_runtime_sql() { local postgis_build="$1" - mkdir -p "$install_dir/share/postgresql/extension" "$install_dir/share/postgresql/proj" + local gdal_data="$mobile_static_dependency_root/gdal/share/gdal" + [ -f "$gdal_data/gdalvrt.xsd" ] || oliphaunt_postgis_fail "PostGIS requires the pinned GDAL runtime data" + mkdir -p \ + "$install_dir/share/postgresql/extension" \ + "$install_dir/share/postgresql/proj" \ + "$install_dir/share/postgresql/gdal" cp -p "$postgis_build/extensions/postgis/postgis.control" \ "$install_dir/share/postgresql/extension/postgis.control" cp -p "$postgis_build/extensions/postgis/sql/"*.sql \ "$install_dir/share/postgresql/extension/" + cp -p "$postgis_build/extensions/postgis_raster/postgis_raster.control" \ + "$install_dir/share/postgresql/extension/postgis_raster.control" + cp -p "$postgis_build/extensions/postgis_raster/sql/"*.sql \ + "$install_dir/share/postgresql/extension/" + perl -0pi -e 's|\$libdir/postgis_raster-3(?:\.6)?|\$libdir/postgis-3|g' \ + "$install_dir/share/postgresql/extension/postgis_raster.control" \ + "$install_dir/share/postgresql/extension/postgis_raster--"*.sql cp -p "$mobile_static_dependency_root/proj/share/proj/proj.db" \ "$install_dir/share/postgresql/proj/proj.db" + cp -a "$gdal_data/." "$install_dir/share/postgresql/gdal/" } oliphaunt_postgis_patch_extension_makefile() { @@ -549,13 +674,15 @@ oliphaunt_postgis_patch_extension_makefile() { local makefile="$postgis_build/postgis/Makefile" [ -f "$makefile" ] || oliphaunt_postgis_fail "PostGIS extension Makefile is missing: $makefile" OLIPHAUNT_POSTGIS_PG_MAGIC_SYMBOL="${prefix}_Pg_magic_func" \ - OLIPHAUNT_POSTGIS_PG_INIT_SYMBOL="${prefix}__PG_init" \ + OLIPHAUNT_POSTGIS_PG_INIT_SYMBOL="${prefix}_core_PG_init" \ + OLIPHAUNT_POSTGIS_PG_FINI_SYMBOL="${prefix}_core_PG_fini" \ OLIPHAUNT_POSTGIS_DIFFERENCE_SYMBOL="${prefix}_difference" \ OLIPHAUNT_POSTGIS_PG_FINFO_DIFFERENCE_SYMBOL="${prefix}_pg_finfo_difference" \ perl -0pi -e ' my $defs = " -DPg_magic_func=$ENV{OLIPHAUNT_POSTGIS_PG_MAGIC_SYMBOL}" . " -D_PG_init=$ENV{OLIPHAUNT_POSTGIS_PG_INIT_SYMBOL}" . + " -D_PG_fini=$ENV{OLIPHAUNT_POSTGIS_PG_FINI_SYMBOL}" . " -Ddifference=$ENV{OLIPHAUNT_POSTGIS_DIFFERENCE_SYMBOL}" . " -Dpg_finfo_difference=$ENV{OLIPHAUNT_POSTGIS_PG_FINFO_DIFFERENCE_SYMBOL}"; my $updated = s|^(PG_CPPFLAGS \+= .*)$|$1$defs|m; @@ -563,6 +690,43 @@ oliphaunt_postgis_patch_extension_makefile() { ' "$makefile" } +oliphaunt_postgis_patch_raster_makefile() { + local postgis_build="$1" + local prefix="$2" + local makefile="$postgis_build/raster/rt_pg/Makefile" + [ -f "$makefile" ] || oliphaunt_postgis_fail "PostGIS raster Makefile is missing: $makefile" + OLIPHAUNT_POSTGIS_RASTER_MAGIC_SYMBOL="${prefix}_raster_Pg_magic_func" \ + OLIPHAUNT_POSTGIS_RASTER_INIT_SYMBOL="${prefix}_raster_PG_init" \ + OLIPHAUNT_POSTGIS_RASTER_FINI_SYMBOL="${prefix}_raster_PG_fini" \ + perl -0pi -e ' + my $defs = + " -DPg_magic_func=$ENV{OLIPHAUNT_POSTGIS_RASTER_MAGIC_SYMBOL}" . + " -D_PG_init=$ENV{OLIPHAUNT_POSTGIS_RASTER_INIT_SYMBOL}" . + " -D_PG_fini=$ENV{OLIPHAUNT_POSTGIS_RASTER_FINI_SYMBOL}"; + die "PostGIS raster PG_CPPFLAGS is missing\n" unless /^PG_CPPFLAGS \+=/m; + my $updated = s|^(SHLIB_LINK_F =)|PG_CPPFLAGS +=$defs\n$1|m; + die "could not patch PostGIS raster PG_CPPFLAGS\n" unless $updated; + ' "$makefile" +} + +oliphaunt_postgis_build_combined_init() { + local postgis_build="$1" + local prefix="$2" + local object_dir="$3" + local source="$object_dir/oliphaunt_postgis_combined_init.c" + local object="$object_dir/oliphaunt_postgis_combined_init.o" + cat > "$source" <> "$make_log" 2>&1 + printf '%s\n' "$object" +} + oliphaunt_postgis_write_static_symbol_aliases() { local path="$1" local prefix="$2" @@ -591,6 +755,18 @@ oliphaunt_postgis_verify_prefixed_legacy_symbols() { fi } +oliphaunt_postgis_build_raster_objects() { + local postgis_build="$1" + # Static carriers need every object, without attempting a shared-module link. + printf '\n.PHONY: oliphaunt-static-objects\noliphaunt-static-objects: $(OBJS)\n' >> \ + "$postgis_build/raster/rt_pg/Makefile" + if ! make -j"$jobs" -C "$postgis_build/raster/rt_pg" oliphaunt-static-objects >> "$make_log" 2>&1; then + tail -n 200 "$make_log" >&2 || true + oliphaunt_postgis_fail "PostGIS raster object compilation failed" + fi + make -j1 -C "$postgis_build/raster/rt_pg" sql_objs >> "$make_log" 2>&1 +} + build_postgis_mobile_static_extension_objects() { local extension="$1" [ "$extension" = "postgis" ] || return 1 @@ -612,6 +788,7 @@ build_postgis_mobile_static_extension_objects() { if [ -f "$archive" ] && [ -s "$objects_file" ] && [ -s "$alias_file" ] && [ -f "$install_dir/share/postgresql/extension/postgis.control" ] && + [ -f "$install_dir/share/postgresql/extension/postgis_raster.control" ] && [ -s "$build_stamp" ] && [ "$(cat "$build_stamp")" = "$build_fingerprint" ] && [ -s "$output_stamp" ] && output_fingerprint="$(oliphaunt_postgis_output_fingerprint "$postgis_build" "$object_dir" "$objects_file" "$alias_file" "$archive")" && @@ -632,19 +809,21 @@ build_postgis_mobile_static_extension_objects() { chmod +x "$work_root/postgis-fake-postgres-bin/postgres" rsync -a --delete --exclude .git "$source_dir/" "$postgis_build/" - local pg_config geos_config pkg_config host_alias ldflags + local pg_config geos_config gdal_config pkg_config host_alias ldflags pg_config="$scripts_dir/pg_config" geos_config="$scripts_dir/geos-config" + gdal_config="$scripts_dir/gdal-config" pkg_config="$scripts_dir/pkg-config" host_alias="$(oliphaunt_postgis_host_alias)" ldflags="$(oliphaunt_postgis_extra_ldflags)" oliphaunt_postgis_pg_config_script "$pg_config" oliphaunt_postgis_geos_config_script "$geos_config" + oliphaunt_postgis_gdal_config_script "$gdal_config" oliphaunt_postgis_pkg_config_script "$pkg_config" local postgis_cflags postgis_cppflags postgis_cflags="$native_cflags" - postgis_cppflags="-I$build_dir/src/include -I$build_dir/src/include/port -I$build_dir/src/interfaces/libpq -I$mobile_static_dependency_root/libxml2/include/libxml2 -I$mobile_static_dependency_root/proj/include -I$mobile_static_dependency_root/json-c/include -I$mobile_static_dependency_root/json-c/include/json-c" + postgis_cppflags="-I$build_dir/src/include -I$build_dir/src/include/port -I$build_dir/src/interfaces/libpq -I$mobile_static_dependency_root/gdal/include -I$mobile_static_dependency_root/libxml2/include/libxml2 -I$mobile_static_dependency_root/proj/include -I$mobile_static_dependency_root/json-c/include -I$mobile_static_dependency_root/json-c/include/json-c" case "$oliphaunt_mobile_target" in android-arm64 | android-x86_64) postgis_cflags="$postgis_cflags -D_GNU_SOURCE" @@ -657,9 +836,9 @@ build_postgis_mobile_static_extension_objects() { --host="$host_alias" --with-pgconfig="$pg_config" --with-geosconfig="$geos_config" + --with-gdalconfig="$gdal_config" --with-xml2config="$mobile_static_dependency_root/libxml2/bin/xml2-config" --without-protobuf - --without-raster --without-topology --without-sfcgal --without-address-standardizer @@ -699,14 +878,15 @@ build_postgis_mobile_static_extension_objects() { build_alias="$(build-aux/config.guess)" ./configure --build="$build_alias" "${configure_args[@]}" >> "$make_log" 2>&1 oliphaunt_postgis_patch_extension_makefile "$postgis_build" "$prefix" + oliphaunt_postgis_patch_raster_makefile "$postgis_build" "$prefix" make -j"$jobs" -C liblwgeom liblwgeom.la >> "$make_log" 2>&1 make -j"$jobs" -C libpgcommon libpgcommon.a >> "$make_log" 2>&1 make -j"$jobs" -C deps/flatgeobuf all >> "$make_log" 2>&1 make -j"$jobs" -C postgis all >> "$make_log" 2>&1 || true - make -j1 raster-sql >> "$make_log" 2>&1 || true - make -j1 -C raster/rt_pg sql_objs >> "$make_log" 2>&1 + make -j"$jobs" -C raster/rt_core all >> "$make_log" 2>&1 + oliphaunt_postgis_build_raster_objects "$postgis_build" make -j1 -C extensions postgis_extension_helper.sql >> "$make_log" 2>&1 - make -j1 -C extensions/postgis postgis.control all >> "$make_log" 2>&1 + make -j1 -C extensions all >> "$make_log" 2>&1 ) : > "$objects_file" @@ -716,6 +896,10 @@ build_postgis_mobile_static_extension_objects() { [ -n "$object" ] || continue oliphaunt_postgis_stage_object "$object" postgis "$object_dir" "$objects_file" done < <(find "$postgis_build/postgis" -maxdepth 1 -type f -name '*.o' -print | LC_ALL=C sort) + while IFS= read -r object; do + [ -n "$object" ] || continue + oliphaunt_postgis_stage_object "$object" raster "$object_dir" "$objects_file" + done < <(find "$postgis_build/raster/rt_pg" -maxdepth 1 -type f -name '*.o' -print | LC_ALL=C sort) for object in \ "$postgis_build/deps/flatgeobuf/flatgeobuf_c.o" \ "$postgis_build/deps/flatgeobuf/geometrywriter.o" \ @@ -727,6 +911,10 @@ build_postgis_mobile_static_extension_objects() { done oliphaunt_postgis_copy_archive_objects "$postgis_build/liblwgeom/.libs/liblwgeom.a" liblwgeom "$object_dir" "$objects_file" oliphaunt_postgis_copy_archive_objects "$postgis_build/libpgcommon/libpgcommon.a" libpgcommon "$object_dir" "$objects_file" + oliphaunt_postgis_copy_archive_objects "$postgis_build/raster/rt_core/librtcore.a" rtcore "$object_dir" "$objects_file" + object="$(oliphaunt_postgis_build_combined_init "$postgis_build" "$prefix" "$object_dir")" + printf '%s\n' "$object" >> "$objects_file" + mobile_static_objects+=("$object") [ -s "$objects_file" ] || oliphaunt_postgis_fail "PostGIS did not produce object inputs" oliphaunt_postgis_verify_prefixed_module_symbol "$stem" "$prefix" oliphaunt_postgis_verify_prefixed_legacy_symbols "$stem" "$prefix" diff --git a/src/native/runtime/bin/mobile-postgis-extensions.test.sh b/src/native/runtime/bin/mobile-postgis-extensions.test.sh new file mode 100755 index 000000000..c2543bc43 --- /dev/null +++ b/src/native/runtime/bin/mobile-postgis-extensions.test.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +# shellcheck source=/dev/null +. "$script_dir/mobile-static-extensions.sh" +# shellcheck source=/dev/null +. "$script_dir/mobile-postgis-extensions.sh" + +test_root="$(mktemp -d "${TMPDIR:-/tmp}/oliphaunt-mobile-postgis-test.XXXXXX")" +trap 'rm -rf "$test_root"' EXIT + +repo_root="$test_root/repo" +mobile_static_dependency_root="$test_root/dependencies" +work_root="$test_root/work" +oliphaunt_mobile_target="ios-simulator" +mobile_static_dependency_archives=() + +mkdir -p \ + "$repo_root/target/oliphaunt-sources/checkouts/gdal" \ + "$mobile_static_dependency_root/proj/lib" +: > "$repo_root/target/oliphaunt-sources/checkouts/gdal/CMakeLists.txt" +: > "$mobile_static_dependency_root/proj/lib/libproj.a" + +oliphaunt_postgis_selected() { + return 0 +} + +cmake_args_file="$test_root/cmake-args.txt" +oliphaunt_postgis_cmake_install() { + local dependency_dir="$3" + shift 3 + printf '%s\n' "$@" > "$cmake_args_file" + mkdir -p "$dependency_dir/lib" "$dependency_dir/include" "$dependency_dir/bin" + : > "$dependency_dir/lib/libgdal.a" + : > "$dependency_dir/include/gdal.h" + printf '#!/usr/bin/env bash\nexit 0\n' > "$dependency_dir/bin/gdal-config" + chmod +x "$dependency_dir/bin/gdal-config" +} + +build_postgis_gdal_dependency +grep -Fx -- '-DCMAKE_BUILD_TYPE=Release' "$cmake_args_file" >/dev/null +grep -Fx -- '-DBUILD_PYTHON_BINDINGS=OFF' "$cmake_args_file" >/dev/null +grep -Fx -- '-DBUILD_PYTHON_BINDINGS_OLD_VAL=OFF' "$cmake_args_file" >/dev/null +resolved_gdal_archive="$(oliphaunt_mobile_static_dependency_archive_for_root "$mobile_static_dependency_root" gdal)" +[ "$resolved_gdal_archive" = "$mobile_static_dependency_root/gdal/lib/libgdal.a" ] || { + echo "mobile packager did not resolve the staged GDAL archive" >&2 + exit 1 +} + +postgis_build="$test_root/postgis-build" +mkdir -p "$postgis_build/raster/rt_pg" +cat > "$postgis_build/raster/rt_pg/Makefile" <<'MAKEFILE' +PG_CPPFLAGS += \ + -Ifixture-one \ + -Ifixture-two +SHLIB_LINK_F = -lfixture + +.PHONY: all print-flags sql_objs +OBJS = rtpostgis.o rtpg_gdal.o +all: + @exit 1 + +$(OBJS): + @: > $@ + +sql_objs: + @: > rtpostgis.sql + +print-flags: + @printf '%s\n' '$(PG_CPPFLAGS)' +MAKEFILE + +prefix="oliphaunt_static_postgis_3" +oliphaunt_postgis_patch_raster_makefile "$postgis_build" "$prefix" +raster_flags="$(make -s -f "$postgis_build/raster/rt_pg/Makefile" print-flags)" +for expected in \ + "-DPg_magic_func=${prefix}_raster_Pg_magic_func" \ + "-D_PG_init=${prefix}_raster_PG_init" \ + "-D_PG_fini=${prefix}_raster_PG_fini"; do + case " $raster_flags " in + *" $expected "*) ;; + *) + echo "missing raster compile definition: $expected" >&2 + exit 1 + ;; + esac +done + +jobs=1 +make_log="$test_root/raster-make.log" +oliphaunt_postgis_build_raster_objects "$postgis_build" +[ -f "$postgis_build/raster/rt_pg/rtpostgis.o" ] && +[ -f "$postgis_build/raster/rt_pg/rtpg_gdal.o" ] && +[ -f "$postgis_build/raster/rt_pg/rtpostgis.sql" ] || { + echo "mobile raster build did not produce every object and SQL input" >&2 + exit 1 +} +cat >> "$postgis_build/raster/rt_pg/Makefile" <<'MAKEFILE' +oliphaunt-static-objects: failed-object.o +failed-object.o: + @exit 1 +MAKEFILE +if (oliphaunt_postgis_build_raster_objects "$postgis_build") > "$test_root/failure.log" 2>&1; then + echo "mobile raster build accepted a partial object set" >&2 + exit 1 +fi + +echo "mobile PostGIS build contracts passed" diff --git a/src/native/runtime/bin/postgis-reproducible-time.test.sh b/src/native/runtime/bin/postgis-reproducible-time.test.sh index 4701eb274..043af61e1 100755 --- a/src/native/runtime/bin/postgis-reproducible-time.test.sh +++ b/src/native/runtime/bin/postgis-reproducible-time.test.sh @@ -4,8 +4,8 @@ set -euo pipefail root="$(cd "$(git rev-parse --show-toplevel)" && pwd -P)" helper="$root/src/extensions/external/postgis/tools/reproducible-time.sh" shim="$root/src/extensions/external/postgis/tools/reproducible-bin/date" -expected_epoch=1776193981 -expected_utc='2026-04-14 19:13:01' +expected_epoch=1780943336 +expected_utc='2026-06-08 18:28:56' test_root="$(mktemp -d "${TMPDIR:-/tmp}/oliphaunt-postgis-time-test.XXXXXX")" original_path="$PATH" cleanup() { @@ -37,14 +37,14 @@ fi grep -Fq 'exactly one canonical source_date_epoch' "$test_root/missing.err" || fail "missing-key error was not actionable" -printf 'source_date_epoch = "1776193981"\n' > "$fake_manifest" +printf 'source_date_epoch = "1780943336"\n' > "$fake_manifest" if oliphaunt_postgis_source_date_epoch "$fake_root" >"$test_root/quoted.out" 2>"$test_root/quoted.err"; then fail "quoted source_date_epoch was accepted" fi grep -Fq 'canonical positive integer' "$test_root/quoted.err" || fail "noncanonical-value error was not actionable" -printf 'source_date_epoch = 1776193981\nsource_date_epoch = 1776193981\n' > "$fake_manifest" +printf 'source_date_epoch = 1780943336\nsource_date_epoch = 1780943336\n' > "$fake_manifest" if oliphaunt_postgis_source_date_epoch "$fake_root" >"$test_root/duplicate.out" 2>"$test_root/duplicate.err"; then fail "duplicate source_date_epoch was accepted" fi @@ -76,7 +76,7 @@ export SOURCE_DATE_EPOCH=333 oliphaunt_postgis_enable_reproducible_time "$root" [ "$SOURCE_DATE_EPOCH" = "$expected_epoch" ] || fail "enable helper retained ambient SOURCE_DATE_EPOCH" -[ "$(command -v date)" = "$shim" ] || +oliphaunt_postgis_same_executable "$(command -v date)" "$shim" || fail "portable date shim was not placed first on PATH" [ "$(date -d "@$SOURCE_DATE_EPOCH" -u '+%Y-%m-%d %H:%M:%S')" = "$expected_utc" ] || fail "GNU-order PostGIS date invocation was not deterministic" diff --git a/src/native/runtime/moon.yml b/src/native/runtime/moon.yml index 7fc3992af..7d77aacc9 100644 --- a/src/native/runtime/moon.yml +++ b/src/native/runtime/moon.yml @@ -75,7 +75,7 @@ tasks: tags: - quality - unit - script: "set -e\nbash src/native/runtime/bin/build-output.test.sh\nbash src/native/runtime/tools/build-dispatch.test.sh\nbash src/native/runtime/bin/postgis-dependency-cache.test.sh\nbash src/native/runtime/bin/compiler-cache.test.sh\n" + script: "set -e\nbash src/native/runtime/bin/build-output.test.sh\nbash src/native/runtime/tools/build-dispatch.test.sh\nbash src/native/runtime/bin/postgis-dependency-cache.test.sh\nbash src/native/runtime/bin/mobile-postgis-extensions.test.sh\nbash src/native/runtime/bin/compiler-cache.test.sh\n" inputs: - bin/build-output.bash - bin/build-output.test.sh @@ -89,6 +89,10 @@ tasks: - bin/build-postgres18-android-arm64.sh - bin/build-macos-extension-archives.sh - bin/mobile-postgis-extensions.sh + - bin/mobile-postgis-extensions.test.sh + - bin/mobile-static-extensions.sh + - /src/extensions/catalog/native-components.toml + - /src/extensions/tools/native-component-contract.mts - tools/build-dispatch.test.sh - tools/build-ci-target.sh - tools/release-runtime.sh diff --git a/src/native/runtime/tools/test-symbol-scope.sh b/src/native/runtime/tools/test-symbol-scope.sh index c53d7918e..7e0d02ad1 100755 --- a/src/native/runtime/tools/test-symbol-scope.sh +++ b/src/native/runtime/tools/test-symbol-scope.sh @@ -228,7 +228,7 @@ postgis_dependency_build_roots="$( in_array && /^\)$/ { exit } ' "$macos_build_script" )" -for dependency_build_root in json-c sqlite geos libxml2 proj; do +for dependency_build_root in json-c sqlite geos libxml2 proj gdal; do if ! grep -Fq '"$work_root/'"$dependency_build_root"'-native-build"' <<< "$postgis_dependency_build_roots"; then echo "native PostGIS dependency cache omits build root: $dependency_build_root" >&2 exit 1 diff --git a/src/native/rust-bindings/src/generated/extensions.rs b/src/native/rust-bindings/src/generated/extensions.rs index 711cff99a..2e7352ab0 100644 --- a/src/native/rust-bindings/src/generated/extensions.rs +++ b/src/native/rust-bindings/src/generated/extensions.rs @@ -645,6 +645,32 @@ pub(super) const fn extension_data_files(extension: Extension) -> &'static [&'st "contrib/postgis-3.6/spatial_ref_sys.sql", "contrib/postgis-3.6/uninstall_legacy.sql", "contrib/postgis-3.6/uninstall_postgis.sql", + "gdal/GDALLogoBW.svg", + "gdal/GDALLogoColor.svg", + "gdal/GDALLogoGS.svg", + "gdal/LICENSE.TXT", + "gdal/cubewerx_extra.wkt", + "gdal/ecw_cs.wkt", + "gdal/epsg.wkt", + "gdal/esri_StatePlane_extra.wkt", + "gdal/gdal_algorithm.schema.json", + "gdal/gdalicon.png", + "gdal/gdalinfo_output.schema.json", + "gdal/gdalmdiminfo_output.schema.json", + "gdal/gdalvrt.xsd", + "gdal/leaflet_template.html", + "gdal/ogr_fields_override.schema.json", + "gdal/ogrinfo_output.schema.json", + "gdal/ozi_datum.csv", + "gdal/ozi_ellips.csv", + "gdal/pci_datum.txt", + "gdal/pci_ellips.txt", + "gdal/stateplane.csv", + "gdal/template_tiles.mapml", + "gdal/tms_LINZAntarticaMapTileGrid.json", + "gdal/tms_MapML_APSTILE.json", + "gdal/tms_MapML_CBMTILE.json", + "gdal/tms_NZTM2000.json", "proj/proj.db", ], ExtensionId::Seg => &[], @@ -694,6 +720,7 @@ pub(super) const fn extension_sql_file_prefixes(extension: Extension) -> &'stati ExtensionId::Postgis => &[ "postgis_comments", "postgis_proc_set_search_path", + "postgis_raster", "rtpostgis", ], ExtensionId::Seg => &[], @@ -787,11 +814,18 @@ pub(super) const fn runtime_environment( ExtensionId::PgWalinspect => &[], ExtensionId::Pgcrypto => &[], ExtensionId::Pgtap => &[], - ExtensionId::Postgis => &[ExtensionRuntimeEnvironment { - name: "PROJ_DATA", - relative_path: "share/postgresql/proj", - required_file: "proj.db", - }], + ExtensionId::Postgis => &[ + ExtensionRuntimeEnvironment { + name: "GDAL_DATA", + relative_path: "share/postgresql/gdal", + required_file: "gdalvrt.xsd", + }, + ExtensionRuntimeEnvironment { + name: "PROJ_DATA", + relative_path: "share/postgresql/proj", + required_file: "proj.db", + }, + ], ExtensionId::Seg => &[], ExtensionId::Tablefunc => &[], ExtensionId::Tcn => &[], diff --git a/src/native/rust-bindings/src/liboliphaunt/root/extensions.rs b/src/native/rust-bindings/src/liboliphaunt/root/extensions.rs index 8617b3740..783e60b0f 100644 --- a/src/native/rust-bindings/src/liboliphaunt/root/extensions.rs +++ b/src/native/rust-bindings/src/liboliphaunt/root/extensions.rs @@ -233,6 +233,14 @@ mod tests { postgis, "postgis_proc_set_search_path.sql" )); + assert!(extension_sql_file_belongs( + postgis, + "postgis_raster.control" + )); + assert!(extension_sql_file_belongs( + postgis, + "postgis_raster--3.6.4.sql" + )); assert!(extension_sql_file_belongs(postgis, "rtpostgis--3.6.sql")); assert!(!extension_sql_file_belongs("pgtap", "postgis_comments.sql")); diff --git a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extension-legal-catalog.json b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extension-legal-catalog.json index 9bd0307d2..38a7cedd5 100644 --- a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extension-legal-catalog.json +++ b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extension-legal-catalog.json @@ -1,6 +1,6 @@ { "schema": "oliphaunt-android-extension-legal-catalog-v1", - "sourceCatalogSha256": "c1d2e09905d7ecc0172173b34b9e4104dad58987503dd3dab7af4ae78890d9f3", + "sourceCatalogSha256": "26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a", "contracts": [ { "scope": "aggregate", @@ -391,6 +391,7 @@ "target": "android-arm64-v8a", "profile": "external-native", "licenseFiles": [ + "share/licenses/gdal/LICENSE.TXT", "share/licenses/geos/COPYING", "share/licenses/geos/src/deps/ryu/LICENSE", "share/licenses/geos/src/deps/ryu/LICENSE-Apache2", @@ -421,6 +422,12 @@ "sha256": "20fd67487d1a631ba959cf7ec3fdbaf4977997ed624c0188dcb3c8870eded830", "mode": "0644" }, + { + "path": "share/licenses/gdal/LICENSE.TXT", + "bytes": 21841, + "sha256": "1dae3468e81d00da56e2936f74d33b8b3ad09d726437f19ce209a5dabea41f77", + "mode": "0644" + }, { "path": "share/licenses/geos/COPYING", "bytes": 26530, @@ -526,6 +533,7 @@ "target": "android-x86_64", "profile": "external-native", "licenseFiles": [ + "share/licenses/gdal/LICENSE.TXT", "share/licenses/geos/COPYING", "share/licenses/geos/src/deps/ryu/LICENSE", "share/licenses/geos/src/deps/ryu/LICENSE-Apache2", @@ -556,6 +564,12 @@ "sha256": "20fd67487d1a631ba959cf7ec3fdbaf4977997ed624c0188dcb3c8870eded830", "mode": "0644" }, + { + "path": "share/licenses/gdal/LICENSE.TXT", + "bytes": 21841, + "sha256": "1dae3468e81d00da56e2936f74d33b8b3ad09d726437f19ce209a5dabea41f77", + "mode": "0644" + }, { "path": "share/licenses/geos/COPYING", "bytes": 26530, @@ -2447,6 +2461,7 @@ "target": "android-arm64-v8a", "profile": "external-native", "licenseFiles": [ + "share/licenses/gdal/LICENSE.TXT", "share/licenses/geos/COPYING", "share/licenses/geos/src/deps/ryu/LICENSE", "share/licenses/geos/src/deps/ryu/LICENSE-Apache2", @@ -2477,6 +2492,12 @@ "sha256": "20fd67487d1a631ba959cf7ec3fdbaf4977997ed624c0188dcb3c8870eded830", "mode": "0644" }, + { + "path": "files/share/licenses/gdal/LICENSE.TXT", + "bytes": 21841, + "sha256": "1dae3468e81d00da56e2936f74d33b8b3ad09d726437f19ce209a5dabea41f77", + "mode": "0644" + }, { "path": "files/share/licenses/geos/COPYING", "bytes": 26530, @@ -2582,6 +2603,7 @@ "target": "android-x86_64", "profile": "external-native", "licenseFiles": [ + "share/licenses/gdal/LICENSE.TXT", "share/licenses/geos/COPYING", "share/licenses/geos/src/deps/ryu/LICENSE", "share/licenses/geos/src/deps/ryu/LICENSE-Apache2", @@ -2612,6 +2634,12 @@ "sha256": "20fd67487d1a631ba959cf7ec3fdbaf4977997ed624c0188dcb3c8870eded830", "mode": "0644" }, + { + "path": "files/share/licenses/gdal/LICENSE.TXT", + "bytes": 21841, + "sha256": "1dae3468e81d00da56e2936f74d33b8b3ad09d726437f19ce209a5dabea41f77", + "mode": "0644" + }, { "path": "files/share/licenses/geos/COPYING", "bytes": 26530, diff --git a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extensions.properties b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extensions.properties index e7fcddbbd..9d3c2a03a 100644 --- a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extensions.properties +++ b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extensions.properties @@ -1,7 +1,7 @@ # This file is generated by src/extensions/tools/check-extension-model.sh. # Do not edit by hand. schema=oliphaunt-android-extension-catalog-v2 -catalogSha256=c1d2e09905d7ecc0172173b34b9e4104dad58987503dd3dab7af4ae78890d9f3 +catalogSha256=26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a extension.amcheck.artifactProduct=oliphaunt-extension-contrib-pg18 extension.amcheck.releaseProduct=liboliphaunt-native extension.amcheck.mavenGroup=dev.oliphaunt.extensions diff --git a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java index c906a044e..29f226dd2 100644 --- a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java +++ b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java @@ -138,20 +138,22 @@ private static void validatesPublicTarGzArchivePreflight() throws Exception { try { PublicTarGzArchivePreflight.Limits extensionLimits = PublicTarGzArchivePreflight.extensionArtifactLimits(); - equal(134_217_728L, extensionLimits.maxCompressedBytes(), "extension compressed limit"); - equal(536_870_912L, extensionLimits.maxExpandedBytes(), "extension expanded limit"); - equal(268_435_456L, extensionLimits.maxEntryBytes(), "extension member limit"); + equal(1_073_741_824L, extensionLimits.maxCompressedBytes(), "extension compressed limit"); + equal(2_147_483_648L, extensionLimits.maxExpandedBytes(), "extension expanded limit"); + equal(1_073_741_824L, extensionLimits.maxEntryBytes(), "extension member limit"); equal(4_096, extensionLimits.maxEntries(), "extension member-count limit"); equal( extensionLimits.maxExpandedBytes(), PublicTarGzArchivePreflight.expansionLimitForContractTest(1, extensionLimits), "extension policy must not add a Java-only compression-ratio limit"); + // The qualified GDAL leaf exceeded 512 MiB. Combine its minimum proven + // size with the prior exact PostGIS high-water marks conservatively. if (!(64_676_748L <= extensionLimits.maxCompressedBytes() - && 345_621_694L <= extensionLimits.maxExpandedBytes() - && 154_827_564L <= extensionLimits.maxEntryBytes() + && 882_492_607L <= extensionLimits.maxExpandedBytes() + && 536_870_913L <= extensionLimits.maxEntryBytes() && 27 <= extensionLimits.maxEntries())) { throw new AssertionError( - "shared extension archive policy must admit the observed Android ARM64 PostGIS leaf shape"); + "shared extension archive policy must admit the qualified Android ARM64 PostGIS leaf envelope"); } List validEntries = List.of( @@ -576,8 +578,8 @@ private static void validatesGeneratedExtensionLegalCatalog() throws Exception { OliphauntExtensionLegalCatalog.Contract postgis = OliphauntExtensionLegalCatalog.requireLeaf("postgis", target); equal("external-native", postgis.profile(), target + " PostGIS legal profile"); - equal(16, postgis.licenseFiles().size(), target + " PostGIS upstream license count"); - equal(18, postgis.members().size(), target + " PostGIS exact legal member count"); + equal(17, postgis.licenseFiles().size(), target + " PostGIS upstream license count"); + equal(19, postgis.members().size(), target + " PostGIS exact legal member count"); for (OliphauntExtensionLegalCatalog.LegalMember member : postgis.members()) { equal(0644, member.mode(), target + " PostGIS legal mode " + member.path()); } @@ -864,7 +866,7 @@ private static void validatesPgcryptoAndPostgisLegalArtifacts() throws Exception try (var files = Files.walk(postgisRoot.resolve("files/share/licenses"))) { upstreamFiles = files.filter(Files::isRegularFile).count(); } - equal(16L, upstreamFiles, "PostGIS exact upstream legal file count"); + equal(17L, upstreamFiles, "PostGIS exact upstream legal file count"); } finally { deleteRecursively(root); } diff --git a/src/native/sdks/react-native/src/generated/extensions.ts b/src/native/sdks/react-native/src/generated/extensions.ts index a510a3679..6ae9a6ab3 100644 --- a/src/native/sdks/react-native/src/generated/extensions.ts +++ b/src/native/sdks/react-native/src/generated/extensions.ts @@ -26,7 +26,7 @@ export type GeneratedExtensionMetadata = { readonly sourceKind: string; }; -export const GENERATED_EXTENSION_METADATA_SHA256 = "c1d2e09905d7ecc0172173b34b9e4104dad58987503dd3dab7af4ae78890d9f3" as const; +export const GENERATED_EXTENSION_METADATA_SHA256 = "26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a" as const; export const GENERATED_EXTENSION_METADATA = [ { @@ -781,6 +781,32 @@ export const GENERATED_EXTENSION_METADATA = [ "share/postgresql/contrib/postgis-3.6/spatial_ref_sys.sql", "share/postgresql/contrib/postgis-3.6/uninstall_legacy.sql", "share/postgresql/contrib/postgis-3.6/uninstall_postgis.sql", + "share/postgresql/gdal/GDALLogoBW.svg", + "share/postgresql/gdal/GDALLogoColor.svg", + "share/postgresql/gdal/GDALLogoGS.svg", + "share/postgresql/gdal/LICENSE.TXT", + "share/postgresql/gdal/cubewerx_extra.wkt", + "share/postgresql/gdal/ecw_cs.wkt", + "share/postgresql/gdal/epsg.wkt", + "share/postgresql/gdal/esri_StatePlane_extra.wkt", + "share/postgresql/gdal/gdal_algorithm.schema.json", + "share/postgresql/gdal/gdalicon.png", + "share/postgresql/gdal/gdalinfo_output.schema.json", + "share/postgresql/gdal/gdalmdiminfo_output.schema.json", + "share/postgresql/gdal/gdalvrt.xsd", + "share/postgresql/gdal/leaflet_template.html", + "share/postgresql/gdal/ogr_fields_override.schema.json", + "share/postgresql/gdal/ogrinfo_output.schema.json", + "share/postgresql/gdal/ozi_datum.csv", + "share/postgresql/gdal/ozi_ellips.csv", + "share/postgresql/gdal/pci_datum.txt", + "share/postgresql/gdal/pci_ellips.txt", + "share/postgresql/gdal/stateplane.csv", + "share/postgresql/gdal/template_tiles.mapml", + "share/postgresql/gdal/tms_LINZAntarticaMapTileGrid.json", + "share/postgresql/gdal/tms_MapML_APSTILE.json", + "share/postgresql/gdal/tms_MapML_CBMTILE.json", + "share/postgresql/gdal/tms_NZTM2000.json", "share/postgresql/proj/proj.db" ], "dependencies": [], @@ -791,10 +817,12 @@ export const GENERATED_EXTENSION_METADATA = [ "extensionSqlFilePrefixes": [ "postgis_comments", "postgis_proc_set_search_path", + "postgis_raster", "rtpostgis" ], "id": "postgis", "iosStaticDependencies": [ + "gdal", "geos", "geos-c", "json-c", @@ -818,6 +846,32 @@ export const GENERATED_EXTENSION_METADATA = [ "contrib/postgis-3.6/spatial_ref_sys.sql", "contrib/postgis-3.6/uninstall_legacy.sql", "contrib/postgis-3.6/uninstall_postgis.sql", + "gdal/GDALLogoBW.svg", + "gdal/GDALLogoColor.svg", + "gdal/GDALLogoGS.svg", + "gdal/LICENSE.TXT", + "gdal/cubewerx_extra.wkt", + "gdal/ecw_cs.wkt", + "gdal/epsg.wkt", + "gdal/esri_StatePlane_extra.wkt", + "gdal/gdal_algorithm.schema.json", + "gdal/gdalicon.png", + "gdal/gdalinfo_output.schema.json", + "gdal/gdalmdiminfo_output.schema.json", + "gdal/gdalvrt.xsd", + "gdal/leaflet_template.html", + "gdal/ogr_fields_override.schema.json", + "gdal/ogrinfo_output.schema.json", + "gdal/ozi_datum.csv", + "gdal/ozi_ellips.csv", + "gdal/pci_datum.txt", + "gdal/pci_ellips.txt", + "gdal/stateplane.csv", + "gdal/template_tiles.mapml", + "gdal/tms_LINZAntarticaMapTileGrid.json", + "gdal/tms_MapML_APSTILE.json", + "gdal/tms_MapML_CBMTILE.json", + "gdal/tms_NZTM2000.json", "proj/proj.db" ], "selectedExtensionDependencies": [], diff --git a/src/native/sdks/ts/src/generated/extensions.ts b/src/native/sdks/ts/src/generated/extensions.ts index c729ea666..6192662a7 100644 --- a/src/native/sdks/ts/src/generated/extensions.ts +++ b/src/native/sdks/ts/src/generated/extensions.ts @@ -25,7 +25,7 @@ export type GeneratedExtensionMetadata = { readonly sourceKind: string; }; -export const GENERATED_EXTENSION_METADATA_SHA256 = "c1d2e09905d7ecc0172173b34b9e4104dad58987503dd3dab7af4ae78890d9f3" as const; +export const GENERATED_EXTENSION_METADATA_SHA256 = "26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a" as const; export const GENERATED_EXTENSION_METADATA = [ { @@ -748,6 +748,32 @@ export const GENERATED_EXTENSION_METADATA = [ "share/postgresql/contrib/postgis-3.6/spatial_ref_sys.sql", "share/postgresql/contrib/postgis-3.6/uninstall_legacy.sql", "share/postgresql/contrib/postgis-3.6/uninstall_postgis.sql", + "share/postgresql/gdal/GDALLogoBW.svg", + "share/postgresql/gdal/GDALLogoColor.svg", + "share/postgresql/gdal/GDALLogoGS.svg", + "share/postgresql/gdal/LICENSE.TXT", + "share/postgresql/gdal/cubewerx_extra.wkt", + "share/postgresql/gdal/ecw_cs.wkt", + "share/postgresql/gdal/epsg.wkt", + "share/postgresql/gdal/esri_StatePlane_extra.wkt", + "share/postgresql/gdal/gdal_algorithm.schema.json", + "share/postgresql/gdal/gdalicon.png", + "share/postgresql/gdal/gdalinfo_output.schema.json", + "share/postgresql/gdal/gdalmdiminfo_output.schema.json", + "share/postgresql/gdal/gdalvrt.xsd", + "share/postgresql/gdal/leaflet_template.html", + "share/postgresql/gdal/ogr_fields_override.schema.json", + "share/postgresql/gdal/ogrinfo_output.schema.json", + "share/postgresql/gdal/ozi_datum.csv", + "share/postgresql/gdal/ozi_ellips.csv", + "share/postgresql/gdal/pci_datum.txt", + "share/postgresql/gdal/pci_ellips.txt", + "share/postgresql/gdal/stateplane.csv", + "share/postgresql/gdal/template_tiles.mapml", + "share/postgresql/gdal/tms_LINZAntarticaMapTileGrid.json", + "share/postgresql/gdal/tms_MapML_APSTILE.json", + "share/postgresql/gdal/tms_MapML_CBMTILE.json", + "share/postgresql/gdal/tms_NZTM2000.json", "share/postgresql/proj/proj.db" ], "dependencies": [], @@ -758,6 +784,7 @@ export const GENERATED_EXTENSION_METADATA = [ "extensionSqlFilePrefixes": [ "postgis_comments", "postgis_proc_set_search_path", + "postgis_raster", "rtpostgis" ], "id": "postgis", @@ -777,6 +804,32 @@ export const GENERATED_EXTENSION_METADATA = [ "contrib/postgis-3.6/spatial_ref_sys.sql", "contrib/postgis-3.6/uninstall_legacy.sql", "contrib/postgis-3.6/uninstall_postgis.sql", + "gdal/GDALLogoBW.svg", + "gdal/GDALLogoColor.svg", + "gdal/GDALLogoGS.svg", + "gdal/LICENSE.TXT", + "gdal/cubewerx_extra.wkt", + "gdal/ecw_cs.wkt", + "gdal/epsg.wkt", + "gdal/esri_StatePlane_extra.wkt", + "gdal/gdal_algorithm.schema.json", + "gdal/gdalicon.png", + "gdal/gdalinfo_output.schema.json", + "gdal/gdalmdiminfo_output.schema.json", + "gdal/gdalvrt.xsd", + "gdal/leaflet_template.html", + "gdal/ogr_fields_override.schema.json", + "gdal/ogrinfo_output.schema.json", + "gdal/ozi_datum.csv", + "gdal/ozi_ellips.csv", + "gdal/pci_datum.txt", + "gdal/pci_ellips.txt", + "gdal/stateplane.csv", + "gdal/template_tiles.mapml", + "gdal/tms_LINZAntarticaMapTileGrid.json", + "gdal/tms_MapML_APSTILE.json", + "gdal/tms_MapML_CBMTILE.json", + "gdal/tms_NZTM2000.json", "proj/proj.db" ], "selectedExtensionDependencies": [], diff --git a/src/test-fixtures/extensions/postgis.sql b/src/test-fixtures/extensions/postgis.sql index dc5dafeac..289ff1463 100644 --- a/src/test-fixtures/extensions/postgis.sql +++ b/src/test-fixtures/extensions/postgis.sql @@ -1,3 +1,5 @@ +CREATE EXTENSION IF NOT EXISTS postgis_raster; +-- oliphaunt-statement DROP TABLE IF EXISTS oliphaunt_postgis_points; -- oliphaunt-statement CREATE TABLE oliphaunt_postgis_points(id int PRIMARY KEY, geom geometry(Point, 4326)); @@ -16,7 +18,11 @@ DECLARE area float8; polygons int; nearby int; + gdal_drivers int; + raster_roundtrip raster; + raster_value float8; BEGIN + SET LOCAL postgis.gdal_enabled_drivers = 'GTiff'; SELECT ST_Distance(ST_GeomFromText('POINT(0 0)'), ST_GeomFromText('POINT(3 4)')) INTO distance; IF distance <> 5 THEN RAISE EXCEPTION 'postgis geometry distance failed: %', distance; @@ -57,4 +63,28 @@ BEGIN IF nearby <> 2 THEN RAISE EXCEPTION 'postgis dwithin failed: %', nearby; END IF; + SELECT count(*) INTO gdal_drivers + FROM ST_GDALDrivers() + WHERE short_name = 'GTiff' AND can_read AND can_write; + IF gdal_drivers <> 1 THEN + RAISE EXCEPTION 'postgis raster GeoTIFF driver is unavailable'; + END IF; + SELECT ST_FromGDALRaster( + ST_AsGDALRaster( + ST_AddBand( + ST_MakeEmptyRaster(2, 2, 0, 0, 1, -1, 0, 0, 4326), + '8BUI'::text, + 7::double precision, + 0::double precision + ), + 'GTiff' + ) + ) INTO raster_roundtrip; + SELECT ST_Value(raster_roundtrip, 1, 1, 1) INTO raster_value; + IF raster_value IS DISTINCT FROM 7 OR ST_SRID(raster_roundtrip) IS DISTINCT FROM 4326 + OR ST_Width(raster_roundtrip) IS DISTINCT FROM 2 OR ST_Height(raster_roundtrip) IS DISTINCT FROM 2 THEN + RAISE EXCEPTION 'postgis raster GDAL round-trip failed: value %, srid %', + raster_value, + ST_SRID(raster_roundtrip); + END IF; END $$; diff --git a/src/wasix/runtime/assets/build/build_wasix_gdal.sh b/src/wasix/runtime/assets/build/build_wasix_gdal.sh new file mode 100755 index 000000000..e0ea88314 --- /dev/null +++ b/src/wasix/runtime/assets/build/build_wasix_gdal.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. "$ROOT/wasix_third_party.sh" + +REPO_ROOT="$(oliphaunt_wasix_repo_root "$ROOT")" +GDAL_SOURCE_DIR="${GDAL_SOURCE_DIR:-$REPO_ROOT/target/oliphaunt-sources/checkouts/gdal}" +GENERATED_ROOT="$(oliphaunt_wasix_generated_root "$REPO_ROOT")" +GDAL_PREFIX="${GDAL_PREFIX:-$GENERATED_ROOT/work/gdal-wasix}" +GDAL_BUILD_DIR="${GDAL_BUILD_DIR:-$GENERATED_ROOT/work/gdal-wasix-build}" +GEOS_PREFIX="${GEOS_PREFIX:-$("$ROOT/build_wasix_geos.sh")}" +JSONC_PREFIX="${JSONC_PREFIX:-$("$ROOT/build_wasix_jsonc.sh")}" +LIBXML2_PREFIX="${LIBXML2_PREFIX:-$("$ROOT/build_wasix_libxml2.sh")}" +LIBICONV_PREFIX="${LIBICONV_PREFIX:-$("$ROOT/build_wasix_libiconv.sh")}" +PROJ_PREFIX="${PROJ_PREFIX:-$("$ROOT/build_wasix_proj.sh")}" +SQLITE_PREFIX="${SQLITE_PREFIX:-$("$ROOT/build_wasix_sqlite.sh")}" +JOBS="${JOBS:-4}" + +if [ ! -f "$GDAL_SOURCE_DIR/CMakeLists.txt" ]; then + echo "missing GDAL source checkout at $GDAL_SOURCE_DIR; run assets fetch/source-spine first" >&2 + exit 1 +fi + +. "$ROOT/docker_wasix_env.sh" +. "$ROOT/profile_flags.sh" +oliphaunt_wasix_apply_wasix_profile build + +source_commit="$(oliphaunt_wasix_source_commit "$GDAL_SOURCE_DIR")" +script_sha256="$(oliphaunt_wasix_script_sha256 "$0")" +helper_sha256="$(oliphaunt_wasix_script_sha256 "$ROOT/wasix_third_party.sh")" +compat_header="$ROOT/wasix_shim/gdal_wasix_compat.h" +compat_sha256="$(oliphaunt_wasix_script_sha256 "$compat_header")" +dependency_stamps="$( + for row in \ + "$GEOS_PREFIX/.oliphaunt-wasix-geos-build" \ + "$JSONC_PREFIX/.oliphaunt-wasix-json-c-build" \ + "$LIBICONV_PREFIX/.oliphaunt-wasix-libiconv-build" \ + "$LIBXML2_PREFIX/.oliphaunt-wasix-libxml2-build" \ + "$PROJ_PREFIX/.oliphaunt-wasix-proj-build" \ + "$SQLITE_PREFIX/.oliphaunt-wasix-sqlite-build" + do + printf '%s=%s\n' "$(basename "$row")" "$(cat "$row")" + done +)" +wasixcc_version="$(wasixcc --version 2>/dev/null)" +wasixcc_version="${wasixcc_version%%$'\n'*}" +stamp="source=$source_commit +script=$script_sha256 +helper=$helper_sha256 +compat=$compat_sha256 +$dependency_stamps +profile=$(oliphaunt_wasix_wasix_profile_signature) +wasixcc=$wasixcc_version +cmake=static-core-vrt-gtiff-internal-codecs-no-apps-no-optional-drivers" + +if [ -f "$GDAL_PREFIX/.oliphaunt-wasix-gdal-build" ] && + [ -f "$GDAL_PREFIX/include/gdal.h" ] && + [ -f "$GDAL_PREFIX/lib/libgdal.a" ] && + [ -x "$GDAL_PREFIX/bin/gdal-config" ] && + [ "$(cat "$GDAL_PREFIX/.oliphaunt-wasix-gdal-build")" = "$stamp" ]; then + echo "$GDAL_PREFIX" + exit 0 +fi + +{ + rm -rf "$GDAL_BUILD_DIR" "$GDAL_PREFIX" + mkdir -p "$GDAL_BUILD_DIR" "$(dirname "$GDAL_PREFIX")" + OLIPHAUNT_WASIX_CMAKE_C_FLAGS_EXTRA="-include $compat_header" \ + OLIPHAUNT_WASIX_CMAKE_CXX_FLAGS_EXTRA="-include $compat_header" \ + oliphaunt_wasix_static_cmake_build \ + "$GDAL_SOURCE_DIR" \ + "$GDAL_BUILD_DIR" \ + "$GDAL_PREFIX" \ + -DBASH_COMPLETIONS_DIR= \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_APPS=OFF \ + -DBUILD_CSHARP_BINDINGS=OFF \ + -DBUILD_JAVA_BINDINGS=OFF \ + -DBUILD_PYTHON_BINDINGS=OFF \ + -DBUILD_TESTING=OFF \ + -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF \ + -DGDAL_OBJECT_LIBRARIES_POSITION_INDEPENDENT_CODE=ON \ + -DOGR_BUILD_OPTIONAL_DRIVERS=OFF \ + -DGDAL_ENABLE_DRIVER_GTIFF=ON \ + -DGDAL_ENABLE_DRIVER_VRT=ON \ + -DOGR_ENABLE_DRIVER_GEOJSON=ON \ + -DOGR_ENABLE_DRIVER_SHAPE=ON \ + -DGDAL_USE_EXTERNAL_LIBS=OFF \ + -DGDAL_USE_INTERNAL_LIBS=ON \ + -DGDAL_USE_OPENMP=OFF \ + -DHAVE_DLFCN_H=0 \ + -DHAVE_DL_ITERATE_PHDR=0 \ + -DHAVE_SCHED_GETAFFINITY=0 \ + -DGDAL_USE_CURL=OFF \ + -DGDAL_USE_OPENSSL=OFF \ + -DGDAL_USE_GEOS=ON \ + -DGEOS_INCLUDE_DIR="$GEOS_PREFIX/include" \ + -DGEOS_LIBRARY="$GEOS_PREFIX/lib/libgeos_c.a" \ + -DGDAL_USE_JSONC=ON \ + -DGDAL_USE_JSONC_INTERNAL=OFF \ + -DJSONC_INCLUDE_DIR="$JSONC_PREFIX/include/json-c" \ + -DJSONC_LIBRARY="$JSONC_PREFIX/lib/libjson-c.a" \ + -DGDAL_USE_LIBXML2=ON \ + -DLIBXML2_INCLUDE_DIR="$LIBXML2_PREFIX/include/libxml2" \ + -DLIBXML2_LIBRARY="$LIBXML2_PREFIX/lib/libxml2.a" \ + -DGDAL_USE_ICONV=ON \ + -DIconv_INCLUDE_DIR="$LIBICONV_PREFIX/include" \ + -DIconv_LIBRARY="$LIBICONV_PREFIX/lib/libiconv.a" \ + -DGDAL_USE_PROJ=ON \ + -DPROJ_INCLUDE_DIR="$PROJ_PREFIX/include" \ + -DPROJ_LIBRARY="$PROJ_PREFIX/lib/libproj.a" \ + -DGDAL_USE_SQLITE3=ON \ + -DSQLite3_INCLUDE_DIR="$SQLITE_PREFIX/include" \ + -DSQLite3_LIBRARY="$SQLITE_PREFIX/lib/libsqlite3.a" \ + -DACCEPT_MISSING_SQLITE3_MUTEX_ALLOC=ON +} >&2 + +test -f "$GDAL_PREFIX/include/gdal.h" +test -f "$GDAL_PREFIX/include/ogr_api.h" +test -f "$GDAL_PREFIX/lib/libgdal.a" +test -x "$GDAL_PREFIX/bin/gdal-config" +printf '%s\n' "$stamp" > "$GDAL_PREFIX/.oliphaunt-wasix-gdal-build" +echo "$GDAL_PREFIX" diff --git a/src/wasix/runtime/assets/build/wasix_shim/gdal_wasix_compat.h b/src/wasix/runtime/assets/build/wasix_shim/gdal_wasix_compat.h new file mode 100644 index 000000000..a3a81ec3b --- /dev/null +++ b/src/wasix/runtime/assets/build/wasix_shim/gdal_wasix_compat.h @@ -0,0 +1,22 @@ +#ifndef OLIPHAUNT_GDAL_WASIX_COMPAT_H +#define OLIPHAUNT_GDAL_WASIX_COMPAT_H + +#include +#include + +/* + * GDAL's process-spawn portability layer unconditionally compiles its + * fork-based fallback on non-Windows targets. WASIX intentionally does not + * expose fork(2), and PostGIS raster does not use GDAL's subprocess helpers. + * Keep the API's normal failure behavior while making that unused fallback + * portable to the WASIX libc. + */ +static inline pid_t oliphaunt_gdal_wasix_fork_unavailable(void) +{ + errno = ENOSYS; + return -1; +} + +#define fork oliphaunt_gdal_wasix_fork_unavailable + +#endif diff --git a/src/wasix/runtime/assets/build/wasix_third_party.sh b/src/wasix/runtime/assets/build/wasix_third_party.sh index 2b7b0c9e9..7b4b2e158 100755 --- a/src/wasix/runtime/assets/build/wasix_third_party.sh +++ b/src/wasix/runtime/assets/build/wasix_third_party.sh @@ -328,8 +328,8 @@ oliphaunt_wasix_static_cmake_build() { -DCMAKE_C_VISIBILITY_PRESET=hidden \ -DCMAKE_CXX_VISIBILITY_PRESET=hidden \ -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ - -DCMAKE_C_FLAGS="$OLIPHAUNT_WASM_PROFILE_CFLAGS -fPIC -fvisibility=hidden -Wno-unused-command-line-argument" \ - -DCMAKE_CXX_FLAGS="$OLIPHAUNT_WASM_PROFILE_CFLAGS -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -Wno-unused-command-line-argument" \ + -DCMAKE_C_FLAGS="$OLIPHAUNT_WASM_PROFILE_CFLAGS -fPIC -fvisibility=hidden -Wno-unused-command-line-argument ${OLIPHAUNT_WASIX_CMAKE_C_FLAGS_EXTRA:-}" \ + -DCMAKE_CXX_FLAGS="$OLIPHAUNT_WASM_PROFILE_CFLAGS -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -Wno-unused-command-line-argument ${OLIPHAUNT_WASIX_CMAKE_CXX_FLAGS_EXTRA:-}" \ "$@" cmake --build "$build_dir" --parallel "$JOBS" cmake --install "$build_dir" diff --git a/src/wasix/runtime/assets/generated/wasix-dl.exports b/src/wasix/runtime/assets/generated/wasix-dl.exports index 388c2ee05..360dee53f 100644 --- a/src/wasix/runtime/assets/generated/wasix-dl.exports +++ b/src/wasix/runtime/assets/generated/wasix-dl.exports @@ -153,10 +153,12 @@ GenericXLogRegisterBuffer GenericXLogStart GetAccessStrategy GetActiveSnapshot +GetAttributeByName GetAttributeByNum GetCachedExpression GetCommandTagName GetConfigOption +GetConfigOptionByName GetCurrentFullTransactionId GetCurrentSubTransactionId GetCurrentTimestamp @@ -341,6 +343,7 @@ SPI_copytuple SPI_cursor_close SPI_cursor_fetch SPI_cursor_find +SPI_cursor_open_with_args SPI_cursor_open_with_paramlist SPI_cursor_parse_open SPI_datumTransfer @@ -361,6 +364,7 @@ SPI_getvalue SPI_keepplan SPI_modifytuple SPI_palloc +SPI_pfree SPI_plan_get_cached_plan SPI_plan_get_plan_sources SPI_plan_is_valid @@ -369,6 +373,7 @@ SPI_prepare_extended SPI_processed SPI_register_relation SPI_register_trigger_data +SPI_repalloc SPI_result SPI_result_code_string SPI_returntuple @@ -496,6 +501,7 @@ array_free_iterator array_iterate arraycontjoinsel arraycontsel +asc_tolower asin assign_expr_collations atan2l @@ -736,6 +742,7 @@ free_object_addresses fseek fstat ftell +func_volatile function_parse_error_transpose fwrite gen_random_uuid @@ -768,6 +775,7 @@ get_fn_opclass_options get_func_arg_info get_func_name get_func_namespace +get_func_result_type get_func_rettype get_func_support get_function_sibling_type @@ -945,7 +953,9 @@ max_parallel_maintenance_workers memchr memcmp memset +mmap mul_size +munmap namein nanosleep network_cmp diff --git a/src/wasix/runtime/tools/xtask/src/asset_checks.rs b/src/wasix/runtime/tools/xtask/src/asset_checks.rs index def8e444f..4104c1bd1 100644 --- a/src/wasix/runtime/tools/xtask/src/asset_checks.rs +++ b/src/wasix/runtime/tools/xtask/src/asset_checks.rs @@ -386,6 +386,7 @@ fn check_extension_archive_layout(path: &Path) -> Result<()> { | "share" | "share/proj" | "share/postgresql" + | "share/postgresql/gdal" | "share/postgresql/extension" | "share/postgresql/tsearch_data" ) { @@ -393,6 +394,7 @@ fn check_extension_archive_layout(path: &Path) -> Result<()> { } if entry.starts_with("lib/postgresql/") || entry.starts_with("share/proj/") + || entry.starts_with("share/postgresql/gdal/") || entry.starts_with("share/postgresql/extension/") || entry.starts_with("share/postgresql/tsearch_data/") { diff --git a/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs b/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs index 3b1b105f8..22cf0fb6e 100644 --- a/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs +++ b/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs @@ -1652,7 +1652,7 @@ fn stage_recipe_staged_extension( extension.id, source.display() ); - copy_dir_all(&source, &stage.join(&data_dir.destination))?; + copy_dir_all_overlay(&source, &stage.join(&data_dir.destination))?; } Ok(()) } diff --git a/src/wasix/runtime/tools/xtask/src/fs_utils.rs b/src/wasix/runtime/tools/xtask/src/fs_utils.rs index aa6605125..22b448e56 100644 --- a/src/wasix/runtime/tools/xtask/src/fs_utils.rs +++ b/src/wasix/runtime/tools/xtask/src/fs_utils.rs @@ -262,6 +262,10 @@ pub(crate) fn copy_dir_all(source: &Path, destination: &Path) -> Result<()> { fs::remove_dir_all(destination) .with_context(|| format!("remove {}", destination.display()))?; } + copy_dir_all_overlay(source, destination) +} + +pub(crate) fn copy_dir_all_overlay(source: &Path, destination: &Path) -> Result<()> { fs::create_dir_all(destination).with_context(|| format!("create {}", destination.display()))?; for entry in WalkDir::new(source) { let entry = entry.with_context(|| format!("walk {}", source.display()))?; @@ -509,6 +513,34 @@ mod tests { } } + #[test] + fn overlay_preserves_core_sql_while_replace_removes_stale_files() { + let fixture = TestDirectory::new("extension-sql-overlay"); + let source = fixture.0.join("raster"); + let destination = fixture.0.join("extension"); + fs::create_dir_all(source.join("nested")).unwrap(); + fs::create_dir_all(&destination).unwrap(); + fs::write(source.join("nested/postgis_raster.control"), b"raster").unwrap(); + fs::write(destination.join("postgis.control"), b"core").unwrap(); + + copy_dir_all_overlay(&source, &destination).unwrap(); + assert_eq!( + fs::read(destination.join("postgis.control")).unwrap(), + b"core" + ); + assert_eq!( + fs::read(destination.join("nested/postgis_raster.control")).unwrap(), + b"raster" + ); + + copy_dir_all(&source, &destination).unwrap(); + assert!(!destination.join("postgis.control").exists()); + assert_eq!( + fs::read(destination.join("nested/postgis_raster.control")).unwrap(), + b"raster" + ); + } + #[test] fn sha256_text_file_lf_is_independent_of_crlf_checkout() { let path = std::env::temp_dir().join(format!( diff --git a/src/wasix/sdks/rust/src/oliphaunt/generated_extensions.rs b/src/wasix/sdks/rust/src/oliphaunt/generated_extensions.rs index 04f2b30d4..dd1e2bc2e 100644 --- a/src/wasix/sdks/rust/src/oliphaunt/generated_extensions.rs +++ b/src/wasix/sdks/rust/src/oliphaunt/generated_extensions.rs @@ -304,10 +304,16 @@ const DEFINITION_PGTAP: Extension = Extension { const DEFINITION_POSTGIS: Extension = Extension { sql_name: "postgis", - native_support_modules: &[super::ExtensionNativeModule { - runtime_path: "lib/postgresql/liboliphaunt_postgis_deps.so", - aot_name: Some("extension:postgis:postgis_deps"), - }], + native_support_modules: &[ + super::ExtensionNativeModule { + runtime_path: "lib/postgresql/liboliphaunt_postgis_deps.so", + aot_name: Some("extension:postgis:postgis_deps"), + }, + super::ExtensionNativeModule { + runtime_path: "lib/postgresql/postgis_raster-3.so", + aot_name: Some("extension:postgis:postgis_raster"), + }, + ], native_module_file: Some("postgis-3.so"), aot_name: Some("extension:postgis"), dependencies: &[], diff --git a/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs b/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs index bc7123e27..373002157 100644 --- a/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs +++ b/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs @@ -1789,6 +1789,7 @@ fn add_oliphaunt_env( ("PGTZ", "UTC"), ("PG_COLOR", "never"), ("PROJ_DATA", "/share/proj"), + ("GDAL_DATA", "/share/postgresql/gdal"), ] { builder.add_env(key, value); } diff --git a/src/wasix/sdks/ts/src/__tests__/extensions.test.ts b/src/wasix/sdks/ts/src/__tests__/extensions.test.ts index c3b916c90..21bea424a 100644 --- a/src/wasix/sdks/ts/src/__tests__/extensions.test.ts +++ b/src/wasix/sdks/ts/src/__tests__/extensions.test.ts @@ -283,6 +283,37 @@ describe('WASIX TypeScript extensions', () => { ); }); + it('mounts declared PostGIS raster modules and GDAL resources', () => { + const files = new Map([ + ['lib/postgresql/postgis_raster-3.so', Uint8Array.of(1)], + ['share/postgresql/gdal/gdalvrt.xsd', Uint8Array.of(2)], + ]); + const postgis = extension('postgis', { installedFiles: [...files.keys()] }); + const layout = runtimeLayout(); + overlayExtensionArchive( + layout, + { + files, + directories: new Set(['lib/postgresql', 'share/postgresql/gdal']), + }, + postgis, + ); + expect(layout.mounts['/lib']?.files['postgresql/postgis_raster-3.so']).toEqual( + Uint8Array.of(1), + ); + expect(layout.mounts['/share']?.files['postgresql/gdal/gdalvrt.xsd']).toEqual(Uint8Array.of(2)); + expect(() => + overlayExtensionArchive( + runtimeLayout(), + { + files: new Map([...files, ['share/postgresql/gdal/undeclared.csv', Uint8Array.of(3)]]), + directories: new Set(), + }, + postgis, + ), + ).toThrow('unexpected share/postgresql/gdal/undeclared.csv'); + }); + it('rejects undeclared, foreign-root, and colliding extension files', () => { const layout = runtimeLayout(); const declared = extension('pgtap', { diff --git a/src/wasix/sdks/ts/src/hosts/browser/wasix-runtime.ts b/src/wasix/sdks/ts/src/hosts/browser/wasix-runtime.ts index 464166dd1..c170fc21a 100644 --- a/src/wasix/sdks/ts/src/hosts/browser/wasix-runtime.ts +++ b/src/wasix/sdks/ts/src/hosts/browser/wasix-runtime.ts @@ -183,6 +183,7 @@ export function wasixPostgresEnvironment( PGTZ: 'UTC', PG_COLOR: 'never', PROJ_DATA: '/share/proj', + GDAL_DATA: '/share/postgresql/gdal', ...(icuEnabled ? { ICU_DATA: '/share/icu' } : {}), // The canonical guest specializes backend atomics for a one-backend // WebAssembly instance. Every host placement must enforce that invariant. diff --git a/src/wasix/sdks/ts/src/resources/extensions.ts b/src/wasix/sdks/ts/src/resources/extensions.ts index ca13a1050..457b36f21 100644 --- a/src/wasix/sdks/ts/src/resources/extensions.ts +++ b/src/wasix/sdks/ts/src/resources/extensions.ts @@ -893,6 +893,7 @@ function extensionMountTarget( const allowed = [ 'lib/postgresql/', 'share/proj/', + 'share/postgresql/gdal/', 'share/postgresql/extension/', 'share/postgresql/tsearch_data/', ]; @@ -900,6 +901,7 @@ function extensionMountTarget( 'lib/postgresql', 'share/proj', 'share/postgresql', + 'share/postgresql/gdal', 'share/postgresql/extension', 'share/postgresql/tsearch_data', ]); From 7bf3da8a596c4e97b39022e4b7cdf58a430ec416 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 05:44:10 +0000 Subject: [PATCH 02/29] test(react-native): cover GDAL in iOS PostGIS carriers --- .../sdks/react-native/tools/stage-ios-app.test.mts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/native/sdks/react-native/tools/stage-ios-app.test.mts b/src/native/sdks/react-native/tools/stage-ios-app.test.mts index ba2e04670..a325f8903 100755 --- a/src/native/sdks/react-native/tools/stage-ios-app.test.mts +++ b/src/native/sdks/react-native/tools/stage-ios-app.test.mts @@ -789,7 +789,15 @@ async function createFixture(root) { assert.deepEqual(iosDependencies, ['openssl']); } if (fixture.sqlName === 'postgis') { - assert.deepEqual(iosDependencies, ['geos', 'geos-c', 'json-c', 'libxml2', 'proj', 'sqlite']); + assert.deepEqual(iosDependencies, [ + 'gdal', + 'geos', + 'geos-c', + 'json-c', + 'libxml2', + 'proj', + 'sqlite', + ]); } const config = { ...fixture, @@ -976,6 +984,7 @@ async function main() { const frameworkNames = (await fs.readdir(path.join(output, 'frameworks', 'extensions'))).sort(); assert.deepEqual(frameworkNames, [ + 'liboliphaunt_dependency_gdal.xcframework', 'liboliphaunt_dependency_geos-c.xcframework', 'liboliphaunt_dependency_geos.xcframework', 'liboliphaunt_dependency_json-c.xcframework', From 4f7de3fccec3580c29275bd4969da8c922c7c85c Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 05:45:37 +0000 Subject: [PATCH 03/29] fix(extensions): reject cached GDAL builds missing runtime data --- src/native/runtime/bin/build-postgres18-linux.sh | 1 + src/native/runtime/bin/build-postgres18-macos.sh | 1 + src/native/runtime/bin/mobile-postgis-extensions.sh | 3 ++- .../runtime/bin/mobile-postgis-extensions.test.sh | 13 ++++++++++++- src/wasix/runtime/assets/build/build_wasix_gdal.sh | 2 ++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/native/runtime/bin/build-postgres18-linux.sh b/src/native/runtime/bin/build-postgres18-linux.sh index 79606457a..89862ad17 100755 --- a/src/native/runtime/bin/build-postgres18-linux.sh +++ b/src/native/runtime/bin/build-postgres18-linux.sh @@ -1188,6 +1188,7 @@ native_postgis_dependency_required_outputs=( "$native_postgis_dependency_root/gdal/bin/gdal-config" "$native_postgis_dependency_root/gdal/include/gdal.h" "$native_postgis_dependency_root/gdal/lib/libgdal.a" + "$native_postgis_dependency_root/gdal/share/gdal/gdalvrt.xsd" "$native_postgis_dependency_root/json-c/lib/libjson-c.a" "$native_postgis_dependency_root/sqlite/lib/libsqlite3.a" "$native_postgis_dependency_root/geos/lib/libgeos_c.a" diff --git a/src/native/runtime/bin/build-postgres18-macos.sh b/src/native/runtime/bin/build-postgres18-macos.sh index 91c667f77..e7c3657e2 100755 --- a/src/native/runtime/bin/build-postgres18-macos.sh +++ b/src/native/runtime/bin/build-postgres18-macos.sh @@ -1428,6 +1428,7 @@ native_postgis_dependency_required_outputs=( "$native_postgis_dependency_root/gdal/bin/gdal-config" "$native_postgis_dependency_root/gdal/include/gdal.h" "$native_postgis_dependency_root/gdal/lib/libgdal.a" + "$native_postgis_dependency_root/gdal/share/gdal/gdalvrt.xsd" "$native_postgis_dependency_root/json-c/lib/libjson-c.a" "$native_postgis_dependency_root/sqlite/lib/libsqlite3.a" "$native_postgis_dependency_root/geos/lib/libgeos_c.a" diff --git a/src/native/runtime/bin/mobile-postgis-extensions.sh b/src/native/runtime/bin/mobile-postgis-extensions.sh index b257aaee1..cfb78c52f 100644 --- a/src/native/runtime/bin/mobile-postgis-extensions.sh +++ b/src/native/runtime/bin/mobile-postgis-extensions.sh @@ -316,7 +316,8 @@ build_postgis_gdal_dependency() { local -a cmake_args local build_root="$work_root/gdal-$oliphaunt_mobile_target-build" local archive="$dependency_dir/lib/libgdal.a" - if [ -f "$archive" ] && [ -f "$dependency_dir/include/gdal.h" ] && [ -x "$dependency_dir/bin/gdal-config" ]; then + if [ -f "$archive" ] && [ -f "$dependency_dir/include/gdal.h" ] && + [ -f "$dependency_dir/share/gdal/gdalvrt.xsd" ] && [ -x "$dependency_dir/bin/gdal-config" ]; then oliphaunt_postgis_dependency_archive gdal "$archive" return 0 fi diff --git a/src/native/runtime/bin/mobile-postgis-extensions.test.sh b/src/native/runtime/bin/mobile-postgis-extensions.test.sh index c2543bc43..6e492e8fa 100755 --- a/src/native/runtime/bin/mobile-postgis-extensions.test.sh +++ b/src/native/runtime/bin/mobile-postgis-extensions.test.sh @@ -27,13 +27,16 @@ oliphaunt_postgis_selected() { } cmake_args_file="$test_root/cmake-args.txt" +dependency_builds=0 oliphaunt_postgis_cmake_install() { local dependency_dir="$3" shift 3 printf '%s\n' "$@" > "$cmake_args_file" - mkdir -p "$dependency_dir/lib" "$dependency_dir/include" "$dependency_dir/bin" + dependency_builds=$((dependency_builds + 1)) + mkdir -p "$dependency_dir/lib" "$dependency_dir/include" "$dependency_dir/bin" "$dependency_dir/share/gdal" : > "$dependency_dir/lib/libgdal.a" : > "$dependency_dir/include/gdal.h" + : > "$dependency_dir/share/gdal/gdalvrt.xsd" printf '#!/usr/bin/env bash\nexit 0\n' > "$dependency_dir/bin/gdal-config" chmod +x "$dependency_dir/bin/gdal-config" } @@ -42,6 +45,14 @@ build_postgis_gdal_dependency grep -Fx -- '-DCMAKE_BUILD_TYPE=Release' "$cmake_args_file" >/dev/null grep -Fx -- '-DBUILD_PYTHON_BINDINGS=OFF' "$cmake_args_file" >/dev/null grep -Fx -- '-DBUILD_PYTHON_BINDINGS_OLD_VAL=OFF' "$cmake_args_file" >/dev/null +build_postgis_gdal_dependency +[ "$dependency_builds" -eq 1 ] +rm "$mobile_static_dependency_root/gdal/share/gdal/gdalvrt.xsd" +build_postgis_gdal_dependency +[ "$dependency_builds" -eq 2 ] || { + echo "mobile GDAL cache accepted missing runtime data" >&2 + exit 1 +} resolved_gdal_archive="$(oliphaunt_mobile_static_dependency_archive_for_root "$mobile_static_dependency_root" gdal)" [ "$resolved_gdal_archive" = "$mobile_static_dependency_root/gdal/lib/libgdal.a" ] || { echo "mobile packager did not resolve the staged GDAL archive" >&2 diff --git a/src/wasix/runtime/assets/build/build_wasix_gdal.sh b/src/wasix/runtime/assets/build/build_wasix_gdal.sh index e0ea88314..babcb94e4 100755 --- a/src/wasix/runtime/assets/build/build_wasix_gdal.sh +++ b/src/wasix/runtime/assets/build/build_wasix_gdal.sh @@ -57,6 +57,7 @@ cmake=static-core-vrt-gtiff-internal-codecs-no-apps-no-optional-drivers" if [ -f "$GDAL_PREFIX/.oliphaunt-wasix-gdal-build" ] && [ -f "$GDAL_PREFIX/include/gdal.h" ] && [ -f "$GDAL_PREFIX/lib/libgdal.a" ] && + [ -f "$GDAL_PREFIX/share/gdal/gdalvrt.xsd" ] && [ -x "$GDAL_PREFIX/bin/gdal-config" ] && [ "$(cat "$GDAL_PREFIX/.oliphaunt-wasix-gdal-build")" = "$stamp" ]; then echo "$GDAL_PREFIX" @@ -119,6 +120,7 @@ fi test -f "$GDAL_PREFIX/include/gdal.h" test -f "$GDAL_PREFIX/include/ogr_api.h" test -f "$GDAL_PREFIX/lib/libgdal.a" +test -f "$GDAL_PREFIX/share/gdal/gdalvrt.xsd" test -x "$GDAL_PREFIX/bin/gdal-config" printf '%s\n' "$stamp" > "$GDAL_PREFIX/.oliphaunt-wasix-gdal-build" echo "$GDAL_PREFIX" From dab534cba73fcf714a2e5acffc5e0765f0898f60 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 05:49:48 +0000 Subject: [PATCH 04/29] fix(extensions): report the pinned PostGIS source revision --- src/extensions/external/postgis/tools/build_wasix.sh | 2 ++ src/native/runtime/bin/build-postgres18-linux.sh | 2 ++ src/native/runtime/bin/build-postgres18-macos.sh | 2 ++ src/native/runtime/bin/mobile-postgis-extensions.sh | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/extensions/external/postgis/tools/build_wasix.sh b/src/extensions/external/postgis/tools/build_wasix.sh index fd596b5e0..8a1ccef90 100755 --- a/src/extensions/external/postgis/tools/build_wasix.sh +++ b/src/extensions/external/postgis/tools/build_wasix.sh @@ -211,6 +211,8 @@ export CPPFLAGS="$("$ROOT/pg_config_wasix.sh" --cppflags) -I$LIBICONV_PREFIX/inc export ac_cv_lib_pq_PQserverVersion=yes export ac_cv_lib_xml2_xmlInitParser=yes oliphaunt_postgis_enable_reproducible_time "$REPO_ROOT" + # Upstream revision generation must identify PostGIS, not the enclosing repository. + export GIT_DIR="$POSTGIS_SOURCE_DIR/.git" [ "$SOURCE_DATE_EPOCH" = "$source_date_epoch" ] || { echo "PostGIS WASIX build epoch changed after fingerprinting" >&2 exit 1 diff --git a/src/native/runtime/bin/build-postgres18-linux.sh b/src/native/runtime/bin/build-postgres18-linux.sh index 89862ad17..e5ff0a5e2 100755 --- a/src/native/runtime/bin/build-postgres18-linux.sh +++ b/src/native/runtime/bin/build-postgres18-linux.sh @@ -1633,6 +1633,8 @@ build_postgis_extension() { # shellcheck source=/dev/null . "$postgis_time_helper" oliphaunt_postgis_enable_reproducible_time "$repo_root" + # Upstream revision generation must identify PostGIS, not the enclosing repository. + export GIT_DIR="$checkout/.git" export CC="$cc_string" export "${postgis_configure_env[@]}" if [ ! -f configure ]; then diff --git a/src/native/runtime/bin/build-postgres18-macos.sh b/src/native/runtime/bin/build-postgres18-macos.sh index e7c3657e2..7200c3915 100755 --- a/src/native/runtime/bin/build-postgres18-macos.sh +++ b/src/native/runtime/bin/build-postgres18-macos.sh @@ -2187,6 +2187,8 @@ build_postgis_extension() { # shellcheck source=/dev/null . "$postgis_time_helper" oliphaunt_postgis_enable_reproducible_time "$repo_root" + # Upstream revision generation must identify PostGIS, not the enclosing repository. + export GIT_DIR="$checkout/.git" local -a embedded_postgis_make_args=() local embedded_postgis_has_ldflags=0 local embedded_postgis_ldflags="$embedded_module_be_dllibs" diff --git a/src/native/runtime/bin/mobile-postgis-extensions.sh b/src/native/runtime/bin/mobile-postgis-extensions.sh index cfb78c52f..fc362faa7 100644 --- a/src/native/runtime/bin/mobile-postgis-extensions.sh +++ b/src/native/runtime/bin/mobile-postgis-extensions.sh @@ -872,6 +872,8 @@ build_postgis_mobile_static_extension_objects() { # shellcheck source=/dev/null . "$time_helper" oliphaunt_postgis_enable_reproducible_time "$repo_root" + # Upstream revision generation must identify PostGIS, not the enclosing repository. + export GIT_DIR="$source_dir/.git" [ "$SOURCE_DATE_EPOCH" = "$source_date_epoch" ] || oliphaunt_postgis_fail "PostGIS build epoch changed after fingerprinting" ./autogen.sh >> "$make_log" 2>&1 From ca0664c5b2247efd3644345430dde77b784c50e7 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 05:57:21 +0000 Subject: [PATCH 05/29] fix(extensions): align JSON-C configure metadata with source pin --- src/native/runtime/bin/build-postgres18-linux.sh | 2 +- src/native/runtime/bin/build-postgres18-macos.sh | 2 +- src/native/runtime/bin/mobile-postgis-extensions.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/native/runtime/bin/build-postgres18-linux.sh b/src/native/runtime/bin/build-postgres18-linux.sh index e5ff0a5e2..4744211af 100755 --- a/src/native/runtime/bin/build-postgres18-linux.sh +++ b/src/native/runtime/bin/build-postgres18-linux.sh @@ -1528,7 +1528,7 @@ case "\$pkg" in json-c) case " \$* " in *" --exists "*) exit 0 ;; - *" --modversion "*) echo "0.18"; exit 0 ;; + *" --modversion "*) echo "0.19"; exit 0 ;; *" --cflags "*) echo "-I$native_postgis_dependency_root/json-c/include -I$native_postgis_dependency_root/json-c/include/json-c"; exit 0 ;; *" --libs "*) echo "-L$native_postgis_dependency_root/json-c/lib -ljson-c"; exit 0 ;; esac diff --git a/src/native/runtime/bin/build-postgres18-macos.sh b/src/native/runtime/bin/build-postgres18-macos.sh index 7200c3915..1195586f2 100755 --- a/src/native/runtime/bin/build-postgres18-macos.sh +++ b/src/native/runtime/bin/build-postgres18-macos.sh @@ -1799,7 +1799,7 @@ case "\$pkg" in json-c) case " \$* " in *" --exists "*) exit 0 ;; - *" --modversion "*) echo "0.18"; exit 0 ;; + *" --modversion "*) echo "0.19"; exit 0 ;; *" --cflags "*) echo "-I$native_postgis_dependency_root/json-c/include -I$native_postgis_dependency_root/json-c/include/json-c"; exit 0 ;; *" --libs "*) echo "-L$native_postgis_dependency_root/json-c/lib -ljson-c"; exit 0 ;; esac diff --git a/src/native/runtime/bin/mobile-postgis-extensions.sh b/src/native/runtime/bin/mobile-postgis-extensions.sh index fc362faa7..2ddbbd5ff 100644 --- a/src/native/runtime/bin/mobile-postgis-extensions.sh +++ b/src/native/runtime/bin/mobile-postgis-extensions.sh @@ -513,7 +513,7 @@ case "\$pkg" in json-c) case " \$* " in *" --exists "*) exit 0 ;; - *" --modversion "*) echo "0.18"; exit 0 ;; + *" --modversion "*) echo "0.19"; exit 0 ;; *" --cflags "*) echo "-I$mobile_static_dependency_root/json-c/include -I$mobile_static_dependency_root/json-c/include/json-c"; exit 0 ;; *" --libs "*) echo "-L$mobile_static_dependency_root/json-c/lib -ljson-c"; exit 0 ;; esac From b105def6ae553488fee9162f95cde86cd81670b9 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 06:00:29 +0000 Subject: [PATCH 06/29] test(kotlin): retain the canonical extension archive limits --- .../OliphauntExtensionCatalogContractTest.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java index 29f226dd2..0b6e3b128 100644 --- a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java +++ b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java @@ -138,22 +138,20 @@ private static void validatesPublicTarGzArchivePreflight() throws Exception { try { PublicTarGzArchivePreflight.Limits extensionLimits = PublicTarGzArchivePreflight.extensionArtifactLimits(); - equal(1_073_741_824L, extensionLimits.maxCompressedBytes(), "extension compressed limit"); - equal(2_147_483_648L, extensionLimits.maxExpandedBytes(), "extension expanded limit"); - equal(1_073_741_824L, extensionLimits.maxEntryBytes(), "extension member limit"); + equal(134_217_728L, extensionLimits.maxCompressedBytes(), "extension compressed limit"); + equal(536_870_912L, extensionLimits.maxExpandedBytes(), "extension expanded limit"); + equal(268_435_456L, extensionLimits.maxEntryBytes(), "extension member limit"); equal(4_096, extensionLimits.maxEntries(), "extension member-count limit"); equal( extensionLimits.maxExpandedBytes(), PublicTarGzArchivePreflight.expansionLimitForContractTest(1, extensionLimits), "extension policy must not add a Java-only compression-ratio limit"); - // The qualified GDAL leaf exceeded 512 MiB. Combine its minimum proven - // size with the prior exact PostGIS high-water marks conservatively. if (!(64_676_748L <= extensionLimits.maxCompressedBytes() - && 882_492_607L <= extensionLimits.maxExpandedBytes() - && 536_870_913L <= extensionLimits.maxEntryBytes() + && 345_621_694L <= extensionLimits.maxExpandedBytes() + && 154_827_564L <= extensionLimits.maxEntryBytes() && 27 <= extensionLimits.maxEntries())) { throw new AssertionError( - "shared extension archive policy must admit the qualified Android ARM64 PostGIS leaf envelope"); + "shared extension archive policy must admit the observed Android ARM64 PostGIS leaf shape"); } List validEntries = List.of( From cb1f9e0f2b7e0eeda155a8ec1375baae6b275967 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 06:56:53 +0000 Subject: [PATCH 07/29] fix(wasix): isolate PostGIS builds and refresh module exports --- src/extensions/external/postgis/tools/build_wasix.sh | 6 +++--- src/wasix/runtime/assets/generated/wasix-dl.exports | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/extensions/external/postgis/tools/build_wasix.sh b/src/extensions/external/postgis/tools/build_wasix.sh index 8a1ccef90..1d2901c40 100755 --- a/src/extensions/external/postgis/tools/build_wasix.sh +++ b/src/extensions/external/postgis/tools/build_wasix.sh @@ -95,7 +95,7 @@ if [ -f "$POSTGIS_BUILD_DIR/.oliphaunt-wasix-postgis-build" ] && exit 0 fi -{ +( rm -rf "$POSTGIS_BUILD_DIR" mkdir -p "$(dirname "$POSTGIS_BUILD_DIR")" oliphaunt_wasix_copy_source_clean "$POSTGIS_SOURCE_DIR" "$POSTGIS_BUILD_DIR" @@ -204,7 +204,7 @@ EOF export CXX=wasixcc++ export AR=wasixar export RANLIB=wasixranlib -export CPPFLAGS="$("$ROOT/pg_config_wasix.sh" --cppflags) -I$LIBICONV_PREFIX/include" + export CPPFLAGS="$("$ROOT/pg_config_wasix.sh" --cppflags) -I$LIBICONV_PREFIX/include" export CFLAGS="$OLIPHAUNT_WASM_PROFILE_CFLAGS -fPIC -fvisibility=hidden -Wno-unused-command-line-argument" export CXXFLAGS="$OLIPHAUNT_WASM_PROFILE_CFLAGS -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -Wno-unused-command-line-argument" export LDFLAGS="-L$LIBICONV_PREFIX/lib -L$SQLITE_PREFIX/lib -liconv -lsqlite3 -lc++ -lc++abi -lunwind" @@ -377,7 +377,7 @@ EOF "$POSTGIS_BUILD_DIR/share/extension-raster/" cp "$POSTGIS_BUILD_DIR/extensions/postgis_raster/sql/"*.sql \ "$POSTGIS_BUILD_DIR/share/extension-raster/" -} >&2 +) >&2 oliphaunt_wasix_extension_build_outputs_exist "$REPO_ROOT" postgis "$POSTGIS_BUILD_DIR" printf '%s\n' "$stamp" > "$POSTGIS_BUILD_DIR/.oliphaunt-wasix-postgis-build" diff --git a/src/wasix/runtime/assets/generated/wasix-dl.exports b/src/wasix/runtime/assets/generated/wasix-dl.exports index 360dee53f..10cfd3d95 100644 --- a/src/wasix/runtime/assets/generated/wasix-dl.exports +++ b/src/wasix/runtime/assets/generated/wasix-dl.exports @@ -506,7 +506,6 @@ asin assign_expr_collations atan2l atexit -atof atoi be_lo_unlink before_shmem_exit @@ -960,7 +959,6 @@ namein nanosleep network_cmp new_object_addresses -nextafterf nocache_index_getattr nocachegetattr nodeToString @@ -1149,7 +1147,6 @@ repalloc_huge reservoir_get_next_S reservoir_init_selection_state resetStringInfo -roundf s_lock sampler_random_fract sampler_random_init_state @@ -1176,7 +1173,6 @@ smgrnblocks smgropen smgrpin smgrtruncate -sprintf sqrtl sscanf standard_ExecutorEnd From 7bef6cc4061366cfe899aa02e2098cc4d0e0541e Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 06:56:53 +0000 Subject: [PATCH 08/29] test(wasix): exercise raster in the browser worker --- src/examples/wasix/browser/main.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/examples/wasix/browser/main.ts b/src/examples/wasix/browser/main.ts index e9da7f498..3090c8026 100644 --- a/src/examples/wasix/browser/main.ts +++ b/src/examples/wasix/browser/main.ts @@ -179,6 +179,10 @@ async function expectLargePostgisWorkerModule(): Promise { const database = await WorkerOliphaunt.open({ seed: standardSeed, extensions: [postgis] }); try { await database.execute('CREATE EXTENSION postgis'); + const { default: fixture } = await import('../../../test-fixtures/extensions/postgis.sql?raw'); + for (const statement of fixture.split('-- oliphaunt-statement')) { + await database.execute(statement); + } const version = await readPostgisVersion(database); await database.queryRaw('CREATE TEMP TABLE postgis_nested_error_catch(value integer)'); await database.queryRaw( From b849fe961583653c30851201b59dc3b3649ed3a5 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 12:40:44 +0530 Subject: [PATCH 09/29] feat(extensions): complete PostGIS companion coverage and artifact contracts (#300) * feat(extensions): complete PostGIS companion coverage and artifact contracts * fix(extensions): retain historical PostGIS data requirements * refactor(extensions): remove legacy PostGIS compatibility paths --- .../src/generated/extension-smoke.ts | 12 +- .../native/tools/create-artifact.test.mts | 4 + .../tools/extension-artifact-packager.mts | 82 +++-- .../extension-artifact-packager.test.mts | 170 ++++++++++ .../tools/windows-extension-sources.mts | 198 +++++++++++- .../tools/windows-extension-sources.test.mts | 115 ++++++- .../tools/build-extension-ci-artifacts.mts | 35 ++- .../packages/tools/check-carriers.mts | 33 +- .../packages/tools/check-carriers.test.mts | 17 +- .../tools/extension-artifact-inventory.mts | 122 +++++++- .../extension-artifact-inventory.test.mts | 117 ++++++- .../extension-wasix-npm-packages.test.mts | 4 +- .../package-extension-release-carriers.mts | 56 ++-- src/extensions/catalog/extensions.source.json | 2 +- src/extensions/external/postgis/recipe.toml | 7 + .../targets/native-static-registry.toml | 3 - .../external/postgis/targets/native.toml | 4 +- .../external/postgis/targets/wasix.toml | 18 +- .../external/postgis/tools/build_wasix.sh | 38 ++- .../postgis/tools/windows/build-sql.sh | 6 + .../postgis/tools/windows/meson.build.in | 8 + .../generated/extensions.catalog.json | 4 +- src/extensions/generated/sdk/extensions.json | 247 ++++++++++++++- .../generated/wasix/extensions.json | 10 +- src/extensions/tools/extension-model.test.mts | 15 + .../tools/extension-projections.mts | 25 +- .../runtime/bin/build-postgres18-linux.sh | 13 +- .../runtime/bin/build-postgres18-macos.sh | 14 +- .../runtime/bin/build-postgres18-windows.sh | 13 +- .../runtime/bin/mobile-postgis-extensions.sh | 62 +++- .../bin/mobile-postgis-extensions.test.sh | 67 ++++ src/native/rust-bindings/src/extension.rs | 23 ++ .../rust-bindings/src/generated/extensions.rs | 99 +++++- .../src/liboliphaunt/root/extensions.rs | 36 +-- .../liboliphaunt/root/runtime/cache_key.rs | 224 ++++++++++++-- .../src/liboliphaunt/root/runtime/install.rs | 291 +++++++++++++++--- .../android/OliphauntExtensionCatalog.java | 9 +- .../ResolveOliphauntAndroidAssetsTask.java | 73 +++-- .../android/extension-legal-catalog.json | 2 +- .../oliphaunt/android/extensions.properties | 43 ++- ...OliphauntExtensionCatalogContractTest.java | 68 +++- .../react-native/src/generated/extensions.ts | 251 ++++++++++++++- .../tools/mobile-extension-artifact-paths.mts | 6 + .../mobile-extension-artifact-paths.test.mts | 2 + .../sdks/react-native/tools/stage-ios-app.mts | 92 ++++-- .../react-native/tools/stage-ios-app.test.mts | 104 ++++++- .../tools/stage-mobile-qualification.test.mts | 2 + .../tools/validate-mobile-runtime-files.mts | 13 +- src/native/sdks/rust/src/server.rs | 1 - .../cube/manifest.properties | 2 + .../earthdistance/manifest.properties | 2 + .../fuzzystrmatch/LICENSE | 1 + .../THIRD_PARTY_LICENSES/PostgreSQL-COPYRIGHT | 1 + .../fuzzystrmatch/THIRD_PARTY_NOTICES.md | 1 + .../files/lib/postgresql/fuzzystrmatch.dylib | 1 + .../extension/fuzzystrmatch--1.5.sql | 1 + .../extension/fuzzystrmatch.control | 2 + .../fuzzystrmatch/manifest.properties | 24 ++ .../pgtap/manifest.properties | 2 + .../extension/postgis_raster--3.6.1.sql | 2 + .../extension/postgis_raster.control | 1 + .../postgis_tiger_geocoder--3.6.1.sql | 2 + .../extension/postgis_tiger_geocoder.control | 1 + .../extension/postgis_topology--3.6.1.sql | 2 + .../extension/postgis_topology.control | 1 + .../postgis/manifest.properties | 4 +- .../Fixtures/swiftpm-extension-selection.json | 84 ++++- .../tools/extension-resource-inventory.mts | 50 ++- .../extension-resource-inventory.test.mts | 93 ++++++ .../sdks/swift/tools/ios-carrier-manifest.mts | 4 + .../swift/tools/ios-carrier-manifest.test.mts | 15 + .../swift/tools/render-extension-products.mts | 7 + .../swift/tools/swift-carrier-resolver.mts | 6 + .../tools/swift-carrier-resolver.test.mts | 26 +- .../ts/src/__tests__/asset-resolver.test.ts | 162 +++++++++- .../sdks/ts/src/generated/extensions.ts | 249 ++++++++++++++- src/native/sdks/ts/src/native/assets.ts | 140 +++++++-- src/test-fixtures/extensions/postgis.sql | 28 ++ .../src/oliphaunt/generated_extensions.rs | 6 +- tools/release/publication-lock.mts | 12 +- tools/release/publication-lock.test.mts | 6 +- 81 files changed, 3436 insertions(+), 362 deletions(-) create mode 100644 src/extensions/artifacts/native/tools/extension-artifact-packager.test.mts create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/LICENSE create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/THIRD_PARTY_LICENSES/PostgreSQL-COPYRIGHT create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/THIRD_PARTY_NOTICES.md create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/lib/postgresql/fuzzystrmatch.dylib create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/share/postgresql/extension/fuzzystrmatch--1.5.sql create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/share/postgresql/extension/fuzzystrmatch.control create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/manifest.properties create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_raster--3.6.1.sql create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_raster.control create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_tiger_geocoder--3.6.1.sql create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_tiger_geocoder.control create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_topology--3.6.1.sql create mode 100644 src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_topology.control diff --git a/src/examples/native/react-native-expo/src/generated/extension-smoke.ts b/src/examples/native/react-native-expo/src/generated/extension-smoke.ts index 7452feb56..92e8ccd8b 100644 --- a/src/examples/native/react-native-expo/src/generated/extension-smoke.ts +++ b/src/examples/native/react-native-expo/src/generated/extension-smoke.ts @@ -7,7 +7,7 @@ export type GeneratedMobileExtensionProof = { readonly selectedExtensionDependencies: readonly string[]; }; -export const GENERATED_MOBILE_EXTENSION_METADATA_SHA256 = "26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a" as const; +export const GENERATED_MOBILE_EXTENSION_METADATA_SHA256 = "dbe1ab284693ef923cb12e35e1c75bcc7fc1ed42af1fecf9fe9fee0aaf0ef8d0" as const; export const GENERATED_MOBILE_EXTENSION_PLAN = [ { @@ -165,7 +165,9 @@ export const GENERATED_MOBILE_EXTENSION_PLAN = [ { "sqlName": "postgis", "createsExtension": true, - "selectedExtensionDependencies": [] + "selectedExtensionDependencies": [ + "fuzzystrmatch" + ] }, { "sqlName": "seg", @@ -358,11 +360,15 @@ export const GENERATED_MOBILE_EXTENSION_SMOKE = { ], "postgis": [ "CREATE EXTENSION IF NOT EXISTS postgis_raster;", + "CREATE EXTENSION IF NOT EXISTS postgis_topology;", + "CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;", + "CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder;", + "DO $$ BEGIN\n IF EXISTS (SELECT 1 FROM topology.topology WHERE name = 'oliphaunt_postgis_topology') THEN\n PERFORM topology.DropTopology('oliphaunt_postgis_topology');\n END IF;\n PERFORM topology.CreateTopology('oliphaunt_postgis_topology', 4326);\n PERFORM topology.ST_AddIsoNode('oliphaunt_postgis_topology', NULL, ST_SetSRID(ST_MakePoint(1, 1), 4326));\nEND $$;", "DROP TABLE IF EXISTS oliphaunt_postgis_points;", "CREATE TABLE oliphaunt_postgis_points(id int PRIMARY KEY, geom geometry(Point, 4326));", "INSERT INTO oliphaunt_postgis_points VALUES\n (1, ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326)),\n (2, ST_SetSRID(ST_MakePoint(-71.061, 48.433), 4326));", "CREATE INDEX oliphaunt_postgis_points_gix ON oliphaunt_postgis_points USING GIST (geom);", - "-- oliphaunt-verify\nDO $$\nDECLARE\n distance float8;\n srid int;\n area float8;\n polygons int;\n nearby int;\n gdal_drivers int;\n raster_roundtrip raster;\n raster_value float8;\nBEGIN\n SET LOCAL postgis.gdal_enabled_drivers = 'GTiff';\n SELECT ST_Distance(ST_GeomFromText('POINT(0 0)'), ST_GeomFromText('POINT(3 4)')) INTO distance;\n IF distance <> 5 THEN\n RAISE EXCEPTION 'postgis geometry distance failed: %', distance;\n END IF;\n IF ST_AsText(ST_Buffer(ST_GeomFromText('POINT(0 0)'), 1, 'quad_segs=1')) IS NULL THEN\n RAISE EXCEPTION 'postgis buffer failed';\n END IF;\n IF NOT ST_Within(\n ST_GeomFromText('POINT(0.5 0.5)'),\n ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))')\n ) THEN\n RAISE EXCEPTION 'postgis within failed';\n END IF;\n SELECT ST_SRID(ST_Transform(ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326), 3857)) INTO srid;\n IF srid <> 3857 THEN\n RAISE EXCEPTION 'postgis transform failed: %', srid;\n END IF;\n SELECT ST_Area(ST_Transform(ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-71.1776848522251 42.3902896512902,-71.1776843766797 42.3903701743239,-71.1775844305465 42.3903829478009,-71.1775825927231 42.3902893647987,-71.1776848522251 42.3902896512902)')), 4326), 26986)) INTO area;\n IF area <= 0 THEN\n RAISE EXCEPTION 'postgis projected area failed: %', area;\n END IF;\n SELECT ST_NumGeometries(ST_Polygonize(ARRAY[\n ST_GeomFromText('LINESTRING(0 0, 1 0)'),\n ST_GeomFromText('LINESTRING(1 0, 1 1)'),\n ST_GeomFromText('LINESTRING(1 1, 0 1)'),\n ST_GeomFromText('LINESTRING(0 1, 0 0)')\n ])) INTO polygons;\n IF polygons <> 1 THEN\n RAISE EXCEPTION 'postgis polygonize failed: %', polygons;\n END IF;\n SELECT count(*) INTO nearby\n FROM oliphaunt_postgis_points\n WHERE ST_DWithin(\n geom::geography,\n ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326)::geography,\n 200\n );\n IF nearby <> 2 THEN\n RAISE EXCEPTION 'postgis dwithin failed: %', nearby;\n END IF;\n SELECT count(*) INTO gdal_drivers\n FROM ST_GDALDrivers()\n WHERE short_name = 'GTiff' AND can_read AND can_write;\n IF gdal_drivers <> 1 THEN\n RAISE EXCEPTION 'postgis raster GeoTIFF driver is unavailable';\n END IF;\n SELECT ST_FromGDALRaster(\n ST_AsGDALRaster(\n ST_AddBand(\n ST_MakeEmptyRaster(2, 2, 0, 0, 1, -1, 0, 0, 4326),\n '8BUI'::text,\n 7::double precision,\n 0::double precision\n ),\n 'GTiff'\n )\n ) INTO raster_roundtrip;\n SELECT ST_Value(raster_roundtrip, 1, 1, 1) INTO raster_value;\n IF raster_value IS DISTINCT FROM 7 OR ST_SRID(raster_roundtrip) IS DISTINCT FROM 4326\n OR ST_Width(raster_roundtrip) IS DISTINCT FROM 2 OR ST_Height(raster_roundtrip) IS DISTINCT FROM 2 THEN\n RAISE EXCEPTION 'postgis raster GDAL round-trip failed: value %, srid %',\n raster_value,\n ST_SRID(raster_roundtrip);\n END IF;\nEND $$;" + "-- oliphaunt-verify\nDO $$\nDECLARE\n distance float8;\n srid int;\n area float8;\n polygons int;\n nearby int;\n gdal_drivers int;\n raster_roundtrip raster;\n raster_value float8;\n topology_nodes int;\n normalized tiger.norm_addy;\nBEGIN\n SELECT count(*) INTO topology_nodes FROM oliphaunt_postgis_topology.node\n WHERE ST_Equals(geom, ST_SetSRID(ST_MakePoint(1, 1), 4326));\n IF topology_nodes <> 1 OR EXISTS (SELECT 1 FROM topology.ValidateTopology('oliphaunt_postgis_topology')) THEN\n RAISE EXCEPTION 'postgis topology persistence or validation failed: % nodes', topology_nodes;\n END IF;\n normalized := tiger.normalize_address('1 Devonshire Place, Boston, MA 02109');\n IF normalized.address IS DISTINCT FROM 1 OR normalized.streetname IS DISTINCT FROM 'Devonshire'\n OR normalized.streettypeabbrev IS DISTINCT FROM 'Pl' OR normalized.location IS DISTINCT FROM 'Boston'\n OR normalized.stateabbrev IS DISTINCT FROM 'MA' OR normalized.zip IS DISTINCT FROM '02109'\n OR normalized.parsed IS DISTINCT FROM true THEN\n RAISE EXCEPTION 'postgis tiger address normalization failed: %', normalized;\n END IF;\n SET LOCAL postgis.gdal_enabled_drivers = 'GTiff';\n SELECT ST_Distance(ST_GeomFromText('POINT(0 0)'), ST_GeomFromText('POINT(3 4)')) INTO distance;\n IF distance <> 5 THEN\n RAISE EXCEPTION 'postgis geometry distance failed: %', distance;\n END IF;\n IF ST_AsText(ST_Buffer(ST_GeomFromText('POINT(0 0)'), 1, 'quad_segs=1')) IS NULL THEN\n RAISE EXCEPTION 'postgis buffer failed';\n END IF;\n IF NOT ST_Within(\n ST_GeomFromText('POINT(0.5 0.5)'),\n ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))')\n ) THEN\n RAISE EXCEPTION 'postgis within failed';\n END IF;\n SELECT ST_SRID(ST_Transform(ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326), 3857)) INTO srid;\n IF srid <> 3857 THEN\n RAISE EXCEPTION 'postgis transform failed: %', srid;\n END IF;\n SELECT ST_Area(ST_Transform(ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-71.1776848522251 42.3902896512902,-71.1776843766797 42.3903701743239,-71.1775844305465 42.3903829478009,-71.1775825927231 42.3902893647987,-71.1776848522251 42.3902896512902)')), 4326), 26986)) INTO area;\n IF area <= 0 THEN\n RAISE EXCEPTION 'postgis projected area failed: %', area;\n END IF;\n SELECT ST_NumGeometries(ST_Polygonize(ARRAY[\n ST_GeomFromText('LINESTRING(0 0, 1 0)'),\n ST_GeomFromText('LINESTRING(1 0, 1 1)'),\n ST_GeomFromText('LINESTRING(1 1, 0 1)'),\n ST_GeomFromText('LINESTRING(0 1, 0 0)')\n ])) INTO polygons;\n IF polygons <> 1 THEN\n RAISE EXCEPTION 'postgis polygonize failed: %', polygons;\n END IF;\n SELECT count(*) INTO nearby\n FROM oliphaunt_postgis_points\n WHERE ST_DWithin(\n geom::geography,\n ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326)::geography,\n 200\n );\n IF nearby <> 2 THEN\n RAISE EXCEPTION 'postgis dwithin failed: %', nearby;\n END IF;\n SELECT count(*) INTO gdal_drivers\n FROM ST_GDALDrivers()\n WHERE short_name = 'GTiff' AND can_read AND can_write;\n IF gdal_drivers <> 1 THEN\n RAISE EXCEPTION 'postgis raster GeoTIFF driver is unavailable';\n END IF;\n SELECT ST_FromGDALRaster(\n ST_AsGDALRaster(\n ST_AddBand(\n ST_MakeEmptyRaster(2, 2, 0, 0, 1, -1, 0, 0, 4326),\n '8BUI'::text,\n 7::double precision,\n 0::double precision\n ),\n 'GTiff'\n )\n ) INTO raster_roundtrip;\n SELECT ST_Value(raster_roundtrip, 1, 1, 1) INTO raster_value;\n IF raster_value IS DISTINCT FROM 7 OR ST_SRID(raster_roundtrip) IS DISTINCT FROM 4326\n OR ST_Width(raster_roundtrip) IS DISTINCT FROM 2 OR ST_Height(raster_roundtrip) IS DISTINCT FROM 2 THEN\n RAISE EXCEPTION 'postgis raster GDAL round-trip failed: value %, srid %',\n raster_value,\n ST_SRID(raster_roundtrip);\n END IF;\nEND $$;" ], "seg": [ "DO $$ BEGIN IF '7(+-)1'::seg::text <> '6 .. 8' THEN RAISE EXCEPTION 'seg cast failed'; END IF; END $$;" diff --git a/src/extensions/artifacts/native/tools/create-artifact.test.mts b/src/extensions/artifacts/native/tools/create-artifact.test.mts index 7fc1ace03..526084ebc 100644 --- a/src/extensions/artifacts/native/tools/create-artifact.test.mts +++ b/src/extensions/artifacts/native/tools/create-artifact.test.mts @@ -9,6 +9,8 @@ const metadata = { sqlName: 'pgtap', createsExtension: true, nativeModuleStem: null, + sqlMembers: ['pgtap'], + nativeModuleStems: [], dependencies: [], dataFiles: [], extensionSqlFileNames: ['uninstall_pgtap.sql'], @@ -46,6 +48,8 @@ if (phase === 'prepare-binaries') { sqlName: 'auto_explain', createsExtension: false, nativeModuleStem: 'auto_explain', + sqlMembers: [], + nativeModuleStems: ['auto_explain'], extensionSqlFileNames: [], extensionSqlFilePrefixes: [], } diff --git a/src/extensions/artifacts/native/tools/extension-artifact-packager.mts b/src/extensions/artifacts/native/tools/extension-artifact-packager.mts index e2adc9f29..1fa077368 100755 --- a/src/extensions/artifacts/native/tools/extension-artifact-packager.mts +++ b/src/extensions/artifacts/native/tools/extension-artifact-packager.mts @@ -1,31 +1,31 @@ #!/usr/bin/env bun -import { fileURLToPath } from 'node:url'; +import { promises as fs } from 'node:fs'; import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { tarHeader } from '../../../../../tools/packaging/archive-directory.mts'; -import { promises as fs } from 'node:fs'; - -import { validateWindowsExtensionArtifactBinaryContract } from './stage-windows-binary-contract.mts'; import { inspectPlatformBinaryTree } from '../../../../../tools/packaging/platform-binary-contract.mts'; -import { - validateExtensionArtifactArchivePlan, - validateExtensionArtifactCompressedBytes, -} from '../../../tools/extension-artifact-archive-policy.mts'; -import { - extensionControlDefaultVersion, - isCanonicalExtensionInstallSql, - validateExtensionInstallSqlReachability, -} from '../../packages/tools/extension-artifact-inventory.mts'; +import { canonicalGzipSync } from '../../../../../tools/packaging/portable-archive.mts'; import { assertReleaseNoticesInArchive, stageReleaseNotices, } from '../../../../../tools/packaging/release-notices.mts'; -import { stageExtensionUpstreamLicenses } from '../../../tools/extension-upstream-licenses.mts'; -import { canonicalGzipSync } from '../../../../../tools/packaging/portable-archive.mts'; import { extensionSqlNames } from '../../../../../tools/release/release-artifact-targets.mts'; +import { + validateExtensionArtifactArchivePlan, + validateExtensionArtifactCompressedBytes, +} from '../../../tools/extension-artifact-archive-policy.mts'; +import { stageExtensionUpstreamLicenses } from '../../../tools/extension-upstream-licenses.mts'; import { loadNativeComponentContract, resolveNativeComponentClosure, } from '../../../tools/native-component-contract.mts'; +import { + extensionControlDefaultVersion, + extensionNativeArtifactClosure, + isCanonicalExtensionInstallSql, + validateExtensionInstallSqlReachability, +} from '../../packages/tools/extension-artifact-inventory.mts'; +import { validateWindowsExtensionArtifactBinaryContract } from './stage-windows-binary-contract.mts'; const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(scriptDir, '../../../../..'); @@ -785,6 +785,12 @@ function artifactMobilePrebuilt(args) { function extensionSqlFileBelongs(sqlName, fileName, extraSql, createsExtension) { return ( + extraSql.controls.includes(fileName) || + extraSql.controls.some( + (control) => + fileName.startsWith(`${control.slice(0, -'.control'.length)}--`) && + fileName.endsWith('.sql'), + ) || (createsExtension && fileName === `${sqlName}.control`) || (createsExtension && fileName === `${sqlName}.sql`) || (createsExtension && fileName.startsWith(`${sqlName}--`) && fileName.endsWith('.sql')) || @@ -793,7 +799,7 @@ function extensionSqlFileBelongs(sqlName, fileName, extraSql, createsExtension) ); } -async function extensionSqlMetadata(sqlName) { +async function extensionSqlMetadata(sqlName, target) { const prefixes = await extensionArtifactList(sqlName, 'extension_sql_file_prefixes'); const names = await extensionArtifactList(sqlName, 'extension_sql_file_names'); if (new Set(prefixes).size !== prefixes.length || new Set(names).size !== names.length) { @@ -810,7 +816,10 @@ async function extensionSqlMetadata(sqlName) { fail(`prebuilt extension '${sqlName}' SQL file prefix '${prefix}' must not contain '.'`); } } - return { names, prefixes }; + const closure = extensionNativeArtifactClosure(sqlName, target); + // Mobile combines companion objects into its primary static module. + if (!DESKTOP_NATIVE_TARGETS.has(target)) closure.modules = []; + return { names, prefixes, ...closure }; } async function ensureParent(file) { @@ -898,6 +907,21 @@ async function copySqlFiles(args, artifactRoot, artifactFiles, extraSql) { label: `prebuilt extension artifact ${artifactRoot}`, }); } + for (const controlFile of extraSql.controls) { + if (controlFile === `${args.sqlName}.control`) continue; + const controlPath = path.join(targetDir, controlFile); + if (!copiedFileNames.includes(controlFile)) { + fail( + `prebuilt extension artifact ${artifactRoot} is missing companion control ${controlFile}`, + ); + } + validateExtensionInstallSqlReachability({ + sqlName: controlFile.slice(0, -'.control'.length), + control: await fs.readFile(controlPath, 'utf8'), + fileNames: copiedFileNames, + label: `prebuilt extension artifact ${artifactRoot}`, + }); + } if (!args.createsExtension && copied === 0) { return; } @@ -969,25 +993,27 @@ function extensionMetadata(args, extraSql) { }; } -async function writeArtifactDirectory(artifactRoot, args) { +export async function writeArtifactDirectory(artifactRoot, args) { const filesRoot = path.join(artifactRoot, 'files'); - const extraSql = await extensionSqlMetadata(args.sqlName); + const extraSql = await extensionSqlMetadata(args.sqlName, args.nativeTarget); const metadata = extensionMetadata(args, extraSql); + const sqlMembers = sortedDeduped([ + ...(args.createsExtension ? [args.sqlName] : []), + ...extraSql.controls.map((control) => control.slice(0, -'.control'.length)), + ]); await copySqlFiles(args, artifactRoot, filesRoot, extraSql); for (const dataFile of metadata.dataFiles) { await copyRuntimeRelativeFile(args.runtime, filesRoot, `share/postgresql/${dataFile}`); } - if (metadata.nativeModuleFile.length > 0) { - await copyRuntimeRelativeFile( - args.runtime, - filesRoot, - `lib/postgresql/${metadata.nativeModuleFile}`, - ); + const modules = new Set(extraSql.modules); + if (metadata.nativeModuleFile.length > 0) modules.add(metadata.nativeModuleFile); + for (const module of modules) { + await copyRuntimeRelativeFile(args.runtime, filesRoot, `lib/postgresql/${module}`); if (DESKTOP_NATIVE_TARGETS.has(args.nativeTarget)) { await copyFileChecked( args.embeddedModuleRoot, - path.join(args.embeddedModuleRoot, metadata.nativeModuleFile), - path.join(filesRoot, 'lib/modules', metadata.nativeModuleFile), + path.join(args.embeddedModuleRoot, module), + path.join(filesRoot, 'lib/modules', module), ); } } @@ -1031,6 +1057,8 @@ async function writeArtifactDirectory(artifactRoot, args) { `staticSymbolAliases=${metadata.staticSymbolAliases.map((alias) => `${alias.sqlSymbol}:${alias.linkedSymbol}`).join(',')}`, `licenseFiles=${licenseFiles.join(',')}`, `licenseProfile=${licenseProfile}`, + `sqlMembers=${sqlMembers.join(',')}`, + `nativeModuleStems=${sortedDeduped([...modules].map((module) => module.replace(/\.(so|dylib|dll)$/u, ''))).join(',')}`, 'files=files', '', ].join('\n'); diff --git a/src/extensions/artifacts/native/tools/extension-artifact-packager.test.mts b/src/extensions/artifacts/native/tools/extension-artifact-packager.test.mts new file mode 100644 index 000000000..da0925a24 --- /dev/null +++ b/src/extensions/artifacts/native/tools/extension-artifact-packager.test.mts @@ -0,0 +1,170 @@ +import { test } from 'bun:test'; +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { + assertExtensionArtifactClosure, + validateExtensionArtifactEntries, +} from '../../packages/tools/extension-artifact-inventory.mts'; +import { writeArtifactDirectory } from './extension-artifact-packager.mts'; + +const repo = path.resolve(import.meta.dir, '../../../../..'); +const recipe = Bun.TOML.parse( + await fs.readFile(path.join(repo, 'src/extensions/external/postgis/recipe.toml'), 'utf8'), +); +const source = Bun.TOML.parse( + await fs.readFile(path.join(repo, 'src/extensions/external/postgis/source.toml'), 'utf8'), +); +const version = source['extension-control']['default-version']; +const controls = ['postgis', 'postgis_raster', 'postgis_topology', 'postgis_tiger_geocoder']; +const stems = ['postgis-3', 'postgis_raster-3', 'postgis_topology-3']; + +async function write(file, data) { + await fs.mkdir(path.dirname(file), { recursive: true }); + await fs.writeFile(file, data); +} + +async function entriesAt(root) { + const entries = new Map(); + for (const relative of await fs.readdir(root, { recursive: true })) { + const file = path.join(root, relative); + const stat = await fs.stat(file); + if (!stat.isFile()) continue; + const data = await fs.readFile(file); + entries.set(relative.split(path.sep).join('/'), { + data, + bytes: data.length, + mode: stat.mode & 0o777, + sha256: createHash('sha256').update(data).digest('hex'), + }); + } + return entries; +} + +for (const [target, suffix] of [ + ['linux-x64-gnu', '.so'], + ['macos-arm64', '.dylib'], + ['windows-x64-msvc', '.dll'], + ['android-arm64-v8a', '.so'], + ['ios-xcframework', '.dylib'], +]) { + test(`PostGIS ${target} freezes and stages its declared companion closure`, async () => { + const mobile = target === 'ios-xcframework' || target.startsWith('android-'); + const root = await fs.mkdtemp(path.join(os.tmpdir(), 'postgis-artifact-')); + const runtime = path.join(root, 'runtime'); + const embedded = path.join(root, 'embedded'); + const artifact = path.join(root, 'artifact'); + const dataFiles = recipe.artifacts.data_files.map((file) => + file.slice('share/postgresql/'.length), + ); + const metadata = { + sqlName: 'postgis', + createsExtension: true, + nativeModuleStem: 'postgis-3', + sqlMembers: [...controls].sort(), + nativeModuleStems: [...stems].sort(), + dependencies: [], + dataFiles, + extensionSqlFileNames: recipe.artifacts.extension_sql_file_names, + extensionSqlFilePrefixes: recipe.artifacts.extension_sql_file_prefixes, + sharedPreloadLibraries: [], + }; + const args = { + ...metadata, + runtime, + embeddedModuleRoot: embedded, + nativeTarget: target, + nativeModuleFile: `postgis-3${suffix}`, + nativeRuntimeProduct: 'liboliphaunt-native', + nativeRuntimeVersion: '1.2.3', + mobileStaticArchives: [], + mobileStaticDependencyArchives: [], + staticSymbolAliases: [], + mobilePrebuilt: false, + }; + try { + for (const name of controls) { + await write( + path.join(runtime, `share/postgresql/extension/${name}.control`), + `default_version = '${version}'\n`, + ); + await write( + path.join(runtime, `share/postgresql/extension/${name}--${version}.sql`), + `-- ${name} install\n`, + ); + } + for (const stem of stems) { + await write(path.join(runtime, `lib/postgresql/${stem}${suffix}`), `server ${stem}`); + await write(path.join(embedded, `${stem}${suffix}`), `embedded ${stem}`); + } + await write( + path.join(runtime, 'share/postgresql/extension/postgis_topology-foreign.control'), + 'foreign', + ); + await write( + path.join(runtime, `lib/postgresql/liboliphaunt_postgis_deps${suffix}`), + 'WASIX-only dependency', + ); + for (const file of dataFiles) await write(path.join(runtime, 'share/postgresql', file), file); + await writeArtifactDirectory(artifact, args); + const entries = await entriesAt(artifact); + const manifest = entries.get('manifest.properties').data.toString(); + assert(manifest.includes(`sqlMembers=${[...controls].sort().join(',')}\n`)); + if (mobile) { + assert(manifest.includes('nativeModuleStems=postgis-3\n')); + assertExtensionArtifactClosure( + { ...metadata, nativeModuleStems: ['postgis-3'] }, + target, + target, + ); + assert.throws( + () => assertExtensionArtifactClosure(metadata, target, target), + /source-owned closure/, + ); + for (const name of controls) + assert(entries.has(`files/share/postgresql/extension/${name}.control`)); + assert(entries.has(`files/lib/postgresql/postgis-3${suffix}`)); + assert(!entries.has(`files/lib/postgresql/postgis_topology-3${suffix}`)); + assert(!entries.has(`files/lib/modules/postgis-3${suffix}`)); + return; + } + const validate = (selected) => + validateExtensionArtifactEntries({ + entries: selected, + metadata, + target, + nativeRuntimeVersion: '1.2.3', + label: target, + }); + validate(entries); + assert(!entries.has('files/share/postgresql/extension/postgis_topology-foreign.control')); + assert(!entries.has(`files/lib/postgresql/liboliphaunt_postgis_deps${suffix}`)); + for (const stem of stems) { + assert.equal( + entries.get(`files/lib/postgresql/${stem}${suffix}`).data.toString(), + `server ${stem}`, + ); + assert.equal( + entries.get(`files/lib/modules/${stem}${suffix}`).data.toString(), + `embedded ${stem}`, + ); + } + for (const missing of [ + 'files/share/postgresql/extension/postgis_topology.control', + `files/share/postgresql/extension/postgis_tiger_geocoder--${version}.sql`, + `files/lib/postgresql/postgis_raster-3${suffix}`, + `files/lib/modules/postgis_topology-3${suffix}`, + ]) { + const incomplete = new Map(entries); + incomplete.delete(missing); + assert.throws(() => validate(incomplete), /missing|canonical installation script/); + } + await fs.unlink(path.join(embedded, `postgis_topology-3${suffix}`)); + await assert.rejects(writeArtifactDirectory(path.join(root, 'incomplete'), args), /ENOENT/); + } finally { + await fs.rm(root, { recursive: true, force: true }); + } + }); +} diff --git a/src/extensions/artifacts/native/tools/windows-extension-sources.mts b/src/extensions/artifacts/native/tools/windows-extension-sources.mts index 9690b8071..5b9919f11 100644 --- a/src/extensions/artifacts/native/tools/windows-extension-sources.mts +++ b/src/extensions/artifacts/native/tools/windows-extension-sources.mts @@ -68,6 +68,24 @@ export function pgxsSources(root: string) { return values.map((value) => value.slice(0, -2) + '.c'); } +// Preserve the pinned tiger SQL recipe's order (and Make's $^ deduplication). +// Only literal SQL prerequisites are supported; upstream expressions need review. +export function postgisSqlInputs(root: string, target: string) { + const makefile = read(path.join(root, 'Makefile.in')).replace(/\\\r?\n/g, ' '); + const rule = makefile.split(/\r?\n/).find((line) => line.startsWith(`${target}:`)); + assert(rule, `missing PostGIS SQL rule ${target}`); + const inputs = rule + .slice(target.length + 1) + .split('|')[0] + .trim() + .split(/\s+/); + assert( + inputs.length && inputs.every((input) => /^[\w./-]+\.sql(?:\.in)?$/.test(input)), + `unsupported PostGIS SQL prerequisites for ${target}`, + ); + return [...new Set(inputs)].map((input) => path.resolve(root, input)); +} + type Config = { repo: string; work: string; postgres: string; install: string }; function moduleRecipe( config: Config, @@ -193,6 +211,24 @@ function postgisDirectory(config: Config) { function postgisTemplates(config: Config) { return path.join(config.repo, 'src/extensions/external/postgis/tools/windows'); } +export function postgisModuleStems(config: Pick) { + const target = Bun.TOML.parse( + read(path.join(config.repo, 'src/extensions/external/postgis/targets/native.toml')), + ); + assert( + Array.isArray(target.module_files) && target.module_files.length, + 'missing PostGIS native modules', + ); + const stems = target.module_files.map((file: unknown) => { + assert(typeof file === 'string', 'invalid PostGIS native module'); + const stem = /^lib\/postgresql\/([\w-]+)\.so$/.exec(file)?.[1]; + assert(stem, `invalid PostGIS native module ${file}`); + return stem; + }); + assert.equal(new Set(stems).size, stems.length, 'duplicate PostGIS native modules'); + assert(stems.includes('postgis-3'), 'missing primary PostGIS native module'); + return stems; +} function expand(input: string, output: string, values: Record) { let text = read(input); for (const [key, value] of Object.entries(values)) text = text.replaceAll(`@${key}@`, value); @@ -303,7 +339,18 @@ export function preparePostgis(config: Config) { MODULEPATH: '$libdir/postgis_raster-3', }, ); - for (const file of ['raster/rt_core/librtcore.h', 'raster/rt_pg/rtpostgis.c']) { + for (const name of ['postgis_topology', 'postgis_tiger_geocoder']) { + expand( + path.join(directory, `extensions/${name}/${name}.control.in`), + path.join(directory, `extensions/${name}/${name}.control`), + { EXTVERSION: pgis.version }, + ); + } + for (const file of [ + 'raster/rt_core/librtcore.h', + 'raster/rt_pg/rtpostgis.c', + 'topology/postgis_topology.c', + ]) { replace( path.join(directory, file), '# define __attribute__ (x)', @@ -318,7 +365,14 @@ export function preparePostgis(config: Config) { path.join(postgisTemplates(config), 'flatgeobuf-compat.h'), path.join(directory, 'oliphaunt_flatgeobuf_windows_compat.h'), ); - for (const folder of ['postgis', 'libpgcommon', 'liblwgeom', 'raster/rt_core', 'raster/rt_pg']) { + for (const folder of [ + 'postgis', + 'libpgcommon', + 'liblwgeom', + 'raster/rt_core', + 'raster/rt_pg', + 'topology', + ]) { for (const file of glob(path.join(directory, folder), '**/*.{c,h}')) { const target = path.join(directory, folder, file), text = read(target); @@ -329,6 +383,13 @@ export function preparePostgis(config: Config) { if (updated !== text) write(target, updated); } } + for (const hook of ['_PG_init', '_PG_fini']) { + replace( + path.join(directory, 'topology/postgis_topology.c'), + `void ${hook}(void);`, + `PGDLLEXPORT void ${hook}(void);`, + ); + } replace( path.join(directory, 'postgis/postgis_legacy.c'), /^([ \t]*)Datum[ \t]+funcname[ \t]*\(PG_FUNCTION_ARGS\);[ \t]*\\\r?$/gm, @@ -417,6 +478,10 @@ export function postgisMeson(config: Config) { ...glob(directory, 'extensions/postgis/sql/postgis--*.sql'), 'extensions/postgis_raster/postgis_raster.control', ...glob(directory, 'extensions/postgis_raster/sql/postgis_raster--*.sql'), + 'extensions/postgis_topology/postgis_topology.control', + ...glob(directory, 'extensions/postgis_topology/sql/postgis_topology--*.sql'), + 'extensions/postgis_tiger_geocoder/postgis_tiger_geocoder.control', + ...glob(directory, 'extensions/postgis_tiger_geocoder/sql/postgis_tiger_geocoder--*.sql'), ]; const contrib = [ 'postgis/legacy.sql', @@ -428,6 +493,9 @@ export function postgisMeson(config: Config) { 'postgis/uninstall_legacy.sql', 'postgis/uninstall_postgis.sql', 'doc/postgis_comments.sql', + 'topology/topology.sql', + 'topology/topology_upgrade.sql', + 'topology/uninstall_topology.sql', ]; expand( path.join(postgisTemplates(config), 'meson.build.in'), @@ -476,8 +544,17 @@ export function postgisSql(config: Config, phase: string) { sql = path.join(directory, 'extensions/postgis/sql'); const raster = path.join(directory, 'raster/rt_pg'); const rasterSql = path.join(directory, 'extensions/postgis_raster/sql'); - mkdirSync(sql, { recursive: true }); - mkdirSync(rasterSql, { recursive: true }); + const topology = path.join(directory, 'topology'); + const topologyExtension = path.join(directory, 'extensions/postgis_topology'); + const topologySql = path.join(topologyExtension, 'sql'); + const tigerExtension = path.join(directory, 'extensions/postgis_tiger_geocoder'); + const tigerSql = path.join(tigerExtension, 'sql'); + const topologyBody = (text: string) => + withoutTransactions(text).replace(/^CREATE SCHEMA[^\r\n]*(?:\r?\n|$)/gm, ''); + const tigerDropSchema = + "SELECT postgis_extension_drop_if_exists('postgis_tiger_geocoder', 'DROP SCHEMA tiger_data');"; + for (const folder of [sql, rasterSql, topologySql, tigerSql]) + mkdirSync(folder, { recursive: true }); if (phase === 'postgis-sql-preprocess') { const template = ( input: string, @@ -540,6 +617,45 @@ export function postgisSql(config: Config, phase: string) { true, false, ); + for (const name of ['topology', 'topology_before_upgrade', 'topology_after_upgrade']) { + template( + path.join(topology, name + '.sql.in'), + path.join(topology, name + '.sql'), + [pg, directory], + '$libdir/postgis_topology-3', + false, + false, + ); + } + write( + path.join(topologySql, 'topology.sql'), + topologyBody(read(path.join(topology, 'topology.sql'))), + ); + copyFileSync( + path.join(tigerExtension, 'sql_bits/add_search_path.sql.in'), + path.join(tigerSql, 'add_search_path.sql'), + ); + for (const name of ['tiger_geocoder', 'tiger_geocoder_minor']) { + const inputs = postgisSqlInputs(tigerExtension, `sql/${name}.sql.in`); + const tail = + name === 'tiger_geocoder' + ? [ + tigerDropSchema, + read(path.join(directory, 'extensions/postgis_extension_helper_uninstall.sql')), + ] + : []; + write( + path.join(tigerSql, `${name}.sql`), + withoutTransactions(joined(inputs.map(read).join(''), ...tail)), + ); + } + write( + path.join(tigerSql, 'postgis_tiger_geocoder_pre.sql'), + joined( + read(path.join(tigerSql, 'tiger_geocoder.sql')), + read(path.join(tigerExtension, 'sql_bits/mark_editable_objects.sql.in')), + ), + ); } else if (phase === 'postgis-sql-upgrade') { const upgrade = (body: string) => joined( @@ -585,6 +701,20 @@ export function postgisSql(config: Config, phase: string) { ), ), ); + const topologyUpgrade = joined( + ...[ + path.join(pg, 'common_before_upgrade.sql'), + path.join(topology, 'topology_before_upgrade.sql'), + path.join(topology, 'topology_upgrade.sql.in'), + path.join(topology, 'topology_after_upgrade.sql'), + path.join(pg, 'common_after_upgrade.sql'), + ].map(read), + ); + write( + path.join(topology, 'topology_upgrade.sql'), + joined('BEGIN;', topologyUpgrade, 'COMMIT;'), + ); + write(path.join(topologySql, 'topology_upgrade.sql'), topologyBody(topologyUpgrade)); } else if (phase === 'postgis-sql-install') { const template = read( path.join(directory, 'extensions/postgis/unpackage_raster_if_needed.sql'), @@ -643,6 +773,55 @@ export function postgisSql(config: Config, phase: string) { write(path.join(rasterSql, 'postgis_raster--TEMPLATED--TO--ANY.sql'), rasterTag); write(path.join(rasterSql, `postgis_raster--${pgis.version}--ANY.sql`), rasterTag); write(path.join(rasterSql, 'postgis_raster--unpackaged.sql'), '-- Nothing to do here\n'); + write( + path.join(topologySql, `postgis_topology--${pgis.version}.sql`), + joined( + ...[ + path.join(topologyExtension, 'extlock.sql'), + path.join(topologySql, 'topology.sql'), + path.join(topologyExtension, 'sql_bits/mark_editable_objects.sql.in'), + ].map(read), + ), + ); + write( + path.join(topologySql, `postgis_topology--ANY--${pgis.version}.sql`), + joined( + ...[ + path.join(topologyExtension, 'extlock.sql'), + path.join(directory, 'extensions/postgis_extension_helper.sql'), + path.join(topologySql, 'topology_upgrade.sql'), + path.join(topologyExtension, 'sql_bits/mark_editable_objects.sql.in'), + path.join(directory, 'extensions/postgis_extension_helper_uninstall.sql'), + ].map(read), + ), + ); + write(path.join(topologySql, 'postgis_topology--unpackaged.sql'), '-- Nothing to do here\n'); + copyFileSync( + path.join(tigerSql, 'postgis_tiger_geocoder.sql'), + path.join(tigerSql, `postgis_tiger_geocoder--${pgis.version}.sql`), + ); + write( + path.join(tigerSql, `postgis_tiger_geocoder--ANY--${pgis.version}.sql`), + joined( + read(path.join(directory, 'extensions/postgis_extension_helper.sql')), + read(path.join(tigerSql, 'tiger_geocoder_minor.sql')), + read(path.join(tigerExtension, 'sql_bits/mark_editable_objects.sql.in')), + tigerDropSchema, + read(path.join(directory, 'extensions/postgis_extension_helper_uninstall.sql')), + ), + ); + copyFileSync( + path.join(tigerExtension, 'sql_bits/tiger_geocoder--unpackaged.sql.in'), + path.join(tigerSql, `postgis_tiger_geocoder--unpackaged--${pgis.version}.sql`), + ); + for (const [name, folder] of [ + ['postgis_topology', topologySql], + ['postgis_tiger_geocoder', tigerSql], + ]) { + const extensionTag = tag.replaceAll('extension postgis ', `extension ${name} `); + write(path.join(folder, `${name}--TEMPLATED--TO--ANY.sql`), extensionTag); + write(path.join(folder, `${name}--${pgis.version}--ANY.sql`), extensionTag); + } } else if (phase === 'postgis-sql-unpackaged') { for (const [name, folder] of [ ['postgis', sql], @@ -651,6 +830,14 @@ export function postgisSql(config: Config, phase: string) { const file = path.join(folder, `${name}--unpackaged--${pgis.version}.sql`); write(file, joined(read(file), read(path.join(folder, `${name}--ANY--${pgis.version}.sql`)))); } + const topologyUnpackaged = path.join( + topologySql, + `postgis_topology--unpackaged--${pgis.version}.sql`, + ); + write( + topologyUnpackaged, + joined(read(topologyUnpackaged), read(path.join(topologySql, 'topology_upgrade.sql'))), + ); } else throw new Error(`unknown SQL phase ${phase}`); } @@ -704,6 +891,9 @@ if (import.meta.main) { case 'postgis-meson': postgisMeson(config); break; + case 'postgis-module-stems': + console.log(postgisModuleStems(config).join('\n')); + break; case 'postgis-version': console.log(postgisVersion(postgisDirectory(config)).version); break; diff --git a/src/extensions/artifacts/native/tools/windows-extension-sources.test.mts b/src/extensions/artifacts/native/tools/windows-extension-sources.test.mts index 528219b40..f91a59b2b 100644 --- a/src/extensions/artifacts/native/tools/windows-extension-sources.test.mts +++ b/src/extensions/artifacts/native/tools/windows-extension-sources.test.mts @@ -3,7 +3,13 @@ import assert from 'node:assert/strict'; import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import path from 'node:path'; -import { pgxsSources, postgisSql, prepareSimple } from './windows-extension-sources.mts'; +import { + pgxsSources, + postgisModuleStems, + postgisSql, + postgisSqlInputs, + prepareSimple, +} from './windows-extension-sources.mts'; test('Windows textsearch compiles upstream layout checks and installs the upgrade chain', () => { const root = mkdtempSync(path.join(tmpdir(), 'textsearch-windows-')); @@ -43,11 +49,13 @@ test('Windows textsearch compiles upstream layout checks and installs the upgrad } }); -test('Windows raster SQL installs and upgrades the raster module under its own extension identity', () => { +test('Windows PostGIS SQL keeps companion extension identities and upgrade paths', () => { const root = mkdtempSync(path.join(tmpdir(), 'postgis-windows-')); const directory = path.join(root, 'postgres/contrib/oliphaunt_external/postgis'); const sql = 'extensions/postgis/sql/'; const rasterSql = 'extensions/postgis_raster/sql/'; + const topologySql = 'extensions/postgis_topology/sql/'; + const tigerSql = 'extensions/postgis_tiger_geocoder/sql/'; const write = (file: string, text: string) => { const target = path.join(directory, file); mkdirSync(path.dirname(target), { recursive: true }); @@ -79,6 +87,10 @@ test('Windows raster SQL installs and upgrades the raster module under its own e sql + 'spatial_ref_sys.sql', 'extensions/postgis_extension_helper.sql', 'extensions/postgis_extension_helper_uninstall.sql', + 'topology/topology_before_upgrade.sql', + 'topology/topology_after_upgrade.sql', + 'extensions/postgis_topology/sql_bits/mark_editable_objects.sql.in', + 'extensions/postgis_tiger_geocoder/sql_bits/mark_editable_objects.sql.in', ]) write(file, '-- fixture\n'); write( @@ -86,6 +98,30 @@ test('Windows raster SQL installs and upgrades the raster module under its own e "CREATE FUNCTION raster_fixture() RETURNS text AS '$libdir/postgis_raster-3', 'raster_fixture' LANGUAGE C;\n", ); write(rasterSql + 'rtpostgis_upgrade.sql.in', 'DROP FUNCTION raster_fixture();\n'); + write( + 'topology/topology_upgrade.sql.in', + 'CREATE SCHEMA topology;\nSELECT topology_fixture();\n', + ); + write( + topologySql + 'topology.sql', + "CREATE FUNCTION topology_fixture() RETURNS text AS '$libdir/postgis_topology-3', 'topology_fixture' LANGUAGE C;\n", + ); + write( + 'extensions/postgis_topology/extlock.sql', + '\\echo Use "CREATE EXTENSION postgis_topology" to load this file. \\quit\n', + ); + write( + tigerSql + 'postgis_tiger_geocoder.sql', + "CREATE FUNCTION tiger.fixture() RETURNS text AS $$ SELECT @extschema:fuzzystrmatch@.soundex('value') $$ LANGUAGE SQL;\n", + ); + write( + tigerSql + 'tiger_geocoder_minor.sql', + "CREATE OR REPLACE FUNCTION tiger.fixture() RETURNS text AS $$ SELECT @extschema:fuzzystrmatch@.soundex('value') $$ LANGUAGE SQL;\n", + ); + write( + 'extensions/postgis_tiger_geocoder/sql_bits/tiger_geocoder--unpackaged.sql.in', + 'ALTER EXTENSION postgis_tiger_geocoder ADD SCHEMA tiger;\n', + ); write('extensions/postgis/unpackage_raster_if_needed.sql', '-- UNPACKAGE_CODE\n-- end\n'); const config = { repo: root, @@ -106,6 +142,23 @@ test('Windows raster SQL installs and upgrades the raster module under its own e ), ); assert(!upgrade.includes('BEGIN;')); + const topologyInstall = read(topologySql + 'postgis_topology--3.6.4.sql'); + assert(topologyInstall.includes('CREATE EXTENSION postgis_topology')); + assert(topologyInstall.includes('$libdir/postgis_topology-3')); + const topologyUpgrade = read(topologySql + 'postgis_topology--ANY--3.6.4.sql'); + assert(topologyUpgrade.includes('SELECT topology_fixture();')); + assert(!topologyUpgrade.includes('CREATE SCHEMA')); + assert(!topologyUpgrade.includes('BEGIN;')); + const tigerInstall = read(tigerSql + 'postgis_tiger_geocoder--3.6.4.sql'); + assert(tigerInstall.includes('CREATE FUNCTION tiger.fixture()')); + assert(tigerInstall.includes('@extschema:fuzzystrmatch@.soundex')); + const tigerUpgrade = read(tigerSql + 'postgis_tiger_geocoder--ANY--3.6.4.sql'); + assert(tigerUpgrade.includes('CREATE OR REPLACE FUNCTION tiger.fixture()')); + assert( + tigerUpgrade.includes( + "postgis_extension_drop_if_exists('postgis_tiger_geocoder', 'DROP SCHEMA tiger_data')", + ), + ); assert( read(rasterSql + 'postgis_raster--3.6.4--ANY.sql').includes( 'extension postgis_raster version', @@ -114,14 +167,72 @@ test('Windows raster SQL installs and upgrades the raster module under its own e for (const [folder, name] of [ [sql, 'postgis'], [rasterSql, 'postgis_raster'], + [topologySql, 'postgis_topology'], ]) { write(folder + name + '--unpackaged--3.6.4.sql', '-- unpackaged objects\n'); } postgisSql(config, 'postgis-sql-unpackaged'); assert(read(rasterSql + 'postgis_raster--unpackaged--3.6.4.sql').endsWith(upgrade)); + assert( + read(topologySql + 'postgis_topology--unpackaged--3.6.4.sql').endsWith( + read(topologySql + 'topology_upgrade.sql'), + ), + ); + assert.equal( + read(tigerSql + 'postgis_tiger_geocoder--unpackaged--3.6.4.sql'), + 'ALTER EXTENSION postgis_tiger_geocoder ADD SCHEMA tiger;\n', + ); } finally { if (originalEpoch === undefined) delete process.env.SOURCE_DATE_EPOCH; else process.env.SOURCE_DATE_EPOCH = originalEpoch; rmSync(root, { recursive: true, force: true }); } }); + +test('Windows tiger SQL follows literal upstream prerequisite order and rejects Make expressions', () => { + const root = mkdtempSync(path.join(tmpdir(), 'postgis-tiger-windows-')); + try { + const makefile = path.join(root, 'Makefile.in'); + writeFileSync( + makefile, + 'sql/tiger_geocoder.sql.in: sql_bits/types.sql.in \\\n ../../extras/functions.sql sql_bits/types.sql.in | sql\n\tcat $^ > $@\n', + ); + assert.deepEqual(postgisSqlInputs(root, 'sql/tiger_geocoder.sql.in'), [ + path.resolve(root, 'sql_bits/types.sql.in'), + path.resolve(root, '../../extras/functions.sql'), + ]); + writeFileSync(makefile, 'sql/tiger_geocoder.sql.in: $(SQL_INPUTS) | sql\n'); + assert.throws( + () => postgisSqlInputs(root, 'sql/tiger_geocoder.sql.in'), + /unsupported PostGIS SQL prerequisites/, + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test('Windows embedded PostGIS builds every declared native module and rejects invalid paths', () => { + const root = mkdtempSync(path.join(tmpdir(), 'postgis-modules-windows-')); + try { + const manifest = path.join(root, 'src/extensions/external/postgis/targets/native.toml'); + mkdirSync(path.dirname(manifest), { recursive: true }); + writeFileSync( + manifest, + 'module_files = ["lib/postgresql/postgis-3.so", "lib/postgresql/postgis_raster-3.so", "lib/postgresql/postgis_topology-3.so"]\n', + ); + assert.deepEqual(postgisModuleStems({ repo: root }), [ + 'postgis-3', + 'postgis_raster-3', + 'postgis_topology-3', + ]); + writeFileSync(manifest, 'module_files = ["../postgis-3.so"]\n'); + assert.throws(() => postgisModuleStems({ repo: root }), /invalid PostGIS native module/); + writeFileSync( + manifest, + 'module_files = ["lib/postgresql/postgis-3.so", "lib/postgresql/postgis-3.so"]\n', + ); + assert.throws(() => postgisModuleStems({ repo: root }), /duplicate PostGIS native modules/); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/src/extensions/artifacts/packages/tools/build-extension-ci-artifacts.mts b/src/extensions/artifacts/packages/tools/build-extension-ci-artifacts.mts index b9113a2d1..a70f7cce8 100644 --- a/src/extensions/artifacts/packages/tools/build-extension-ci-artifacts.mts +++ b/src/extensions/artifacts/packages/tools/build-extension-ci-artifacts.mts @@ -1,6 +1,7 @@ #!/usr/bin/env bun import { createHash } from 'node:crypto'; import { + chmodSync, copyFileSync, cpSync, existsSync, @@ -10,25 +11,15 @@ import { rmSync, statSync, writeFileSync, - chmodSync, } from 'node:fs'; import path from 'node:path'; - +import { createDeterministicTar } from '../../../../../tools/packaging/cargo-source-package.mts'; +import { canonicalGzipSync } from '../../../../../tools/packaging/portable-archive.mts'; import { assertReleaseNoticesInArchive, stageReleaseNotices, } from '../../../../../tools/packaging/release-notices.mts'; import { - assertExtensionUpstreamLicensesInArchive, - extensionCarrierLegalContract, - stageExtensionUpstreamLicenses, -} from '../../../tools/extension-upstream-licenses.mts'; - -import { createDeterministicTar } from '../../../../../tools/packaging/cargo-source-package.mts'; -import { extensionRuntimeAssetContract } from './extension-runtime-asset-contract.mts'; -import { canonicalGzipSync } from '../../../../../tools/packaging/portable-archive.mts'; -import { - ROOT, compareText, currentProductVersionSync, exactExtensionProducts, @@ -39,18 +30,25 @@ import { extensionReleaseVersion, extensionSourceIdentity, extensionSqlNames, + ROOT, } from '../../../../../tools/release/release-artifact-targets.mts'; import { swiftExtensionCarrierAssetName, writeSwiftExtensionCarrierManifest, } from '../../../../native/sdks/swift/tools/ios-carrier-manifest.mts'; -import { AOT_TARGET_TRIPLES } from '../../../../wasix/runtime/tools/wasix-cargo-artifact-contract.mts'; import { assertCanonicalWasixAotManifest } from '../../../../wasix/runtime/tools/wasix-aot-manifest.mts'; +import { AOT_TARGET_TRIPLES } from '../../../../wasix/runtime/tools/wasix-cargo-artifact-contract.mts'; import { assertWasixExtensionArchiveInstall, assertWasixExtensionInstallSidecar, projectWasixExtensionInstallSidecar, } from '../../../contracts/wasix-extension-install.mts'; +import { + assertExtensionUpstreamLicensesInArchive, + extensionCarrierLegalContract, + stageExtensionUpstreamLicenses, +} from '../../../tools/extension-upstream-licenses.mts'; +import { extensionRuntimeAssetContract } from './extension-runtime-asset-contract.mts'; const PREFIX = 'build-extension-ci-artifacts.mts'; @@ -700,6 +698,11 @@ function stageMember( `${sqlName}.extension-sql-file-prefixes`, ), nativeModuleStem: extensionRow['native-module-stem'], + sqlMembers: stringList(extensionRow['sql-members'], `${sqlName}.sql-members`), + nativeModuleStems: stringList( + extensionRow['native-module-stems'], + `${sqlName}.native-module-stems`, + ), iosNativeDependencies: assets .filter((asset) => asset.kind === 'ios-dependency-xcframework') .map((asset) => asset.identity) @@ -878,6 +881,8 @@ export function extensionReleasePropertiesText({ `extensionSqlFileNames=${propertiesCsv(manifest.extensionSqlFileNames)}\n`, `extensionSqlFilePrefixes=${propertiesCsv(manifest.extensionSqlFilePrefixes)}\n`, `nativeModuleStem=${manifest.nativeModuleStem || ''}\n`, + `sqlMembers=${propertiesCsv(manifest.sqlMembers)}\n`, + `nativeModuleStems=${propertiesCsv(manifest.nativeModuleStems)}\n`, `iosNativeDependencies=${propertiesCsv(manifest.iosNativeDependencies)}\n`, `sharedPreloadLibraries=${propertiesCsv(manifest.sharedPreloadLibraries)}\n`, ); @@ -904,6 +909,8 @@ export function extensionReleasePropertiesText({ `${prefix}.extensionSqlFileNames=${propertiesCsv(member.extensionSqlFileNames)}\n`, `${prefix}.extensionSqlFilePrefixes=${propertiesCsv(member.extensionSqlFilePrefixes)}\n`, `${prefix}.nativeModuleStem=${member.nativeModuleStem || ''}\n`, + `${prefix}.sqlMembers=${propertiesCsv(member.sqlMembers)}\n`, + `${prefix}.nativeModuleStems=${propertiesCsv(member.nativeModuleStems)}\n`, `${prefix}.iosNativeDependencies=${propertiesCsv(member.iosNativeDependencies)}\n`, `${prefix}.sharedPreloadLibraries=${propertiesCsv(member.sharedPreloadLibraries)}\n`, ); @@ -1087,6 +1094,8 @@ function stageProductVariant( extensionSqlFilePrefixes: member.extensionSqlFilePrefixes, createsExtension: member.createsExtension, nativeModuleStem: member.nativeModuleStem, + sqlMembers: member.sqlMembers, + nativeModuleStems: member.nativeModuleStems, iosNativeDependencies: member.iosNativeDependencies, iosRegistration: member.iosRegistration, wasixInstall: member.wasixInstall, diff --git a/src/extensions/artifacts/packages/tools/check-carriers.mts b/src/extensions/artifacts/packages/tools/check-carriers.mts index 33118746f..7b3a3faf1 100644 --- a/src/extensions/artifacts/packages/tools/check-carriers.mts +++ b/src/extensions/artifacts/packages/tools/check-carriers.mts @@ -1,10 +1,6 @@ import { createHash } from 'node:crypto'; import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; import path from 'node:path'; -import { - buildSwiftExtensionCarrierManifest, - swiftExtensionCarrierAssetName, -} from '../../../../native/sdks/swift/tools/ios-carrier-manifest.mts'; import { portableMemberName, readFileOnlyTarGzipEntries, @@ -36,11 +32,16 @@ import { extensionSqlNames, ROOT, } from '../../../../../tools/release/release-artifact-targets.mts'; +import { + buildSwiftExtensionCarrierManifest, + swiftExtensionCarrierAssetName, +} from '../../../../native/sdks/swift/tools/ios-carrier-manifest.mts'; import { assertWasixExtensionMemberInstall } from '../../../contracts/wasix-extension-install.mts'; import { assertExtensionUpstreamLicensesInArchive, extensionCarrierLegalContract, } from '../../../tools/extension-upstream-licenses.mts'; +import { assertExtensionArtifactClosure } from './extension-artifact-inventory.mts'; import { extensionRuntimeAssetContract } from './extension-runtime-asset-contract.mts'; export const EXTENSION_ROOT = path.resolve( @@ -67,6 +68,8 @@ const PUBLIC_EXTENSION_RELEASE_MANIFEST_KEYS = new Set([ 'extensionSqlFileNames', 'extensionSqlFilePrefixes', 'nativeModuleStem', + 'sqlMembers', + 'nativeModuleStems', 'iosNativeDependencies', 'iosRegistration', 'wasixInstall', @@ -94,6 +97,8 @@ const EXTENSION_BUNDLE_MEMBER_KEYS = new Set([ 'extensionSqlFileNames', 'extensionSqlFilePrefixes', 'nativeModuleStem', + 'sqlMembers', + 'nativeModuleStems', 'iosNativeDependencies', 'iosRegistration', 'wasixInstall', @@ -250,6 +255,16 @@ function requireExactKeys(value, expected, context) { } } +function extensionClosure(member, context) { + for (const key of ['sqlMembers', 'nativeModuleStems']) { + requireSortedUniqueStrings(member[key], `${context}.${key}`); + if (member[key].some((name) => !/^[A-Za-z0-9._-]{1,128}$/u.test(name))) + fail(`${context}.${key} must contain portable identifiers`); + } + assertExtensionArtifactClosure(member, 'linux-x64-gnu', context); + return { sqlMembers: member.sqlMembers, nativeModuleStems: member.nativeModuleStems }; +} + function requireSortedUniqueStrings(value, context) { if ( !Array.isArray(value) || @@ -361,6 +376,7 @@ function validateBundleMemberMetadata(member, manifest, stagedTargets) { ); } validateMemberWasixInstall(member, `${rel(manifest)} member ${member.sqlName}`); + extensionClosure(member, `${rel(manifest)} member ${member.sqlName}`); const stagesIos = stagedTargets.has('ios-xcframework'); if (member.nativeModuleStem === null) { if (member.iosNativeDependencies.length > 0 || member.iosRegistration !== null) { @@ -854,6 +870,8 @@ async function checkExtensionBundleProduct( expectedProperties[`${prefix}.extensionSqlFilePrefixes`] = member.extensionSqlFilePrefixes.join(','); expectedProperties[`${prefix}.nativeModuleStem`] = member.nativeModuleStem ?? ''; + for (const [key, values] of Object.entries(extensionClosure(member, prefix))) + expectedProperties[`${prefix}.${key}`] = values.join(','); expectedProperties[`${prefix}.iosNativeDependencies`] = member.iosNativeDependencies.join(','); expectedProperties[`${prefix}.sharedPreloadLibraries`] = member.sharedPreloadLibraries.join(','); @@ -1115,6 +1133,7 @@ async function checkExtensionProductVariant( extensionSqlFileNames: data.extensionSqlFileNames, extensionSqlFilePrefixes: data.extensionSqlFilePrefixes, nativeModuleStem: data.nativeModuleStem, + ...extensionClosure(data, rel(manifest)), iosNativeDependencies: data.iosNativeDependencies, iosRegistration: data.iosRegistration, wasixInstall: data.wasixInstall, @@ -1186,6 +1205,12 @@ async function checkExtensionProductVariant( extensionSqlFileNames: data.extensionSqlFileNames.join(','), extensionSqlFilePrefixes: data.extensionSqlFilePrefixes.join(','), nativeModuleStem: data.nativeModuleStem ?? '', + ...Object.fromEntries( + Object.entries(extensionClosure(data, rel(manifest))).map(([key, values]) => [ + key, + values.join(','), + ]), + ), iosNativeDependencies: data.iosNativeDependencies.join(','), sharedPreloadLibraries: data.sharedPreloadLibraries.join(','), }; diff --git a/src/extensions/artifacts/packages/tools/check-carriers.test.mts b/src/extensions/artifacts/packages/tools/check-carriers.test.mts index b603d9117..e178cd1e3 100644 --- a/src/extensions/artifacts/packages/tools/check-carriers.test.mts +++ b/src/extensions/artifacts/packages/tools/check-carriers.test.mts @@ -1,8 +1,8 @@ -import test from 'node:test'; import assert from 'node:assert/strict'; -import { expectedExtensionBundleManifest } from './check-carriers.mts'; -import { extensionReleasePropertiesText } from './build-extension-ci-artifacts.mts'; +import test from 'node:test'; import { parseUniquePropertiesText } from '../../../../../tools/packaging/release-carrier.mts'; +import { extensionReleasePropertiesText } from './build-extension-ci-artifacts.mts'; +import { expectedExtensionBundleManifest } from './check-carriers.mts'; test('derives nested bundle compatibility from the staged bundle data', () => { const compatibility = { @@ -95,6 +95,8 @@ test('renders every single-extension asset identity into the public properties m extensionSqlFileNames: ['uninstall_postgis.sql'], extensionSqlFilePrefixes: ['postgis_comments', 'rtpostgis'], nativeModuleStem: 'postgis-3', + sqlMembers: ['postgis', 'postgis_raster', 'postgis_tiger_geocoder', 'postgis_topology'], + nativeModuleStems: ['postgis-3', 'postgis_raster-3', 'postgis_topology-3'], iosNativeDependencies: dependencyIdentities, sharedPreloadLibraries: [], assets, @@ -128,6 +130,11 @@ test('renders every single-extension asset identity into the public properties m assert.equal(properties.dataFiles, 'contrib/postgis-3.6/postgis.sql,proj/proj.db'); assert.equal(properties.extensionSqlFileNames, 'uninstall_postgis.sql'); assert.equal(properties.extensionSqlFilePrefixes, 'postgis_comments,rtpostgis'); + assert.equal( + properties.sqlMembers, + 'postgis,postgis_raster,postgis_tiger_geocoder,postgis_topology', + ); + assert.equal(properties.nativeModuleStems, 'postgis-3,postgis_raster-3,postgis_topology-3'); assert.doesNotMatch(text, /^carrier\./mu); }); @@ -146,6 +153,8 @@ test('freezes each bundle member desktop inventory in the public properties mani extensionSqlFileNames: ['uninstall_pgtap.sql'], extensionSqlFilePrefixes: ['pgtap-core', 'pgtap-schema'], nativeModuleStem: null, + sqlMembers: ['pgtap'], + nativeModuleStems: [], iosNativeDependencies: [], sharedPreloadLibraries: [], assets: [], @@ -166,4 +175,6 @@ test('freezes each bundle member desktop inventory in the public properties mani assert.equal(properties['extension.pgtap.dataFiles'], ''); assert.equal(properties['extension.pgtap.extensionSqlFileNames'], 'uninstall_pgtap.sql'); assert.equal(properties['extension.pgtap.extensionSqlFilePrefixes'], 'pgtap-core,pgtap-schema'); + assert.equal(properties['extension.pgtap.sqlMembers'], 'pgtap'); + assert.equal(properties['extension.pgtap.nativeModuleStems'], ''); }); diff --git a/src/extensions/artifacts/packages/tools/extension-artifact-inventory.mts b/src/extensions/artifacts/packages/tools/extension-artifact-inventory.mts index 96f8ca457..3854da366 100644 --- a/src/extensions/artifacts/packages/tools/extension-artifact-inventory.mts +++ b/src/extensions/artifacts/packages/tools/extension-artifact-inventory.mts @@ -2,6 +2,7 @@ import { createHash } from 'node:crypto'; import { closeSync, constants, + existsSync, fstatSync, lstatSync, openSync, @@ -11,14 +12,13 @@ import { import path from 'node:path'; import { TextDecoder } from 'node:util'; import { strictGunzip } from '../../../../../tools/packaging/portable-archive.mts'; - +import { releaseNoticeRows } from '../../../../../tools/packaging/release-notices.mts'; +import { extensionProductForSqlName } from '../../../../../tools/release/release-artifact-targets.mts'; import { EXTENSION_ARTIFACT_ARCHIVE_POLICY } from '../../../tools/extension-artifact-archive-policy.mts'; import { extensionCarrierLegalContract, extensionUpstreamLicenseRow, } from '../../../tools/extension-upstream-licenses.mts'; -import { extensionProductForSqlName } from '../../../../../tools/release/release-artifact-targets.mts'; -import { releaseNoticeRows } from '../../../../../tools/packaging/release-notices.mts'; export const EXTENSION_ARTIFACT_PROPERTY_KEYS = Object.freeze([ 'packageLayout', @@ -42,6 +42,8 @@ export const EXTENSION_ARTIFACT_PROPERTY_KEYS = Object.freeze([ 'staticSymbolAliases', 'licenseFiles', 'licenseProfile', + 'sqlMembers', + 'nativeModuleStems', 'files', ]); @@ -145,15 +147,16 @@ export function parseExtensionArtifactProperties(text, label) { } properties.set(key, rawLine.slice(separator + 1)); } + const orderedKeys = EXTENSION_ARTIFACT_PROPERTY_KEYS; const actual = [...properties.keys()].sort(compareText); - const expected = [...EXTENSION_ARTIFACT_PROPERTY_KEYS].sort(compareText); + const expected = [...orderedKeys].sort(compareText); if (JSON.stringify(actual) !== JSON.stringify(expected)) { throw inventoryError( label, `property fields must be exactly ${expected.join(',')}; got ${actual.join(',')}`, ); } - if (JSON.stringify([...properties.keys()]) !== JSON.stringify(EXTENSION_ARTIFACT_PROPERTY_KEYS)) { + if (JSON.stringify([...properties.keys()]) !== JSON.stringify(orderedKeys)) { throw inventoryError(label, 'properties must use the canonical field order'); } return properties; @@ -439,6 +442,8 @@ function normalizeMetadata(row, label) { sqlName, createsExtension, nativeModuleStem, + sqlMembers: list('sqlMembers', 'sql-members'), + nativeModuleStems: list('nativeModuleStems', 'native-module-stems'), dependencies: (() => { const selected = row?.selectedExtensionDependencies ?? @@ -458,6 +463,8 @@ function normalizeMetadata(row, label) { for (const [field, values] of [ ['selected-extension-dependencies', metadata.dependencies], ['shared-preload-libraries', metadata.sharedPreloadLibraries], + ['sql-members', metadata.sqlMembers], + ['native-module-stems', metadata.nativeModuleStems], ]) { if (values.some((item) => !PORTABLE_ID.test(item))) { throw inventoryError(label, `${field} contains a non-portable identifier`); @@ -487,8 +494,80 @@ function moduleSuffix(target) { return '.so'; } +// The target recipe owns native modules; the common recipe also lists WASIX-only dependencies. +export function extensionNativeArtifactClosure(sqlName, target) { + if (!PORTABLE_ID.test(sqlName)) throw inventoryError(sqlName, 'invalid extension SQL name'); + const directory = path.resolve(import.meta.dir, '../../../external', sqlName); + const read = (file) => { + const full = path.join(directory, file); + return existsSync(full) ? Bun.TOML.parse(readFileSync(full, 'utf8')) : {}; + }; + const controls = (read('recipe.toml').artifacts?.control_files ?? []).map((file) => { + const normalized = safeRelativePath(file, `${sqlName} control file`); + const name = normalized.slice('share/postgresql/extension/'.length); + if ( + normalized !== `share/postgresql/extension/${name}` || + !name.endsWith('.control') || + !PORTABLE_ID.test(name.slice(0, -'.control'.length)) + ) { + throw inventoryError(sqlName, `invalid extension control file ${file}`); + } + return name; + }); + const modules = (read('targets/native.toml').module_files ?? []).map((file) => { + const normalized = safeRelativePath(file, `${sqlName} native module`); + const name = normalized.slice('lib/postgresql/'.length); + if (normalized !== `lib/postgresql/${name}` || !PORTABLE_ID.test(name)) { + throw inventoryError(sqlName, `invalid native module ${file}`); + } + if (!/\.(so|dylib|dll)$/u.test(name)) { + throw inventoryError(sqlName, `invalid native module suffix ${file}`); + } + return name.replace(/\.(so|dylib|dll)$/u, moduleSuffix(target)); + }); + return { + controls: [...new Set(controls)].sort(compareText), + modules: [...new Set(modules)].sort(compareText), + }; +} + +export function assertExtensionArtifactClosure(metadata, target, label) { + const closure = extensionNativeArtifactClosure(metadata.sqlName, target); + const mobile = target === 'ios-xcframework' || target.startsWith('android-'); + const expected = { + sqlMembers: [ + ...new Set([ + ...(metadata.createsExtension ? [metadata.sqlName] : []), + ...closure.controls.map((name) => name.slice(0, -'.control'.length)), + ]), + ].sort(compareText), + nativeModuleStems: [ + ...new Set([ + ...(metadata.nativeModuleStem === null ? [] : [metadata.nativeModuleStem]), + ...(mobile ? [] : closure.modules).map((name) => + name.slice(0, -moduleSuffix(target).length), + ), + ]), + ].sort(compareText), + }; + for (const [field, values] of Object.entries(expected)) { + if (JSON.stringify(metadata[field]) !== JSON.stringify(values)) { + throw inventoryError( + label, + `${field} must match the current source-owned closure ${JSON.stringify(values)}`, + ); + } + } +} + function sqlFileOwned(fileName, metadata) { return ( + metadata.companionControls?.includes(fileName) || + metadata.companionControls?.some( + (control) => + fileName.startsWith(`${control.slice(0, -'.control'.length)}--`) && + fileName.endsWith('.sql'), + ) || (metadata.createsExtension && fileName === `${metadata.sqlName}.control`) || (metadata.createsExtension && fileName === `${metadata.sqlName}.sql`) || (metadata.createsExtension && @@ -740,6 +819,20 @@ export function validateExtensionArtifactEntries({ decodeUtf8(manifestEntry.data, `${label} manifest.properties`), `${label} manifest.properties`, ); + assertExtensionArtifactClosure(metadata, target, `${label} canonical metadata`); + const sqlMembers = csv(properties.get('sqlMembers'), `${label} sqlMembers`); + const nativeModuleStems = csv(properties.get('nativeModuleStems'), `${label} nativeModuleStems`); + for (const [key, actual] of [ + ['sqlMembers', sqlMembers], + ['nativeModuleStems', nativeModuleStems], + ]) { + if (JSON.stringify(metadata[key]) !== JSON.stringify(actual)) { + throw inventoryError(label, `manifest ${key} must match the frozen release inventory`); + } + } + metadata.companionControls = sqlMembers + .filter((name) => name !== metadata.sqlName) + .map((name) => `${name}.control`); const legal = canonicalLegalContract(metadata, target, label); const expectedProperties = new Map([ ['packageLayout', 'oliphaunt-extension-artifact-v1'], @@ -824,13 +917,26 @@ export function validateExtensionArtifactEntries({ label, }); } + for (const controlFile of metadata.companionControls) { + const controlName = `${extensionPrefix}${controlFile}`; + const control = entries.get(controlName); + if (control === undefined) + throw inventoryError(label, `missing companion control ${controlName}`); + validateExtensionInstallSqlReachability({ + sqlName: controlFile.slice(0, -'.control'.length), + control: decodeUtf8(control.data, `${label} ${controlName}`), + fileNames: extensionFileNames, + label, + }); + } for (const dataFile of metadata.dataFiles) { allowed.add(`files/share/postgresql/${dataFile}`); } - if (metadata.nativeModuleStem !== null) { - allowed.add(`files/lib/postgresql/${properties.get('nativeModuleFile')}`); + const modules = new Set(nativeModuleStems.map((stem) => `${stem}${moduleSuffix(target)}`)); + for (const module of modules) { + allowed.add(`files/lib/postgresql/${module}`); if (DESKTOP_NATIVE_TARGETS.has(target)) { - allowed.add(`files/lib/modules/${properties.get('nativeModuleFile')}`); + allowed.add(`files/lib/modules/${module}`); } } const mobilePaths = declaredMobilePaths(properties, metadata, label); diff --git a/src/extensions/artifacts/packages/tools/extension-artifact-inventory.test.mts b/src/extensions/artifacts/packages/tools/extension-artifact-inventory.test.mts index 2740113a5..1c7fe20fe 100644 --- a/src/extensions/artifacts/packages/tools/extension-artifact-inventory.test.mts +++ b/src/extensions/artifacts/packages/tools/extension-artifact-inventory.test.mts @@ -25,6 +25,10 @@ import { parseExtensionArtifactProperties, validateExtensionArtifactArchive, } from './extension-artifact-inventory.mts'; +import { + extractExtensionRuntime, + frozenExtensionMemberInventory, +} from './package-extension-release-carriers.mts'; function writeString(buffer, offset, length, value) { const bytes = Buffer.from(value); @@ -134,10 +138,13 @@ function manifest(overrides = {}) { staticSymbolAliases: '', licenseFiles: legal.licenseFiles.join(','), licenseProfile: legal.profile, + sqlMembers: overrides.createsExtension === 'no' ? '' : sqlName, + nativeModuleStems: overrides.nativeModuleStem ?? '', files: 'files', ...overrides, }; - return `${EXTENSION_ARTIFACT_PROPERTY_KEYS.map((key) => `${key}=${values[key]}`).join('\n')}\n`; + const keys = EXTENSION_ARTIFACT_PROPERTY_KEYS; + return `${keys.map((key) => `${key}=${values[key]}`).join('\n')}\n`; } async function canonicalLegalEntries(root, sqlName, target = 'linux-x64-gnu') { @@ -174,6 +181,8 @@ const pgtapMetadata = { sqlName: 'pgtap', createsExtension: true, nativeModuleStem: null, + sqlMembers: ['pgtap'], + nativeModuleStems: [], dependencies: [], dataFiles: [], extensionSqlFileNames: ['uninstall_pgtap.sql'], @@ -537,6 +546,8 @@ async function main() { sqlName: 'auto_explain', createsExtension: false, nativeModuleStem: 'auto_explain', + sqlMembers: [], + nativeModuleStems: ['auto_explain'], extensionSqlFileNames: [], extensionSqlFilePrefixes: [], }; @@ -567,6 +578,8 @@ async function main() { sqlName: 'postgis', createsExtension: true, nativeModuleStem: 'postgis-3', + sqlMembers: ['postgis', 'postgis_raster', 'postgis_tiger_geocoder', 'postgis_topology'], + nativeModuleStems: ['postgis-3', 'postgis_raster-3', 'postgis_topology-3'], dependencies: [], dataFiles: [ 'contrib/postgis-3.6/legacy.sql', @@ -591,6 +604,8 @@ async function main() { sqlName: 'postgis', nativeModuleStem: 'postgis-3', nativeModuleFile: 'postgis-3.so', + sqlMembers: postgisMetadata.sqlMembers.join(','), + nativeModuleStems: postgisMetadata.nativeModuleStems.join(','), dataFiles: postgisMetadata.dataFiles.join(','), extensionSqlFileNames: postgisMetadata.extensionSqlFileNames.join(','), extensionSqlFilePrefixes: postgisMetadata.extensionSqlFilePrefixes.join(','), @@ -598,6 +613,14 @@ async function main() { ], ['files/lib/postgresql/postgis-3.so', 'module'], ['files/lib/modules/postgis-3.so', 'embedded module'], + ...['postgis_raster-3', 'postgis_topology-3'].flatMap((stem) => [ + [`files/lib/postgresql/${stem}.so`, 'module'], + [`files/lib/modules/${stem}.so`, 'embedded module'], + ]), + ...['postgis_raster', 'postgis_topology', 'postgis_tiger_geocoder'].flatMap((name) => [ + [`files/share/postgresql/extension/${name}.control`, "default_version = '3.6.3'\n"], + [`files/share/postgresql/extension/${name}--3.6.3.sql`, 'install'], + ]), ['files/share/postgresql/extension/postgis--3.6.1--3.6.2.sql', 'upgrade'], ['files/share/postgresql/extension/postgis--3.6.2--3.6.3.sql', 'upgrade'], ['files/share/postgresql/extension/postgis--3.6.3.sql', 'install'], @@ -609,6 +632,14 @@ async function main() { ...postgisLegal, ]); const postgisFile = await writeArchive(root, 'postgis-legitimate.tar.gz', postgis); + assert.throws( + () => + parseExtensionArtifactProperties( + postgis.get('manifest.properties').replace(/^nativeModuleStems=.*\n/mu, ''), + 'partial frozen closure', + ), + /property fields/u, + ); const validatedPostgis = validateExtensionArtifactArchive({ file: postgisFile, metadata: postgisMetadata, @@ -616,6 +647,25 @@ async function main() { nativeRuntimeVersion: '1.2.3', label: 'postgis legitimate', }); + const extracted = path.join(root, 'extracted-postgis'); + extractExtensionRuntime(postgisFile, extracted, { + metadata: postgisMetadata, + target: 'linux-x64-gnu', + nativeRuntimeVersion: '1.2.3', + }); + await assert.rejects(fs.access(path.join(extracted, '.oliphaunt')), { code: 'ENOENT' }); + assert.equal( + await fs.readFile(path.join(extracted, 'lib/modules/postgis_topology-3.so'), 'utf8'), + 'embedded module', + ); + assert.deepEqual( + frozenExtensionMemberInventory(postgisMetadata).nativeModuleStems, + postgisMetadata.nativeModuleStems, + ); + assert.throws( + () => frozenExtensionMemberInventory({ ...postgisMetadata, nativeModuleStems: undefined }), + /nativeModuleStems/u, + ); assert.equal( validatedPostgis.runtimeFiles.length, [...postgis.keys()].filter((member) => member.startsWith('files/')).length, @@ -624,6 +674,71 @@ async function main() { validatedPostgis.legalFiles.map(({ path: member }) => member), [...postgisLegal.keys()].sort(), ); + const legacyPostgis = new Map( + [...postgis].filter(([name]) => !/postgis_(raster|topology|tiger_geocoder)/u.test(name)), + ); + legacyPostgis.set( + 'manifest.properties', + legacyPostgis + .get('manifest.properties') + .split('\n') + .filter((line) => !/^(sqlMembers|nativeModuleStems)=/u.test(line)) + .join('\n'), + ); + const legacyFile = await writeArchive(root, 'postgis-legacy-core-only.tar.gz', legacyPostgis); + assert.throws( + () => + frozenExtensionMemberInventory({ + ...postgisMetadata, + sqlMembers: undefined, + nativeModuleStems: undefined, + }), + /sqlMembers/u, + ); + assert.throws( + () => + validateExtensionArtifactArchive({ + file: legacyFile, + metadata: postgisMetadata, + target: 'linux-x64-gnu', + nativeRuntimeVersion: '1.2.3', + label: 'new frozen PostGIS rejects incomplete legacy payload', + }), + /property fields/u, + ); + const incompleteMetadata = { + ...postgisMetadata, + sqlMembers: ['postgis'], + nativeModuleStems: ['postgis-3'], + }; + assert.throws( + () => frozenExtensionMemberInventory(incompleteMetadata), + /current source-owned closure/u, + ); + assert.throws( + () => + frozenExtensionMemberInventory({ ...postgisMetadata, nativeModuleStems: ['postgis-3'] }), + /nativeModuleStems must match the current source-owned closure/u, + ); + const incompletePostgis = new Map(legacyPostgis); + incompletePostgis.set( + 'manifest.properties', + manifest({ + sqlName: 'postgis', + nativeModuleStem: 'postgis-3', + nativeModuleFile: 'postgis-3.so', + dataFiles: postgisMetadata.dataFiles.join(','), + extensionSqlFileNames: '', + extensionSqlFilePrefixes: postgisMetadata.extensionSqlFilePrefixes.join(','), + }), + ); + await expectArchiveFailure( + root, + 'postgis-incomplete-closure.tar.gz', + incompletePostgis, + incompleteMetadata, + /current source-owned closure/u, + ); const missingEmbeddedPostgis = new Map(postgis); missingEmbeddedPostgis.delete('files/lib/modules/postgis-3.so'); diff --git a/src/extensions/artifacts/packages/tools/extension-wasix-npm-packages.test.mts b/src/extensions/artifacts/packages/tools/extension-wasix-npm-packages.test.mts index 0da931765..bb366ff32 100644 --- a/src/extensions/artifacts/packages/tools/extension-wasix-npm-packages.test.mts +++ b/src/extensions/artifacts/packages/tools/extension-wasix-npm-packages.test.mts @@ -23,6 +23,7 @@ import { extensionRegistryPackageTargetSets, extensionReleaseVersion, } from '../../../../../tools/release/release-artifact-targets.mts'; +import { canonicalWasixAotMetadata } from '../../../../wasix/runtime/tools/wasix-aot-manifest.mts'; import { extensionNpmPackageForProduct, extensionNpmWasixPackageForProduct, @@ -33,7 +34,6 @@ import { stageExtensionWasixNpmPackages, writeWasixExtensionAotNpmPackage, } from './package-extension-release-carriers.mts'; -import { canonicalWasixAotMetadata } from '../../../../wasix/runtime/tools/wasix-aot-manifest.mts'; const ROOT = path.resolve(import.meta.dir, '../../../../..'); const directories = []; @@ -119,6 +119,8 @@ function inventory(sqlName, dependencies = []) { sqlName, createsExtension: true, nativeModuleStem: null, + sqlMembers: [sqlName], + nativeModuleStems: [], dependencies, dataFiles: [], extensionSqlFileNames: [`${sqlName}.control`], diff --git a/src/extensions/artifacts/packages/tools/package-extension-release-carriers.mts b/src/extensions/artifacts/packages/tools/package-extension-release-carriers.mts index 67ee51748..b935b2a20 100644 --- a/src/extensions/artifacts/packages/tools/package-extension-release-carriers.mts +++ b/src/extensions/artifacts/packages/tools/package-extension-release-carriers.mts @@ -4,7 +4,6 @@ import { chmodSync, closeSync, copyFileSync, - cpSync, existsSync, lstatSync, mkdirSync, @@ -19,22 +18,11 @@ import { writeFileSync, } from 'node:fs'; import path from 'node:path'; -import { extensionRuntimeAssetContract } from './extension-runtime-asset-contract.mts'; -import { RUST_BUILD_SCRIPT_SHA256 } from '../../../../../tools/packaging/rust-build-script-sha256.mts'; -import { - AOT_TARGET_TRIPLES, - CORE_RUNTIME_ARCHIVE_FILES, -} from '../../../../wasix/runtime/tools/wasix-cargo-artifact-contract.mts'; -import { parseNpmExtensionLicenseFiles } from '../../../../native/sdks/ts/src/native/extension-contract.ts'; import { fitCargoPayloadParts, packageGeneratedCargoSource, readCargoPackageNameVersion, } from '../../../../../tools/packaging/cargo-source-package.mts'; -import { - buildIosCarrierManifest, - IOS_CARRIER_FILENAME, -} from '../../../../native/sdks/swift/tools/ios-carrier-manifest.mts'; import { packGeneratedNpmCarrier } from '../../../../../tools/packaging/npm-package.mts'; import { NPM_TRUSTED_PUBLISHING_REPOSITORY } from '../../../../../tools/packaging/npm-trusted-publishing.mts'; import { @@ -47,6 +35,22 @@ import { releaseNoticeRows, stageReleaseNotices, } from '../../../../../tools/packaging/release-notices.mts'; +import { RUST_BUILD_SCRIPT_SHA256 } from '../../../../../tools/packaging/rust-build-script-sha256.mts'; +import { + compareText, + extensionRegistryPackageTargetSets, +} from '../../../../../tools/release/release-artifact-targets.mts'; +import { + buildIosCarrierManifest, + IOS_CARRIER_FILENAME, +} from '../../../../native/sdks/swift/tools/ios-carrier-manifest.mts'; +import { parseNpmExtensionLicenseFiles } from '../../../../native/sdks/ts/src/native/extension-contract.ts'; +import { assertWasixAotArtifactPayloads } from '../../../../wasix/runtime/tools/check-release-assets.mts'; +import { assertCanonicalWasixAotManifest } from '../../../../wasix/runtime/tools/wasix-aot-manifest.mts'; +import { + AOT_TARGET_TRIPLES, + CORE_RUNTIME_ARCHIVE_FILES, +} from '../../../../wasix/runtime/tools/wasix-cargo-artifact-contract.mts'; import { assertWasixExtensionArchiveInstall, assertWasixExtensionInstall, @@ -56,10 +60,6 @@ import { WASIX_EXTENSION_INSTALL_SCHEMA, WASIX_EXTENSION_INSTALL_SIDECAR_SCHEMA, } from '../../../contracts/wasix-extension-install.mts'; -import { - compareText, - extensionRegistryPackageTargetSets, -} from '../../../../../tools/release/release-artifact-targets.mts'; import { assertExtensionUpstreamLicensesInArchive, assertExtensionUpstreamLicensesInDirectory, @@ -67,19 +67,20 @@ import { extensionUpstreamLicenseFileInventory, stageExtensionUpstreamLicenses, } from '../../../tools/extension-upstream-licenses.mts'; -import { validateExtensionArtifactArchive } from './extension-artifact-inventory.mts'; +import { + assertExtensionArtifactClosure, + validateExtensionArtifactArchive, +} from './extension-artifact-inventory.mts'; import { extensionNpmPackageForProduct, extensionNpmTargetPackageForProduct, - extensionNpmWasixPackageForProduct, extensionNpmWasixAotTargets, + extensionNpmWasixPackageForProduct, nativeExtensionCargoLinksName, nativeExtensionCargoPackageName, nativeExtensionCargoPartPackageName, } from './extension-registry-packages.mts'; - -import { assertCanonicalWasixAotManifest } from '../../../../wasix/runtime/tools/wasix-aot-manifest.mts'; -import { assertWasixAotArtifactPayloads } from '../../../../wasix/runtime/tools/check-release-assets.mts'; +import { extensionRuntimeAssetContract } from './extension-runtime-asset-contract.mts'; const ROOT = path.resolve(import.meta.dirname, '../../../../..'); const TOOL = 'package-extension-release-carriers.mts'; @@ -466,14 +467,14 @@ const FROZEN_EXTENSION_INVENTORY_LIST_FIELDS = Object.freeze([ 'extensionSqlFileNames', 'extensionSqlFilePrefixes', 'sharedPreloadLibraries', + 'sqlMembers', + 'nativeModuleStems', ]); /** * Return the exact desktop inventory frozen into one product/member release row. * - * Registry materialization deliberately does not consult the repository-wide - * generated SDK catalog: independently versioned external products must remain - * bound to the metadata that was qualified and versioned with that product. + * The release must include the complete current source-owned companion closure. */ export function frozenExtensionMemberInventory(member, { product, version } = {}) { const owner = [product, version].every((value) => typeof value === 'string' && value.length > 0) @@ -516,6 +517,7 @@ export function frozenExtensionMemberInventory(member, { product, version } = {} if (inventory.dependencies.includes(sqlName)) { throw new Error(`${TOOL}: ${owner}/${sqlName} frozen dependencies must exclude itself`); } + assertExtensionArtifactClosure(inventory, 'linux-x64-gnu', `${owner}/${sqlName}`); return Object.freeze(inventory); } @@ -1018,7 +1020,11 @@ function checkedArchiveMemberPath(name, archive) { return parts.join('/'); } -function extractExtensionRuntime(asset, runtimeDir, { metadata, target, nativeRuntimeVersion }) { +export function extractExtensionRuntime( + asset, + runtimeDir, + { metadata, target, nativeRuntimeVersion }, +) { // Native release assets are stripped and platform-validated on their target // builders. Carrier assembly preserves those qualified bytes; host-side // binary rewriting would make output coordinator-dependent. diff --git a/src/extensions/catalog/extensions.source.json b/src/extensions/catalog/extensions.source.json index 6afb49f57..0fd1d6883 100644 --- a/src/extensions/catalog/extensions.source.json +++ b/src/extensions/catalog/extensions.source.json @@ -996,7 +996,7 @@ "requires": [], "relocatable": "false" }, - "dependencies": [], + "dependencies": ["fuzzystrmatch"], "load-order": ["lib/postgresql/postgis-3.so"], "lifecycle": { "create-extension": true, diff --git a/src/extensions/external/postgis/recipe.toml b/src/extensions/external/postgis/recipe.toml index 4a0b79d97..8ab503f98 100644 --- a/src/extensions/external/postgis/recipe.toml +++ b/src/extensions/external/postgis/recipe.toml @@ -24,21 +24,28 @@ trusted = false control_files = [ "share/postgresql/extension/postgis.control", "share/postgresql/extension/postgis_raster.control", + "share/postgresql/extension/postgis_topology.control", + "share/postgresql/extension/postgis_tiger_geocoder.control", ] sql_globs = [ "share/postgresql/extension/postgis--*.sql", "share/postgresql/extension/postgis_raster--*.sql", + "share/postgresql/extension/postgis_topology--*.sql", + "share/postgresql/extension/postgis_tiger_geocoder--*.sql", ] extension_sql_file_prefixes = [ "postgis_comments", "postgis_proc_set_search_path", "postgis_raster", + "postgis_topology", + "postgis_tiger_geocoder", "rtpostgis", ] extension_sql_file_names = ["uninstall_postgis.sql"] native_modules = ["lib/postgresql/postgis-3.so"] native_dependency_modules = [ "lib/postgresql/postgis_raster-3.so", + "lib/postgresql/postgis_topology-3.so", "lib/postgresql/liboliphaunt_postgis_deps.so", ] data_files = [ diff --git a/src/extensions/external/postgis/targets/native-static-registry.toml b/src/extensions/external/postgis/targets/native-static-registry.toml index 569cd4447..1fb49d877 100644 --- a/src/extensions/external/postgis/targets/native-static-registry.toml +++ b/src/extensions/external/postgis/targets/native-static-registry.toml @@ -47,15 +47,12 @@ data_files = [ "share/postgresql/proj/proj.db", ] configure_flags = [ - "--without-topology", "--without-sfcgal", "--without-address-standardizer", "--without-protobuf", ] excluded_sql_extensions = [ - "postgis_topology", "postgis_sfcgal", - "postgis_tiger_geocoder", "address_standardizer", "address_standardizer_data_us", ] diff --git a/src/extensions/external/postgis/targets/native.toml b/src/extensions/external/postgis/targets/native.toml index d8ca7f3d6..aa411bcca 100644 --- a/src/extensions/external/postgis/targets/native.toml +++ b/src/extensions/external/postgis/targets/native.toml @@ -4,6 +4,7 @@ build_kind = "autotools" module_files = [ "lib/postgresql/postgis-3.so", "lib/postgresql/postgis_raster-3.so", + "lib/postgresql/postgis_topology-3.so", ] data_files = [ "share/postgresql/gdal/GDALLogoBW.svg", @@ -43,15 +44,12 @@ data_files = [ "share/postgresql/proj/proj.db", ] configure_flags = [ - "--without-topology", "--without-sfcgal", "--without-address-standardizer", "--without-protobuf", ] excluded_sql_extensions = [ - "postgis_topology", "postgis_sfcgal", - "postgis_tiger_geocoder", "address_standardizer", "address_standardizer_data_us", ] diff --git a/src/extensions/external/postgis/targets/wasix.toml b/src/extensions/external/postgis/targets/wasix.toml index 78ead3d7d..8e17b0d73 100644 --- a/src/extensions/external/postgis/targets/wasix.toml +++ b/src/extensions/external/postgis/targets/wasix.toml @@ -5,6 +5,7 @@ build_script = "src/extensions/external/postgis/tools/build_wasix.sh" module_files = [ "lib/postgresql/postgis-3.so", "lib/postgresql/postgis_raster-3.so", + "lib/postgresql/postgis_topology-3.so", "lib/postgresql/liboliphaunt_postgis_deps.so", ] data_files = [ @@ -45,10 +46,8 @@ data_files = [ "share/postgresql/proj/proj.db", ] configure_flags = [ - "--without-topology", "--without-sfcgal", "--without-address-standardizer", - "--without-tiger", "--without-protobuf", "--disable-nls", ] @@ -56,19 +55,22 @@ required_build_files = [ "postgis/postgis-3.so", "postgis/liboliphaunt_postgis_deps.so", "raster/rt_pg/postgis_raster-3.so", + "topology/postgis_topology-3.so", "extensions/postgis/postgis.control", "extensions/postgis_raster/postgis_raster.control", + "extensions/postgis_topology/postgis_topology.control", + "extensions/postgis_tiger_geocoder/postgis_tiger_geocoder.control", "share/gdal/gdalvrt.xsd", "share/proj/proj.db", ] required_build_globs = [ "extensions/postgis/sql/postgis--*.sql", "extensions/postgis_raster/sql/postgis_raster--*.sql", + "extensions/postgis_topology/sql/postgis_topology--*.sql", + "extensions/postgis_tiger_geocoder/sql/postgis_tiger_geocoder--*.sql", ] excluded_sql_extensions = [ - "postgis_topology", "postgis_sfcgal", - "postgis_tiger_geocoder", "address_standardizer", "address_standardizer_data_us", ] @@ -85,6 +87,12 @@ runtime_path = "lib/postgresql/postgis_raster-3.so" build_path = "postgis/raster/rt_pg/postgis_raster-3.so" aot_file = "postgis_raster-llvm-opta.bin.zst" +[[native_support_modules]] +name = "postgis_topology" +runtime_path = "lib/postgresql/postgis_topology-3.so" +build_path = "postgis/topology/postgis_topology-3.so" +aot_file = "postgis_topology-llvm-opta.bin.zst" + [staging] module_source_dir = "postgis/postgis" control_source = "postgis/extensions/postgis/postgis.control" @@ -99,5 +107,5 @@ source = "postgis/share/gdal" destination = "share/postgresql/gdal" [[staging.data_dirs]] -source = "postgis/share/extension-raster" +source = "postgis/share/extension-members" destination = "share/postgresql/extension" diff --git a/src/extensions/external/postgis/tools/build_wasix.sh b/src/extensions/external/postgis/tools/build_wasix.sh index 1d2901c40..793f253a7 100755 --- a/src/extensions/external/postgis/tools/build_wasix.sh +++ b/src/extensions/external/postgis/tools/build_wasix.sh @@ -86,7 +86,7 @@ $dependency_stamp_block profile=$(oliphaunt_wasix_wasix_profile_signature) wasixcc=$wasixcc_version configure=$(oliphaunt_wasix_extension_configure_signature "$REPO_ROOT" postgis) -link=postgis-raster-plus-selected-support-side-module-v3" +link=postgis-raster-topology-plus-selected-support-side-module-v4" if [ -f "$POSTGIS_BUILD_DIR/.oliphaunt-wasix-postgis-build" ] && oliphaunt_wasix_extension_build_outputs_exist "$REPO_ROOT" postgis "$POSTGIS_BUILD_DIR" --quiet && @@ -359,11 +359,33 @@ EOF -rpath '$ORIGIN' \ -o "$POSTGIS_BUILD_DIR/raster/rt_pg/postgis_raster-3.so" + make -s -j"$JOBS" -C topology postgis_topology.o + make -s -j1 -C topology topology.sql topology_upgrade.sql uninstall_topology.sql + "$wasm_ld" \ + --shared \ + --shared-memory \ + --experimental-pic \ + --unresolved-symbols=import-dynamic \ + --extra-features=atomics,bulk-memory,mutable-globals \ + --export=__wasm_call_ctors \ + --export-if-defined=__wasm_apply_data_relocs \ + --export-all \ + --no-gc-sections \ + "$POSTGIS_BUILD_DIR/topology/postgis_topology.o" \ + "$POSTGIS_BUILD_DIR/libpgcommon/libpgcommon.a" \ + "$POSTGIS_BUILD_DIR/liblwgeom/.libs/liblwgeom.a" \ + --Bdynamic \ + -L"$POSTGIS_BUILD_DIR/postgis" \ + -loliphaunt_postgis_deps \ + -rpath '$ORIGIN' \ + -o "$POSTGIS_BUILD_DIR/topology/postgis_topology-3.so" + # Keep extension SQL generation serial: the PostGIS SQL Makefiles share # generated .tmp files and do not declare complete parallel dependency edges. oliphaunt_wasix_verify_side_module_sjlj "$postgis_deps_module" oliphaunt_wasix_verify_side_module_sjlj "$POSTGIS_BUILD_DIR/postgis/postgis-3.so" oliphaunt_wasix_verify_side_module_sjlj "$POSTGIS_BUILD_DIR/raster/rt_pg/postgis_raster-3.so" + oliphaunt_wasix_verify_side_module_sjlj "$POSTGIS_BUILD_DIR/topology/postgis_topology-3.so" make -s -j1 -C extensions postgis_extension_helper.sql make -s -j1 -C extensions all mkdir -p "$POSTGIS_BUILD_DIR/share" @@ -371,12 +393,14 @@ EOF cp -R "$PROJ_PREFIX/share/proj" "$POSTGIS_BUILD_DIR/share/proj" rm -rf "$POSTGIS_BUILD_DIR/share/gdal" cp -R "$GDAL_PREFIX/share/gdal" "$POSTGIS_BUILD_DIR/share/gdal" - rm -rf "$POSTGIS_BUILD_DIR/share/extension-raster" - mkdir -p "$POSTGIS_BUILD_DIR/share/extension-raster" - cp "$POSTGIS_BUILD_DIR/extensions/postgis_raster/postgis_raster.control" \ - "$POSTGIS_BUILD_DIR/share/extension-raster/" - cp "$POSTGIS_BUILD_DIR/extensions/postgis_raster/sql/"*.sql \ - "$POSTGIS_BUILD_DIR/share/extension-raster/" + rm -rf "$POSTGIS_BUILD_DIR/share/extension-members" + mkdir -p "$POSTGIS_BUILD_DIR/share/extension-members" + for member in postgis_raster postgis_topology postgis_tiger_geocoder; do + cp "$POSTGIS_BUILD_DIR/extensions/$member/$member.control" \ + "$POSTGIS_BUILD_DIR/share/extension-members/" + cp "$POSTGIS_BUILD_DIR/extensions/$member/sql/"*.sql \ + "$POSTGIS_BUILD_DIR/share/extension-members/" + done ) >&2 oliphaunt_wasix_extension_build_outputs_exist "$REPO_ROOT" postgis "$POSTGIS_BUILD_DIR" diff --git a/src/extensions/external/postgis/tools/windows/build-sql.sh b/src/extensions/external/postgis/tools/windows/build-sql.sh index ed936b30a..5f6a57091 100644 --- a/src/extensions/external/postgis/tools/windows/build-sql.sh +++ b/src/extensions/external/postgis/tools/windows/build-sql.sh @@ -5,6 +5,8 @@ windows_postgis_build_sql() { local directory="$build_dir/contrib/oliphaunt_external/postgis" local pg="$directory/postgis" sql="$directory/extensions/postgis/sql" raster="$directory/raster/rt_pg" local raster_sql="$directory/extensions/postgis_raster/sql" + local topology="$directory/topology" topology_sql="$directory/extensions/postgis_topology/sql" + local tiger_sql="$directory/extensions/postgis_tiger_geocoder/sql" local version pg_version version="$(windows_extension_generate postgis-version)" pg_version="$(windows_extension_generate postgres-major)" @@ -16,10 +18,14 @@ windows_postgis_build_sql() { perl "$directory/utils/create_upgrade.pl" "$sql/postgis_for_extension.sql" >"$sql/postgis_upgrade_for_extension.sql.in" perl "$directory/utils/create_uninstall.pl" "$raster/rtpostgis.sql" "$pg_version" >"$raster/uninstall_rtpostgis.sql" perl "$directory/utils/create_upgrade.pl" "$raster_sql/rtpostgis.sql" >"$raster_sql/rtpostgis_upgrade.sql.in" + perl "$directory/utils/create_upgrade.pl" "$topology/topology.sql" >"$topology/topology_upgrade.sql.in" + perl "$directory/utils/create_uninstall.pl" "$topology/topology.sql" "$pg_version" >"$topology/uninstall_topology.sql" + perl "$directory/utils/create_or_replace_to_create.pl" "$tiger_sql/postgis_tiger_geocoder_pre.sql" >"$tiger_sql/postgis_tiger_geocoder.sql" windows_extension_generate postgis-sql-upgrade perl "$directory/utils/create_extension_unpackage.pl" postgis <"$sql/raster_drop_all.sql" >"$sql/raster_unpackage_body.sql" windows_extension_generate postgis-sql-install perl "$directory/utils/create_unpackaged.pl" postgis <"$sql/postgis--$version.sql" >"$sql/postgis--unpackaged--$version.sql" perl "$directory/utils/create_unpackaged.pl" postgis_raster <"$raster_sql/postgis_raster--$version.sql" >"$raster_sql/postgis_raster--unpackaged--$version.sql" + perl "$directory/utils/create_unpackaged.pl" postgis_topology <"$topology/topology.sql" >"$topology_sql/postgis_topology--unpackaged--$version.sql" windows_extension_generate postgis-sql-unpackaged } diff --git a/src/extensions/external/postgis/tools/windows/meson.build.in b/src/extensions/external/postgis/tools/windows/meson.build.in index 4c1b37749..5e01ba6d8 100644 --- a/src/extensions/external/postgis/tools/windows/meson.build.in +++ b/src/extensions/external/postgis/tools/windows/meson.build.in @@ -206,6 +206,14 @@ postgis_raster = shared_module( ) contrib_targets += postgis_raster +postgis_topology = shared_module( + 'postgis_topology-3', + files('topology/postgis_topology.c') + postgis_common_sources, + c_pch: pch_postgres_h, + kwargs: postgis_module_args, +) +contrib_targets += postgis_topology + install_data( @EXTENSION_DATA@, kwargs: contrib_data_args, diff --git a/src/extensions/generated/extensions.catalog.json b/src/extensions/generated/extensions.catalog.json index 65675d0bc..df0b8c6ec 100644 --- a/src/extensions/generated/extensions.catalog.json +++ b/src/extensions/generated/extensions.catalog.json @@ -1177,7 +1177,9 @@ "requires": [], "relocatable": "false" }, - "dependencies": [], + "dependencies": [ + "fuzzystrmatch" + ], "load-order": [ "lib/postgresql/postgis-3.so" ], diff --git a/src/extensions/generated/sdk/extensions.json b/src/extensions/generated/sdk/extensions.json index 0bc985f91..09f565cca 100644 --- a/src/extensions/generated/sdk/extensions.json +++ b/src/extensions/generated/sdk/extensions.json @@ -1,5 +1,5 @@ { - "extension-catalog-sha256": "26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a", + "extension-catalog-sha256": "dbe1ab284693ef923cb12e35e1c75bcc7fc1ed42af1fecf9fe9fee0aaf0ef8d0", "extensions": [ { "artifact-product": "oliphaunt-extension-contrib-pg18", @@ -16,6 +16,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "amcheck", + "native-module-stems": [ + "amcheck" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -25,6 +28,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "amcheck" + ], "sql-name": "amcheck" }, { @@ -42,6 +48,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "auto_explain", + "native-module-stems": [ + "auto_explain" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -51,6 +60,7 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [], "sql-name": "auto_explain" }, { @@ -68,6 +78,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "bloom", + "native-module-stems": [ + "bloom" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -77,6 +90,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "bloom" + ], "sql-name": "bloom" }, { @@ -94,6 +110,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "btree_gin", + "native-module-stems": [ + "btree_gin" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -103,6 +122,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "btree_gin" + ], "sql-name": "btree_gin" }, { @@ -120,6 +142,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "btree_gist", + "native-module-stems": [ + "btree_gist" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -129,6 +154,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "btree_gist" + ], "sql-name": "btree_gist" }, { @@ -146,6 +174,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "citext", + "native-module-stems": [ + "citext" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -155,6 +186,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "citext" + ], "sql-name": "citext" }, { @@ -172,6 +206,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "cube", + "native-module-stems": [ + "cube" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -181,6 +218,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "cube" + ], "sql-name": "cube" }, { @@ -198,6 +238,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "dict_int", + "native-module-stems": [ + "dict_int" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -207,6 +250,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "dict_int" + ], "sql-name": "dict_int" }, { @@ -226,6 +272,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "dict_xsyn", + "native-module-stems": [ + "dict_xsyn" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -237,6 +286,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "dict_xsyn" + ], "sql-name": "dict_xsyn" }, { @@ -256,6 +308,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "earthdistance", + "native-module-stems": [ + "earthdistance" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -267,6 +322,9 @@ ], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "earthdistance" + ], "sql-name": "earthdistance" }, { @@ -284,6 +342,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "file_fdw", + "native-module-stems": [ + "file_fdw" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -293,6 +354,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "file_fdw" + ], "sql-name": "file_fdw" }, { @@ -310,6 +374,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "fuzzystrmatch", + "native-module-stems": [ + "fuzzystrmatch" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -319,6 +386,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "fuzzystrmatch" + ], "sql-name": "fuzzystrmatch" }, { @@ -336,6 +406,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "hstore", + "native-module-stems": [ + "hstore" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -345,6 +418,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "hstore" + ], "sql-name": "hstore" }, { @@ -362,6 +438,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "_int", + "native-module-stems": [ + "_int" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -371,6 +450,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "intarray" + ], "sql-name": "intarray" }, { @@ -388,6 +470,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "isn", + "native-module-stems": [ + "isn" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -397,6 +482,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "isn" + ], "sql-name": "isn" }, { @@ -414,6 +502,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "lo", + "native-module-stems": [ + "lo" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -423,6 +514,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "lo" + ], "sql-name": "lo" }, { @@ -440,6 +534,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "ltree", + "native-module-stems": [ + "ltree" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -449,6 +546,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "ltree" + ], "sql-name": "ltree" }, { @@ -466,6 +566,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "pageinspect", + "native-module-stems": [ + "pageinspect" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -475,6 +578,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "pageinspect" + ], "sql-name": "pageinspect" }, { @@ -492,6 +598,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "pg_buffercache", + "native-module-stems": [ + "pg_buffercache" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -501,6 +610,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "pg_buffercache" + ], "sql-name": "pg_buffercache" }, { @@ -518,6 +630,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "pg_freespacemap", + "native-module-stems": [ + "pg_freespacemap" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -527,6 +642,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "pg_freespacemap" + ], "sql-name": "pg_freespacemap" }, { @@ -544,6 +662,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "pg_hashids", + "native-module-stems": [ + "pg_hashids" + ], "npm-package": "@oliphaunt/extension-pg-hashids", "postgres-major": 18, "release-product": "oliphaunt-extension-pg-hashids", @@ -553,6 +674,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "oliphaunt-other-extension", + "sql-members": [ + "pg_hashids" + ], "sql-name": "pg_hashids" }, { @@ -570,6 +694,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "pg_ivm", + "native-module-stems": [ + "pg_ivm" + ], "npm-package": "@oliphaunt/extension-pg-ivm", "postgres-major": 18, "release-product": "oliphaunt-extension-pg-ivm", @@ -579,6 +706,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "oliphaunt-other-extension", + "sql-members": [ + "pg_ivm" + ], "sql-name": "pg_ivm" }, { @@ -596,6 +726,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "pg_surgery", + "native-module-stems": [ + "pg_surgery" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -605,6 +738,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "pg_surgery" + ], "sql-name": "pg_surgery" }, { @@ -622,6 +758,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "pg_textsearch", + "native-module-stems": [ + "pg_textsearch" + ], "npm-package": "@oliphaunt/extension-pg-textsearch", "postgres-major": 18, "release-product": "oliphaunt-extension-pg-textsearch", @@ -633,6 +772,9 @@ "pg_textsearch" ], "source-kind": "oliphaunt-other-extension", + "sql-members": [ + "pg_textsearch" + ], "sql-name": "pg_textsearch" }, { @@ -650,6 +792,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "pg_trgm", + "native-module-stems": [ + "pg_trgm" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -659,6 +804,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "pg_trgm" + ], "sql-name": "pg_trgm" }, { @@ -676,6 +824,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "pg_uuidv7", + "native-module-stems": [ + "pg_uuidv7" + ], "npm-package": "@oliphaunt/extension-pg-uuidv7", "postgres-major": 18, "release-product": "oliphaunt-extension-pg-uuidv7", @@ -685,6 +836,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "oliphaunt-other-extension", + "sql-members": [ + "pg_uuidv7" + ], "sql-name": "pg_uuidv7" }, { @@ -702,6 +856,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "pg_visibility", + "native-module-stems": [ + "pg_visibility" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -711,6 +868,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "pg_visibility" + ], "sql-name": "pg_visibility" }, { @@ -728,6 +888,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "pg_walinspect", + "native-module-stems": [ + "pg_walinspect" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -737,6 +900,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "pg_walinspect" + ], "sql-name": "pg_walinspect" }, { @@ -853,6 +1019,9 @@ "openssl" ], "native-module-stem": "pgcrypto", + "native-module-stems": [ + "pgcrypto" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -862,6 +1031,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "pgcrypto" + ], "sql-name": "pgcrypto" }, { @@ -886,6 +1058,7 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": null, + "native-module-stems": [], "npm-package": "@oliphaunt/extension-pgtap", "postgres-major": 18, "release-product": "oliphaunt-extension-pgtap", @@ -895,6 +1068,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "oliphaunt-other-extension", + "sql-members": [ + "pgtap" + ], "sql-name": "pgtap" }, { @@ -938,7 +1114,9 @@ "share/postgresql/gdal/tms_NZTM2000.json", "share/postgresql/proj/proj.db" ], - "dependencies": [], + "dependencies": [ + "fuzzystrmatch" + ], "display-name": "PostGIS", "extension-sql-file-names": [ "uninstall_postgis.sql" @@ -947,6 +1125,8 @@ "postgis_comments", "postgis_proc_set_search_path", "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology", "rtpostgis" ], "id": "postgis", @@ -1181,6 +1361,11 @@ "sqlite" ], "native-module-stem": "postgis-3", + "native-module-stems": [ + "postgis-3", + "postgis_raster-3", + "postgis_topology-3" + ], "npm-package": "@oliphaunt/extension-postgis", "postgres-major": 18, "release-product": "oliphaunt-extension-postgis", @@ -1234,9 +1419,17 @@ "gdal/tms_NZTM2000.json", "proj/proj.db" ], - "selected-extension-dependencies": [], + "selected-extension-dependencies": [ + "fuzzystrmatch" + ], "shared-preload-libraries": [], "source-kind": "postgis", + "sql-members": [ + "postgis", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology" + ], "sql-name": "postgis" }, { @@ -1254,6 +1447,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "seg", + "native-module-stems": [ + "seg" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -1263,6 +1459,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "seg" + ], "sql-name": "seg" }, { @@ -1280,6 +1479,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "tablefunc", + "native-module-stems": [ + "tablefunc" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -1289,6 +1491,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "tablefunc" + ], "sql-name": "tablefunc" }, { @@ -1306,6 +1511,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "tcn", + "native-module-stems": [ + "tcn" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -1315,6 +1523,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "tcn" + ], "sql-name": "tcn" }, { @@ -1332,6 +1543,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "tsm_system_rows", + "native-module-stems": [ + "tsm_system_rows" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -1341,6 +1555,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "tsm_system_rows" + ], "sql-name": "tsm_system_rows" }, { @@ -1358,6 +1575,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "tsm_system_time", + "native-module-stems": [ + "tsm_system_time" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -1367,6 +1587,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "tsm_system_time" + ], "sql-name": "tsm_system_time" }, { @@ -1386,6 +1609,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "unaccent", + "native-module-stems": [ + "unaccent" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -1397,6 +1623,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "unaccent" + ], "sql-name": "unaccent" }, { @@ -1513,6 +1742,9 @@ "portable-uuid" ], "native-module-stem": "uuid-ossp", + "native-module-stems": [ + "uuid-ossp" + ], "npm-package": "@oliphaunt/extension-contrib-pg18", "postgres-major": 18, "release-product": "liboliphaunt-native", @@ -1522,6 +1754,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "uuid-ossp" + ], "sql-name": "uuid-ossp" }, { @@ -1539,6 +1774,9 @@ "native-component-requirements": [], "native-components": [], "native-module-stem": "vector", + "native-module-stems": [ + "vector" + ], "npm-package": "@oliphaunt/extension-vector", "postgres-major": 18, "release-product": "oliphaunt-extension-vector", @@ -1548,6 +1786,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "oliphaunt-other-extension", + "sql-members": [ + "vector" + ], "sql-name": "vector" } ], diff --git a/src/extensions/generated/wasix/extensions.json b/src/extensions/generated/wasix/extensions.json index 9b95e1b0f..5b148430b 100644 --- a/src/extensions/generated/wasix/extensions.json +++ b/src/extensions/generated/wasix/extensions.json @@ -675,7 +675,9 @@ }, { "archive": "extensions/postgis.tar.zst", - "dependencies": [], + "dependencies": [ + "fuzzystrmatch" + ], "id": "postgis", "lifecycle": { "create-extension": true, @@ -726,6 +728,12 @@ "build-path": "postgis/raster/rt_pg/postgis_raster-3.so", "name": "postgis_raster", "runtime-path": "lib/postgresql/postgis_raster-3.so" + }, + { + "aot-file": "postgis_topology-llvm-opta.bin.zst", + "build-path": "postgis/topology/postgis_topology-3.so", + "name": "postgis_topology", + "runtime-path": "lib/postgresql/postgis_topology-3.so" } ], "sql-name": "postgis" diff --git a/src/extensions/tools/extension-model.test.mts b/src/extensions/tools/extension-model.test.mts index 64b618a52..6b13f6700 100644 --- a/src/extensions/tools/extension-model.test.mts +++ b/src/extensions/tools/extension-model.test.mts @@ -141,6 +141,21 @@ test('extension projections retain dependency and runtime data while deriving mo JSON.parse(outputs.get('src/extensions/generated/sdk/extensions.json')!), metadata, ); + const postgis = metadata.extensions.find((row: any) => row['sql-name'] === 'postgis'); + assert.deepEqual(postgis['native-module-stems'], [ + 'postgis-3', + 'postgis_raster-3', + 'postgis_topology-3', + ]); + assert.deepEqual(postgis['sql-members'], [ + 'postgis', + 'postgis_raster', + 'postgis_tiger_geocoder', + 'postgis_topology', + ]); + const loadOnly = metadata.extensions.find((row: any) => row['sql-name'] === 'auto_explain'); + assert.deepEqual(loadOnly['sql-members'], []); + assert.deepEqual(loadOnly['native-module-stems'], ['auto_explain']); const extension = source.extensions.find((row: any) => row.id === 'pg_textsearch'); extension.control['default-version'] = '9.8.7'; const updated = extensionProjections(source, releases).get( diff --git a/src/extensions/tools/extension-projections.mts b/src/extensions/tools/extension-projections.mts index da44b182a..fabc28521 100644 --- a/src/extensions/tools/extension-projections.mts +++ b/src/extensions/tools/extension-projections.mts @@ -72,6 +72,22 @@ export function extensionProjections(catalog: Row, releases: Row[]): Map row['sql-name'] === sql); const files = [...(recipe.artifacts?.data_files ?? contribRow?.['data-files'] ?? [])].sort(); + const nativeTargetPath = `${externalRoot}/${sql}/targets/native.toml`; + const nativeTarget = existsSync(nativeTargetPath) ? readToml(nativeTargetPath) : {}; + const primaryModule = moduleStem(extension); + const nativeModuleStems = unique( + (nativeTarget.module_files ?? (primaryModule === null ? [] : [primaryModule])).map( + (file: string) => file.replace(/^lib\/postgresql\//, '').replace(/\.(so|dylib|dll)$/, ''), + ), + ); + const sqlMembers = unique( + ( + recipe.artifacts?.control_files ?? + (extension.lifecycle?.['create-extension'] ? [`${sql}.control`] : []) + ).map((file: string) => + file.replace(/^share\/postgresql\/extension\//, '').replace(/\.control$/, ''), + ), + ); const requirements = resolutions(sql); const preload = unique( (extension.lifecycle?.['startup-config'] ?? []).flatMap((assignment: string) => @@ -122,7 +138,9 @@ export function extensionProjections(catalog: Row, releases: Row[]): Map names.has(name)) @@ -198,6 +216,8 @@ export function extensionProjections(catalog: Row, releases: Row[]): Map ` public static let ${row.id.replace(/[-_]([a-z]) mavenArtifact: row['maven-artifact'], runtimeBound: row['runtime-bound'], dependencies: row['selected-extension-dependencies'].join(','), + sqlMembers: row['sql-members'].join(','), }).map(([key, value]) => `extension.${row['sql-name']}.${key}=${value}`), ), '', @@ -521,6 +542,8 @@ function rustModule(rows: Row[]): string { option(row['shared-preload-libraries'][0] ?? null), ), ...[ + ['native_module_stems', 'native-module-stems'], + ['sql_members', 'sql-members'], ['extension_data_files', 'runtime-share-data-files'], ['extension_sql_file_prefixes', 'extension-sql-file-prefixes'], ['extension_sql_file_names', 'extension-sql-file-names'], diff --git a/src/native/runtime/bin/build-postgres18-linux.sh b/src/native/runtime/bin/build-postgres18-linux.sh index 4744211af..75160e7cd 100755 --- a/src/native/runtime/bin/build-postgres18-linux.sh +++ b/src/native/runtime/bin/build-postgres18-linux.sh @@ -155,6 +155,8 @@ required_extension_controls=( pgcrypto postgis postgis_raster + postgis_topology + postgis_tiger_geocoder pg_surgery pg_textsearch pg_trgm @@ -198,6 +200,7 @@ required_extension_modules=( pgcrypto postgis-3 postgis_raster-3 + postgis_topology-3 pg_surgery pg_textsearch pg_trgm @@ -262,8 +265,8 @@ filter_native_extension_selection() { postgis) selected_external+=(postgis) module_file="postgis-3.so" - selected_controls+=(postgis_raster) - selected_modules+=(postgis_raster-3) + selected_controls+=(postgis_raster postgis_topology postgis_tiger_geocoder) + selected_modules+=(postgis_raster-3 postgis_topology-3) ;; *) external_id="$(awk -F '\t' -v sql="$sql" 'NR > 1 && $2 == sql { print $1; found = 1; exit } END { exit found ? 0 : 1 }' "$pgxs_plan" || true)" @@ -1645,10 +1648,8 @@ build_postgis_extension() { --with-pgconfig="$install_dir/bin/pg_config" \ "${postgis_configure_args[@]}" \ --without-protobuf \ - --without-topology \ --without-sfcgal \ --without-address-standardizer \ - --without-tiger \ --disable-nls >> "$make_log" 2>&1 patch_postgis_generated_makefiles "$postgis_build_dir" make postgis_revision.h >> "$make_log" 2>&1 @@ -1658,16 +1659,20 @@ build_postgis_extension() { make -j"$jobs" -C postgis CC="$cc_string" "${postgis_make_args[@]}" install >> "$make_log" 2>&1 make -j"$jobs" -C raster/rt_core CC="$cc_string" "${postgis_make_args[@]}" all >> "$make_log" 2>&1 make -j"$jobs" -C raster/rt_pg CC="$cc_string" BE_DLLLIBS="$normal_module_be_dllibs" "${postgis_make_args[@]}" install >> "$make_log" 2>&1 + make -j"$jobs" -C topology CC="$cc_string" BE_DLLLIBS="$normal_module_be_dllibs" "${postgis_make_args[@]}" install >> "$make_log" 2>&1 make -j1 -C extensions CC="$cc_string" "${postgis_make_args[@]}" all >> "$make_log" 2>&1 make -j1 -C extensions CC="$cc_string" "${postgis_make_args[@]}" install >> "$make_log" 2>&1 make -C postgis clean >> "$make_log" 2>&1 || true make postgis_revision.h >> "$make_log" 2>&1 make -j"$jobs" -C postgis CC="$cc_string" CFLAGS="$native_cflags" BE_DLLLIBS="$embedded_module_be_dllibs" "${postgis_make_args[@]}" all >> "$make_log" 2>&1 make -C raster/rt_pg clean >> "$make_log" 2>&1 || true + make -C topology clean >> "$make_log" 2>&1 || true make -j"$jobs" -C raster/rt_pg CC="$cc_string" CFLAGS="$native_cflags" BE_DLLLIBS="$embedded_module_be_dllibs" "${postgis_make_args[@]}" all >> "$make_log" 2>&1 + make -j"$jobs" -C topology CC="$cc_string" CFLAGS="$native_cflags" BE_DLLLIBS="$embedded_module_be_dllibs" "${postgis_make_args[@]}" all >> "$make_log" 2>&1 ) copy_embedded_postgis_module "$postgis_build_dir/postgis" postgis-3 copy_embedded_postgis_module "$postgis_build_dir/raster/rt_pg" postgis_raster-3 + copy_embedded_postgis_module "$postgis_build_dir/topology" postgis_topology-3 stage_postgis_data_files "$postgis_build_dir" } diff --git a/src/native/runtime/bin/build-postgres18-macos.sh b/src/native/runtime/bin/build-postgres18-macos.sh index 1195586f2..7f53bd4f4 100755 --- a/src/native/runtime/bin/build-postgres18-macos.sh +++ b/src/native/runtime/bin/build-postgres18-macos.sh @@ -139,6 +139,8 @@ required_extension_controls=( pgcrypto postgis postgis_raster + postgis_topology + postgis_tiger_geocoder pg_surgery pg_textsearch pg_trgm @@ -182,6 +184,7 @@ required_extension_modules=( pgcrypto postgis-3 postgis_raster-3 + postgis_topology-3 pg_surgery pg_textsearch pg_trgm @@ -246,8 +249,8 @@ filter_native_extension_selection() { postgis) selected_external+=(postgis) module_file="postgis-3.so" - selected_controls+=(postgis_raster) - selected_modules+=(postgis_raster-3) + selected_controls+=(postgis_raster postgis_topology postgis_tiger_geocoder) + selected_modules+=(postgis_raster-3 postgis_topology-3) ;; *) external_id="$(awk -F '\t' -v sql="$sql" 'NR > 1 && $2 == sql { print $1; found = 1; exit } END { exit found ? 0 : 1 }' "$pgxs_plan" || true)" @@ -2220,10 +2223,8 @@ build_postgis_extension() { --with-pgconfig="$install_dir/bin/pg_config" \ "${postgis_configure_args[@]}" \ --without-protobuf \ - --without-topology \ --without-sfcgal \ --without-address-standardizer \ - --without-tiger \ --disable-nls patch_postgis_generated_makefiles "$postgis_build_dir" @@ -2237,6 +2238,7 @@ build_postgis_extension() { make -j"$jobs" -C postgis CC="$postgis_cc_string" BE_DLLLIBS="$normal_module_be_dllibs" "${postgis_make_args[@]}" install make -j"$jobs" -C raster/rt_core CC="$postgis_cc_string" "${postgis_make_args[@]}" all make -j"$jobs" -C raster/rt_pg CC="$postgis_cc_string" BE_DLLLIBS="$normal_module_be_dllibs" "${postgis_make_args[@]}" install + make -j"$jobs" -C topology CC="$postgis_cc_string" BE_DLLLIBS="$normal_module_be_dllibs" "${postgis_make_args[@]}" install # PostGIS extension SQL generation shares raster helper outputs, so keep # this packaging phase serial. make -j1 -C extensions CC="$postgis_cc_string" "${postgis_make_args[@]}" all @@ -2245,13 +2247,17 @@ build_postgis_extension() { make postgis_revision.h make -j"$jobs" -C postgis CC="$postgis_cc_string" CFLAGS="$native_cflags" BE_DLLLIBS="$embedded_module_be_dllibs" "${embedded_postgis_make_args[@]}" all make -C raster/rt_pg clean || true + make -C topology clean || true make -j"$jobs" -C raster/rt_pg CC="$postgis_cc_string" CFLAGS="$native_cflags" BE_DLLLIBS="$embedded_module_be_dllibs" "${embedded_postgis_make_args[@]}" all + make -j"$jobs" -C topology CC="$postgis_cc_string" CFLAGS="$native_cflags" BE_DLLLIBS="$embedded_module_be_dllibs" "${embedded_postgis_make_args[@]}" all ) normalize_installed_module_suffix postgis-3 normalize_installed_module_suffix postgis_raster-3 + normalize_installed_module_suffix postgis_topology-3 copy_embedded_postgis_module "$postgis_build_dir/postgis" postgis-3 copy_embedded_postgis_module "$postgis_build_dir/raster/rt_pg" postgis_raster-3 + copy_embedded_postgis_module "$postgis_build_dir/topology" postgis_topology-3 stage_postgis_data_files "$postgis_build_dir" } diff --git a/src/native/runtime/bin/build-postgres18-windows.sh b/src/native/runtime/bin/build-postgres18-windows.sh index 3399fe7ef..c83ce7b72 100755 --- a/src/native/runtime/bin/build-postgres18-windows.sh +++ b/src/native/runtime/bin/build-postgres18-windows.sh @@ -116,9 +116,16 @@ extension_catalog() { } windows_extension_modules() { [ "$build_extensions" != 0 ] || return 0 - local sql_name stem + local sql_name stem postgis_stems while IFS=$'\t' read -r sql_name stem; do - if selected "$sql_name" && [ "$stem" != - ]; then printf '%s\t%s\n' "$sql_name" "$stem"; fi + if selected "$sql_name" && [ "$stem" != - ]; then + if [ "$sql_name" = postgis ]; then + postgis_stems="$(windows_extension_generate postgis-module-stems)" || return + while IFS= read -r stem; do printf '%s\t%s\n' "$sql_name" "$stem"; done <<<"$postgis_stems" + else + printf '%s\t%s\n' "$sql_name" "$stem" + fi + fi done < <(awk -F '\t' 'NR>1 { print $1 "\t" $2 }' "$catalog") } windows_extension_prune() { @@ -294,7 +301,7 @@ desired_hash="$( while IFS= read -r -d '' input; do printf '%s\n' "$input" sha256 "$input" - done < <(find extensions/external src/third-party/icu src/third-party/openssl -type f ! -name '*.test.*' ! -path '*/fixtures/*' ! -name '*.md' -print0 | sort -z) + done < <(find src/extensions/external src/third-party/icu src/third-party/openssl -type f ! -name '*.test.*' ! -path '*/fixtures/*' ! -name '*.md' -print0 | sort -z) fi } | sha256sum | awk '{print $1}' )" diff --git a/src/native/runtime/bin/mobile-postgis-extensions.sh b/src/native/runtime/bin/mobile-postgis-extensions.sh index 2ddbbd5ff..5106cff89 100644 --- a/src/native/runtime/bin/mobile-postgis-extensions.sh +++ b/src/native/runtime/bin/mobile-postgis-extensions.sh @@ -44,6 +44,8 @@ oliphaunt_postgis_output_fingerprint() { local archive="$5" local control_file="$install_dir/share/postgresql/extension/postgis.control" local raster_control_file="$install_dir/share/postgresql/extension/postgis_raster.control" + local topology_control_file="$install_dir/share/postgresql/extension/postgis_topology.control" + local tiger_control_file="$install_dir/share/postgresql/extension/postgis_tiger_geocoder.control" local proj_database="$install_dir/share/postgresql/proj/proj.db" local gdal_schema="$install_dir/share/postgresql/gdal/gdalvrt.xsd" local sql_source_root @@ -51,12 +53,12 @@ oliphaunt_postgis_output_fingerprint() { local sql_count=0 local -a outputs - outputs=("$archive" "$objects_file" "$alias_file" "$control_file" "$raster_control_file" "$proj_database" "$gdal_schema") + outputs=("$archive" "$objects_file" "$alias_file" "$control_file" "$raster_control_file" "$topology_control_file" "$tiger_control_file" "$proj_database" "$gdal_schema") while IFS= read -r object; do [ -n "$object" ] || continue outputs+=("$object") done < "$objects_file" - for sql_source_root in "$postgis_build/extensions/postgis/sql" "$postgis_build/extensions/postgis_raster/sql"; do + for sql_source_root in "$postgis_build/extensions/postgis/sql" "$postgis_build/extensions/postgis_raster/sql" "$postgis_build/extensions/postgis_topology/sql" "$postgis_build/extensions/postgis_tiger_geocoder/sql"; do while IFS= read -r sql_source; do [ -n "$sql_source" ] || continue installed_sql="$install_dir/share/postgresql/extension/$(basename "$sql_source")" @@ -653,17 +655,18 @@ oliphaunt_postgis_stage_runtime_sql() { "$install_dir/share/postgresql/extension" \ "$install_dir/share/postgresql/proj" \ "$install_dir/share/postgresql/gdal" - cp -p "$postgis_build/extensions/postgis/postgis.control" \ - "$install_dir/share/postgresql/extension/postgis.control" - cp -p "$postgis_build/extensions/postgis/sql/"*.sql \ - "$install_dir/share/postgresql/extension/" - cp -p "$postgis_build/extensions/postgis_raster/postgis_raster.control" \ - "$install_dir/share/postgresql/extension/postgis_raster.control" - cp -p "$postgis_build/extensions/postgis_raster/sql/"*.sql \ - "$install_dir/share/postgresql/extension/" - perl -0pi -e 's|\$libdir/postgis_raster-3(?:\.6)?|\$libdir/postgis-3|g' \ - "$install_dir/share/postgresql/extension/postgis_raster.control" \ - "$install_dir/share/postgresql/extension/postgis_raster--"*.sql + local member + for member in postgis postgis_raster postgis_topology postgis_tiger_geocoder; do + cp -p "$postgis_build/extensions/$member/$member.control" \ + "$install_dir/share/postgresql/extension/" + cp -p "$postgis_build/extensions/$member/sql/"*.sql \ + "$install_dir/share/postgresql/extension/" + done + for member in postgis_raster postgis_topology; do + perl -0pi -e 's#\$libdir/postgis_(?:raster|topology)-3(?:\.6)?#\$libdir/postgis-3#g' \ + "$install_dir/share/postgresql/extension/$member.control" \ + "$install_dir/share/postgresql/extension/$member--"*.sql + done cp -p "$mobile_static_dependency_root/proj/share/proj/proj.db" \ "$install_dir/share/postgresql/proj/proj.db" cp -a "$gdal_data/." "$install_dir/share/postgresql/gdal/" @@ -710,6 +713,30 @@ oliphaunt_postgis_patch_raster_makefile() { ' "$makefile" } +oliphaunt_postgis_patch_topology_makefile() { + local postgis_build="$1" + local prefix="$2" + local makefile="$postgis_build/topology/Makefile" + [ -f "$makefile" ] || oliphaunt_postgis_fail "PostGIS topology Makefile is missing: $makefile" + OLIPHAUNT_POSTGIS_TOPOLOGY_PREFIX="$prefix" perl -0pi -e ' + my $prefix = $ENV{OLIPHAUNT_POSTGIS_TOPOLOGY_PREFIX}; + my $defs = " -DPg_magic_func=${prefix}_topology_Pg_magic_func" . + " -D_PG_init=${prefix}_topology_PG_init" . + " -D_PG_fini=${prefix}_topology_PG_fini"; + my $updated = s|^(PG_CPPFLAGS \+= .*)$|$1$defs|m; + die "could not patch PostGIS topology PG_CPPFLAGS\n" unless $updated; + ' "$makefile" +} + +oliphaunt_postgis_build_topology_objects() { + local postgis_build="$1" + if ! make -j"$jobs" -C "$postgis_build/topology" postgis_topology.o >> "$make_log" 2>&1; then + tail -n 200 "$make_log" >&2 || true + oliphaunt_postgis_fail "PostGIS topology object compilation failed" + fi + make -j1 -C "$postgis_build/topology" topology.sql topology_upgrade.sql uninstall_topology.sql >> "$make_log" 2>&1 +} + oliphaunt_postgis_build_combined_init() { local postgis_build="$1" local prefix="$2" @@ -719,9 +746,11 @@ oliphaunt_postgis_build_combined_init() { cat > "$source" <> "$make_log" 2>&1 @@ -790,6 +819,8 @@ build_postgis_mobile_static_extension_objects() { if [ -f "$archive" ] && [ -s "$objects_file" ] && [ -s "$alias_file" ] && [ -f "$install_dir/share/postgresql/extension/postgis.control" ] && [ -f "$install_dir/share/postgresql/extension/postgis_raster.control" ] && + [ -f "$install_dir/share/postgresql/extension/postgis_topology.control" ] && + [ -f "$install_dir/share/postgresql/extension/postgis_tiger_geocoder.control" ] && [ -s "$build_stamp" ] && [ "$(cat "$build_stamp")" = "$build_fingerprint" ] && [ -s "$output_stamp" ] && output_fingerprint="$(oliphaunt_postgis_output_fingerprint "$postgis_build" "$object_dir" "$objects_file" "$alias_file" "$archive")" && @@ -840,10 +871,8 @@ build_postgis_mobile_static_extension_objects() { --with-gdalconfig="$gdal_config" --with-xml2config="$mobile_static_dependency_root/libxml2/bin/xml2-config" --without-protobuf - --without-topology --without-sfcgal --without-address-standardizer - --without-tiger --disable-nls ) case "$oliphaunt_mobile_target" in @@ -882,12 +911,14 @@ build_postgis_mobile_static_extension_objects() { ./configure --build="$build_alias" "${configure_args[@]}" >> "$make_log" 2>&1 oliphaunt_postgis_patch_extension_makefile "$postgis_build" "$prefix" oliphaunt_postgis_patch_raster_makefile "$postgis_build" "$prefix" + oliphaunt_postgis_patch_topology_makefile "$postgis_build" "$prefix" make -j"$jobs" -C liblwgeom liblwgeom.la >> "$make_log" 2>&1 make -j"$jobs" -C libpgcommon libpgcommon.a >> "$make_log" 2>&1 make -j"$jobs" -C deps/flatgeobuf all >> "$make_log" 2>&1 make -j"$jobs" -C postgis all >> "$make_log" 2>&1 || true make -j"$jobs" -C raster/rt_core all >> "$make_log" 2>&1 oliphaunt_postgis_build_raster_objects "$postgis_build" + oliphaunt_postgis_build_topology_objects "$postgis_build" make -j1 -C extensions postgis_extension_helper.sql >> "$make_log" 2>&1 make -j1 -C extensions all >> "$make_log" 2>&1 ) @@ -903,6 +934,7 @@ build_postgis_mobile_static_extension_objects() { [ -n "$object" ] || continue oliphaunt_postgis_stage_object "$object" raster "$object_dir" "$objects_file" done < <(find "$postgis_build/raster/rt_pg" -maxdepth 1 -type f -name '*.o' -print | LC_ALL=C sort) + oliphaunt_postgis_stage_object "$postgis_build/topology/postgis_topology.o" topology "$object_dir" "$objects_file" for object in \ "$postgis_build/deps/flatgeobuf/flatgeobuf_c.o" \ "$postgis_build/deps/flatgeobuf/geometrywriter.o" \ diff --git a/src/native/runtime/bin/mobile-postgis-extensions.test.sh b/src/native/runtime/bin/mobile-postgis-extensions.test.sh index 6e492e8fa..48c641faf 100755 --- a/src/native/runtime/bin/mobile-postgis-extensions.test.sh +++ b/src/native/runtime/bin/mobile-postgis-extensions.test.sh @@ -117,4 +117,71 @@ if (oliphaunt_postgis_build_raster_objects "$postgis_build") > "$test_root/failu exit 1 fi +mkdir -p "$postgis_build/topology" +cat > "$postgis_build/topology/Makefile" <<'MAKEFILE' +PG_CPPFLAGS += -Ifixture +.PHONY: all print-flags +all: + @exit 1 +postgis_topology.o topology.sql topology_upgrade.sql uninstall_topology.sql: + @: > $@ +print-flags: + @printf '%s\n' '$(PG_CPPFLAGS)' +MAKEFILE +oliphaunt_postgis_patch_topology_makefile "$postgis_build" "$prefix" +topology_flags="$(make -s -f "$postgis_build/topology/Makefile" print-flags)" +for expected in \ + "-DPg_magic_func=${prefix}_topology_Pg_magic_func" \ + "-D_PG_init=${prefix}_topology_PG_init" \ + "-D_PG_fini=${prefix}_topology_PG_fini"; do + case " $topology_flags " in + *" $expected "*) ;; + *) echo "missing topology compile definition: $expected" >&2; exit 1 ;; + esac +done +oliphaunt_postgis_build_topology_objects "$postgis_build" +for output in postgis_topology.o topology.sql topology_upgrade.sql uninstall_topology.sql; do + [ -f "$postgis_build/topology/$output" ] +done + +# The merged carrier must initialize all three C modules in dependency order. +cc=("${CC:-cc}") +native_cflags="" +object_dir="$test_root/objects" +mkdir -p "$object_dir" +combined_object="$(oliphaunt_postgis_build_combined_init "$postgis_build" "$prefix" "$object_dir")" +cat > "$test_root/init-test.c" < +static int initialized; +void ${prefix}_core_PG_init(void) { assert(initialized++ == 0); } +void ${prefix}_raster_PG_init(void) { assert(initialized++ == 1); } +void ${prefix}_topology_PG_init(void) { assert(initialized++ == 2); } +extern void ${prefix}__PG_init(void); +int main(void) { ${prefix}__PG_init(); assert(initialized == 3); } +EOF +"${cc[@]}" "$test_root/init-test.c" "$combined_object" -o "$test_root/init-test" +"$test_root/init-test" + +install_dir="$test_root/install" +mkdir -p "$mobile_static_dependency_root/proj/share/proj" +printf 'database\n' > "$mobile_static_dependency_root/proj/share/proj/proj.db" +for member in postgis postgis_raster postgis_topology postgis_tiger_geocoder; do + mkdir -p "$postgis_build/extensions/$member/sql" + printf 'module_pathname = '\''$libdir/%s-3'\''\n' "$member" > "$postgis_build/extensions/$member/$member.control" + printf 'SELECT '\''$libdir/%s-3.6'\'';\n' "$member" > "$postgis_build/extensions/$member/sql/$member--3.6.4.sql" +done +oliphaunt_postgis_stage_runtime_sql "$postgis_build" +for member in postgis postgis_raster postgis_topology postgis_tiger_geocoder; do + [ -s "$install_dir/share/postgresql/extension/$member.control" ] + [ -s "$install_dir/share/postgresql/extension/$member--3.6.4.sql" ] +done +for member in postgis_raster postgis_topology; do + grep -F '$libdir/postgis-3' "$install_dir/share/postgresql/extension/$member.control" >/dev/null + grep -F '$libdir/postgis-3' "$install_dir/share/postgresql/extension/$member--3.6.4.sql" >/dev/null + if grep -F "$member-3" "$install_dir/share/postgresql/extension/$member--3.6.4.sql"; then + echo "mobile SQL retained a separate module identity" >&2 + exit 1 + fi +done + echo "mobile PostGIS build contracts passed" diff --git a/src/native/rust-bindings/src/extension.rs b/src/native/rust-bindings/src/extension.rs index fa4ac3196..9146ff484 100644 --- a/src/native/rust-bindings/src/extension.rs +++ b/src/native/rust-bindings/src/extension.rs @@ -42,6 +42,19 @@ impl Extension { .map(|stem| format!("{}{}", stem, std::env::consts::DLL_SUFFIX)) } + /// Every native PostgreSQL module shipped by this extension product. + pub fn native_module_files(self) -> Vec { + generated_extensions::native_module_stems(self) + .iter() + .map(|stem| format!("{stem}{}", std::env::consts::DLL_SUFFIX)) + .collect() + } + + /// SQL extensions installed from this product's shared artifact. + pub const fn sql_members(self) -> &'static [&'static str] { + generated_extensions::sql_members(self) + } + pub const fn creates_extension(self) -> bool { generated_extensions::creates_extension(self) } @@ -261,6 +274,16 @@ mod package_tests { version: "9.8.8", ..PACKAGE }; + static POSTGIS_PACKAGE: ExtensionPackage = ExtensionPackage { + product: "oliphaunt-extension-postgis", + ..PACKAGE + }; + let postgis = Extension::POSTGIS.with_package(&POSTGIS_PACKAGE); + assert_eq!( + resolve_extensions(&[postgis]).unwrap(), + [Extension::FUZZYSTRMATCH, postgis], + "independent packages use the current catalog dependency contract" + ); let selected = Extension::EARTHDISTANCE.with_package(&PACKAGE); let resolved = resolve_extensions(&[selected]).unwrap(); assert_eq!( diff --git a/src/native/rust-bindings/src/generated/extensions.rs b/src/native/rust-bindings/src/generated/extensions.rs index 2e7352ab0..0f35270fa 100644 --- a/src/native/rust-bindings/src/generated/extensions.rs +++ b/src/native/rust-bindings/src/generated/extensions.rs @@ -546,7 +546,7 @@ pub(super) const fn dependencies(extension: Extension) -> &'static [Extension] { ExtensionId::PgWalinspect => &[], ExtensionId::Pgcrypto => &[], ExtensionId::Pgtap => &[], - ExtensionId::Postgis => &[], + ExtensionId::Postgis => &[Extension::FUZZYSTRMATCH], ExtensionId::Seg => &[], ExtensionId::Tablefunc => &[], ExtensionId::Tcn => &[], @@ -603,6 +603,101 @@ pub(super) const fn required_shared_preload_library(extension: Extension) -> Opt } } +/// Generated extension metadata accessor. +pub(super) const fn native_module_stems(extension: Extension) -> &'static [&'static str] { + match extension.id { + ExtensionId::Amcheck => &["amcheck"], + ExtensionId::AutoExplain => &["auto_explain"], + ExtensionId::Bloom => &["bloom"], + ExtensionId::BtreeGin => &["btree_gin"], + ExtensionId::BtreeGist => &["btree_gist"], + ExtensionId::Citext => &["citext"], + ExtensionId::Cube => &["cube"], + ExtensionId::DictInt => &["dict_int"], + ExtensionId::DictXsyn => &["dict_xsyn"], + ExtensionId::Earthdistance => &["earthdistance"], + ExtensionId::FileFdw => &["file_fdw"], + ExtensionId::Fuzzystrmatch => &["fuzzystrmatch"], + ExtensionId::Hstore => &["hstore"], + ExtensionId::Intarray => &["_int"], + ExtensionId::Isn => &["isn"], + ExtensionId::Lo => &["lo"], + ExtensionId::Ltree => &["ltree"], + ExtensionId::Pageinspect => &["pageinspect"], + ExtensionId::PgBuffercache => &["pg_buffercache"], + ExtensionId::PgFreespacemap => &["pg_freespacemap"], + ExtensionId::PgHashids => &["pg_hashids"], + ExtensionId::PgIvm => &["pg_ivm"], + ExtensionId::PgSurgery => &["pg_surgery"], + ExtensionId::PgTextsearch => &["pg_textsearch"], + ExtensionId::PgTrgm => &["pg_trgm"], + ExtensionId::PgUuidv7 => &["pg_uuidv7"], + ExtensionId::PgVisibility => &["pg_visibility"], + ExtensionId::PgWalinspect => &["pg_walinspect"], + ExtensionId::Pgcrypto => &["pgcrypto"], + ExtensionId::Pgtap => &[], + ExtensionId::Postgis => &["postgis-3", "postgis_raster-3", "postgis_topology-3"], + ExtensionId::Seg => &["seg"], + ExtensionId::Tablefunc => &["tablefunc"], + ExtensionId::Tcn => &["tcn"], + ExtensionId::TsmSystemRows => &["tsm_system_rows"], + ExtensionId::TsmSystemTime => &["tsm_system_time"], + ExtensionId::Unaccent => &["unaccent"], + ExtensionId::UuidOssp => &["uuid-ossp"], + ExtensionId::Vector => &["vector"], + } +} + +/// Generated extension metadata accessor. +pub(super) const fn sql_members(extension: Extension) -> &'static [&'static str] { + match extension.id { + ExtensionId::Amcheck => &["amcheck"], + ExtensionId::AutoExplain => &[], + ExtensionId::Bloom => &["bloom"], + ExtensionId::BtreeGin => &["btree_gin"], + ExtensionId::BtreeGist => &["btree_gist"], + ExtensionId::Citext => &["citext"], + ExtensionId::Cube => &["cube"], + ExtensionId::DictInt => &["dict_int"], + ExtensionId::DictXsyn => &["dict_xsyn"], + ExtensionId::Earthdistance => &["earthdistance"], + ExtensionId::FileFdw => &["file_fdw"], + ExtensionId::Fuzzystrmatch => &["fuzzystrmatch"], + ExtensionId::Hstore => &["hstore"], + ExtensionId::Intarray => &["intarray"], + ExtensionId::Isn => &["isn"], + ExtensionId::Lo => &["lo"], + ExtensionId::Ltree => &["ltree"], + ExtensionId::Pageinspect => &["pageinspect"], + ExtensionId::PgBuffercache => &["pg_buffercache"], + ExtensionId::PgFreespacemap => &["pg_freespacemap"], + ExtensionId::PgHashids => &["pg_hashids"], + ExtensionId::PgIvm => &["pg_ivm"], + ExtensionId::PgSurgery => &["pg_surgery"], + ExtensionId::PgTextsearch => &["pg_textsearch"], + ExtensionId::PgTrgm => &["pg_trgm"], + ExtensionId::PgUuidv7 => &["pg_uuidv7"], + ExtensionId::PgVisibility => &["pg_visibility"], + ExtensionId::PgWalinspect => &["pg_walinspect"], + ExtensionId::Pgcrypto => &["pgcrypto"], + ExtensionId::Pgtap => &["pgtap"], + ExtensionId::Postgis => &[ + "postgis", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology", + ], + ExtensionId::Seg => &["seg"], + ExtensionId::Tablefunc => &["tablefunc"], + ExtensionId::Tcn => &["tcn"], + ExtensionId::TsmSystemRows => &["tsm_system_rows"], + ExtensionId::TsmSystemTime => &["tsm_system_time"], + ExtensionId::Unaccent => &["unaccent"], + ExtensionId::UuidOssp => &["uuid-ossp"], + ExtensionId::Vector => &["vector"], + } +} + /// Generated extension metadata accessor. pub(super) const fn extension_data_files(extension: Extension) -> &'static [&'static str] { match extension.id { @@ -721,6 +816,8 @@ pub(super) const fn extension_sql_file_prefixes(extension: Extension) -> &'stati "postgis_comments", "postgis_proc_set_search_path", "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology", "rtpostgis", ], ExtensionId::Seg => &[], diff --git a/src/native/rust-bindings/src/liboliphaunt/root/extensions.rs b/src/native/rust-bindings/src/liboliphaunt/root/extensions.rs index 783e60b0f..37e480849 100644 --- a/src/native/rust-bindings/src/liboliphaunt/root/extensions.rs +++ b/src/native/rust-bindings/src/liboliphaunt/root/extensions.rs @@ -5,9 +5,9 @@ use std::path::Path; use super::files::copy_file_preserving_permissions; use crate::error::{Error, Result}; -#[cfg(test)] -use crate::extension::extension_install_sql_file_belongs; -use crate::extension::{Extension, extension_data_files, extension_sql_file_belongs}; +use crate::extension::{ + Extension, extension_data_files, extension_install_sql_file_belongs, extension_sql_file_belongs, +}; pub(super) fn selected_extension_names(extensions: &[Extension]) -> Vec<&'static str> { let mut names = extensions @@ -27,16 +27,16 @@ pub(super) fn core_share_file(relative: &Path) -> bool { { return false; } - !matches!( - relative.to_str(), - Some("tsearch_data/unaccent.rules" | "tsearch_data/xsyn_sample.rules") - ) + !Extension::ALL + .iter() + .flat_map(|extension| extension_data_files(*extension)) + .any(|file| relative == Path::new(file)) } pub(super) fn packaged_extension_module_files() -> BTreeSet { Extension::ALL .iter() - .filter_map(|extension| extension.native_module_file()) + .flat_map(|extension| extension.native_module_files()) .collect() } @@ -47,10 +47,6 @@ pub(super) fn embedded_core_module_files() -> BTreeSet { .collect() } -pub(super) fn data_files(extension: Extension) -> &'static [&'static str] { - extension_data_files(extension) -} - pub(super) fn copy_extension_sql_files( source_share: &Path, target_share: &Path, @@ -92,7 +88,7 @@ pub(super) fn copy_named_extension_sql_files( if extension_sql_file_belongs(sql_name, &file_name) { if extension_sql_file_is_control(sql_name, &file_name) { copied_control = true; - } else if extension_sql_file_is_sql(&file_name) { + } else if extension_install_sql_file_belongs(sql_name, &file_name) { copied_sql = true; } copy_file_preserving_permissions(&entry.path(), &target_dir.join(&file_name))?; @@ -115,20 +111,6 @@ pub(super) fn copy_named_extension_sql_files( Ok(()) } -pub(super) fn copy_extension_data_files( - source_share: &Path, - target_share: &Path, - extension: Extension, -) -> Result<()> { - for relative in extension_data_files(extension) { - copy_file_preserving_permissions( - &source_share.join(relative), - &target_share.join(relative), - )?; - } - Ok(()) -} - pub(super) fn copy_embedded_module( embedded_modules: &Path, target_lib: &Path, diff --git a/src/native/rust-bindings/src/liboliphaunt/root/runtime/cache_key.rs b/src/native/rust-bindings/src/liboliphaunt/root/runtime/cache_key.rs index 056ec98e6..20127268c 100644 --- a/src/native/rust-bindings/src/liboliphaunt/root/runtime/cache_key.rs +++ b/src/native/rust-bindings/src/liboliphaunt/root/runtime/cache_key.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use super::super::NativeRuntimeProfile; use super::super::extensions::{ - core_share_file, data_files, embedded_core_module_files, extension_sql_file_is_control, + core_share_file, embedded_core_module_files, extension_sql_file_is_control, extension_sql_file_is_sql, packaged_extension_module_files, selected_extension_names, }; use super::super::files::sorted_read_dir; @@ -15,9 +15,9 @@ use super::super::fingerprint::{ use super::super::{NATIVE_RUNTIME_TOOLS, existing_native_tool_path, native_tool_path}; use super::extension_artifact_root_for; use crate::error::{Error, Result}; -use crate::extension::Extension; +use crate::extension::{Extension, extension_data_files as data_files}; -const RUNTIME_CACHE_VERSION: &str = "pg18-runtime-cache-v7"; +const RUNTIME_CACHE_VERSION: &str = "pg18-runtime-cache-v8"; #[cfg(test)] pub(super) fn runtime_cache_key( @@ -91,6 +91,12 @@ pub(super) fn runtime_cache_key_with_icu( extension_artifact_root_for(install_dir, extension_artifact_dirs, *extension); let extension_share = extension_root.join("share/postgresql"); fingerprint_named_extension_sql_files(&mut state, &extension_share, extension.sql_name())?; + for member in extension.sql_members() { + hash_str(&mut state, member); + if *member != extension.sql_name() { + fingerprint_named_extension_sql_files(&mut state, &extension_share, member)?; + } + } for relative in data_files(*extension) { fingerprint_optional_file( &mut state, @@ -139,14 +145,11 @@ pub(super) fn runtime_cache_key_with_icu( )?; } for extension in extensions { - if let Some(module) = extension.native_module_file() { - let extension_root = extension_artifact_root_for( - install_dir, - extension_artifact_dirs, - *extension, - ); + let extension_root = + extension_artifact_root_for(install_dir, extension_artifact_dirs, *extension); + for module in extension.native_module_files() { let extension_lib = extension_root.join("lib/modules"); - if extension_lib.join(&module).is_file() { + if extension_root != install_dir || extension_lib.join(&module).is_file() { fingerprint_optional_file( &mut state, &extension_lib, @@ -164,12 +167,9 @@ pub(super) fn runtime_cache_key_with_icu( } NativeRuntimeProfile::PostgresServer => { for extension in extensions { - if let Some(module) = extension.native_module_file() { - let extension_root = extension_artifact_root_for( - install_dir, - extension_artifact_dirs, - *extension, - ); + let extension_root = + extension_artifact_root_for(install_dir, extension_artifact_dirs, *extension); + for module in extension.native_module_files() { let extension_lib = extension_root.join("lib/postgresql"); fingerprint_optional_file( &mut state, @@ -244,19 +244,20 @@ pub(super) fn cached_runtime_is_valid_with_icu( } for extension in extensions { - if extension.creates_extension() - && (!cache_dir + for sql_name in extension.sql_members() { + if !cache_dir .join("share/postgresql/extension") - .join(format!("{}.control", extension.sql_name())) + .join(format!("{sql_name}.control")) .is_file() - || !cache_contains_extension_sql_file(cache_dir, *extension)) - { - return false; + || !cache_contains_extension_sql_file(cache_dir, sql_name) + { + return false; + } } - if let Some(module) = extension.native_module_file() - && !cache_dir.join("lib/postgresql").join(module).is_file() - { - return false; + for module in extension.native_module_files() { + if !cache_dir.join("lib/postgresql").join(module).is_file() { + return false; + } } for relative in data_files(*extension) { if !cache_dir.join("share/postgresql").join(relative).is_file() { @@ -267,16 +268,16 @@ pub(super) fn cached_runtime_is_valid_with_icu( true } -fn cache_contains_extension_sql_file(cache_dir: &Path, extension: Extension) -> bool { +fn cache_contains_extension_sql_file(cache_dir: &Path, sql_name: &str) -> bool { let extension_dir = cache_dir.join("share/postgresql/extension"); let Ok(entries) = fs::read_dir(extension_dir) else { return false; }; entries.filter_map(|entry| entry.ok()).any(|entry| { let file_name = entry.file_name().to_string_lossy().into_owned(); - !extension_sql_file_is_control(extension.sql_name(), &file_name) + !extension_sql_file_is_control(sql_name, &file_name) && extension_sql_file_is_sql(&file_name) - && crate::extension::extension_sql_file_belongs(extension.sql_name(), &file_name) + && crate::extension::extension_install_sql_file_belongs(sql_name, &file_name) && entry.path().is_file() }) } @@ -550,6 +551,143 @@ mod tests { ); } + #[test] + fn postgis_companion_profiles_and_runtime_files_invalidate_only_selected_caches() { + let temp = TempTree::new("postgis-companion-cache"); + let install_dir = temp.path().join("install"); + let artifact_dir = temp.path().join("extension/postgis"); + let embedded_modules = temp.path().join("embedded-modules"); + write_fake_install(&install_dir); + write_fake_postgis_extension(&artifact_dir); + write_fake_postgis_extension(&install_dir); + for module in embedded_core_module_files() { + write_file(&embedded_modules.join(module), b"embedded-core"); + } + let key = |profile| { + runtime_cache_key( + profile, + &install_dir, + Some(&embedded_modules), + std::slice::from_ref(&artifact_dir), + &[Extension::POSTGIS], + ) + .unwrap() + }; + let server = key(NativeRuntimeProfile::PostgresServer); + let embedded = key(NativeRuntimeProfile::OliphauntEmbedded); + let companion = format!("postgis_raster-3{}", std::env::consts::DLL_SUFFIX); + write_file( + &artifact_dir.join("lib/postgresql").join(&companion), + b"changed server companion", + ); + assert_ne!(server, key(NativeRuntimeProfile::PostgresServer)); + assert_eq!(embedded, key(NativeRuntimeProfile::OliphauntEmbedded)); + write_file( + &artifact_dir.join("lib/modules").join(&companion), + b"changed embedded companion", + ); + let changed_module = key(NativeRuntimeProfile::OliphauntEmbedded); + assert_ne!(embedded, changed_module); + write_file( + &artifact_dir.join("share/postgresql/extension/postgis_topology.control"), + b"changed topology control", + ); + let changed_control = key(NativeRuntimeProfile::OliphauntEmbedded); + assert_ne!(changed_module, changed_control); + write_file( + &artifact_dir.join("share/postgresql/extension/postgis_tiger_geocoder--1.0.sql"), + b"changed tiger install", + ); + let changed_sql = key(NativeRuntimeProfile::OliphauntEmbedded); + assert_ne!(changed_control, changed_sql); + write_file( + &artifact_dir.join("share/postgresql/proj/proj.db"), + b"changed projection database", + ); + assert_ne!(changed_sql, key(NativeRuntimeProfile::OliphauntEmbedded)); + + let unselected = runtime_cache_key( + NativeRuntimeProfile::PostgresServer, + &install_dir, + None, + &[], + &[], + ) + .unwrap(); + for module in Extension::POSTGIS.native_module_files() { + write_file( + &install_dir.join("lib/postgresql").join(module), + b"unselected changed module", + ); + } + for relative in data_files(Extension::POSTGIS) { + write_file( + &install_dir.join("share/postgresql").join(relative), + b"unselected changed data", + ); + } + assert_eq!( + unselected, + runtime_cache_key( + NativeRuntimeProfile::PostgresServer, + &install_dir, + None, + &[], + &[] + ) + .unwrap() + ); + } + + #[test] + fn cached_postgis_runtime_requires_every_companion_module_control_and_install_script() { + let temp = TempTree::new("postgis-cache-completeness"); + let cache_dir = temp.path().join("cache"); + write_minimal_cache_dir(&cache_dir, "cache-key"); + + write_fake_postgis_extension(&cache_dir); + let valid = || { + cached_runtime_is_valid( + NativeRuntimeProfile::PostgresServer, + &cache_dir, + "cache-key", + &[Extension::POSTGIS], + ) + }; + assert!(valid()); + let mut required = Extension::POSTGIS + .native_module_files() + .into_iter() + .map(|module| cache_dir.join("lib/postgresql").join(module)) + .collect::>(); + for member in Extension::POSTGIS.sql_members() { + required.push( + cache_dir + .join("share/postgresql/extension") + .join(format!("{member}.control")), + ); + required.push( + cache_dir + .join("share/postgresql/extension") + .join(format!("{member}--1.0.sql")), + ); + write_file( + &cache_dir + .join("share/postgresql/extension") + .join(format!("{member}--ANY--1.0.sql")), + b"upgrade alone is insufficient", + ); + } + required.push(cache_dir.join("share/postgresql/proj/proj.db")); + for file in required { + let bytes = fs::read(&file).unwrap(); + fs::remove_file(&file).unwrap(); + assert!(!valid(), "cache accepted missing {}", file.display()); + write_file(&file, &bytes); + } + assert!(valid()); + } + #[test] fn icu_data_content_does_not_participate_in_base_runtime_cache_key() { let temp = TempTree::new("icu-data"); @@ -678,7 +816,6 @@ mod tests { let temp = TempTree::new("validation"); let cache_dir = temp.path().join("cache"); write_minimal_cache_dir(&cache_dir, "cache-key"); - assert!( cached_runtime_is_valid( NativeRuntimeProfile::PostgresServer, @@ -859,6 +996,33 @@ mod tests { ); } + fn write_fake_postgis_extension(root: &Path) { + for member in Extension::POSTGIS.sql_members() { + write_file( + &root + .join("share/postgresql/extension") + .join(format!("{member}.control")), + b"default_version = '1.0'\n", + ); + write_file( + &root + .join("share/postgresql/extension") + .join(format!("{member}--1.0.sql")), + b"select 1;\n", + ); + } + for module in Extension::POSTGIS.native_module_files() { + write_file(&root.join("lib/postgresql").join(&module), b"server module"); + write_file(&root.join("lib/modules").join(&module), b"embedded module"); + } + for relative in data_files(Extension::POSTGIS) { + write_file( + &root.join("share/postgresql").join(relative), + b"runtime data", + ); + } + } + fn write_minimal_cache_dir(cache_dir: &Path, key: &str) { write_file(&cache_dir.join(".complete"), b"ok\n"); write_file( diff --git a/src/native/rust-bindings/src/liboliphaunt/root/runtime/install.rs b/src/native/rust-bindings/src/liboliphaunt/root/runtime/install.rs index 6bc185c5b..15eff6de7 100644 --- a/src/native/rust-bindings/src/liboliphaunt/root/runtime/install.rs +++ b/src/native/rust-bindings/src/liboliphaunt/root/runtime/install.rs @@ -3,9 +3,8 @@ use std::path::{Path, PathBuf}; use super::super::NativeRuntimeProfile; use super::super::extensions::{ - copy_embedded_module, copy_extension_data_files, copy_extension_sql_files, - copy_named_extension_sql_files, core_share_file, embedded_core_module_files, - packaged_extension_module_files, + copy_embedded_module, copy_extension_sql_files, copy_named_extension_sql_files, + core_share_file, embedded_core_module_files, packaged_extension_module_files, }; use super::super::files::{ copy_directory_filtered, copy_directory_tree, copy_file_preserving_permissions, @@ -14,7 +13,7 @@ use super::super::files::{ use super::super::{NATIVE_RUNTIME_TOOLS, existing_native_tool_path, native_tool_path}; use super::extension_artifact_root_for; use crate::error::{Error, Result}; -use crate::extension::Extension; +use crate::extension::{Extension, extension_data_files}; #[cfg(test)] pub(super) fn install_cached_runtime( @@ -160,7 +159,17 @@ fn install_native_share_tree( extension_artifact_root_for(install_dir, extension_artifact_dirs, *extension); let extension_share = extension_root.join("share/postgresql"); copy_extension_sql_files(&extension_share, &target_share, *extension)?; - copy_extension_data_files(&extension_share, &target_share, *extension)?; + for member in extension.sql_members() { + if *member != extension.sql_name() { + copy_named_extension_sql_files(&extension_share, &target_share, member, true)?; + } + } + for relative in extension_data_files(*extension) { + copy_file_preserving_permissions( + &extension_share.join(relative), + &target_share.join(relative), + )?; + } } Ok(()) } @@ -502,6 +511,203 @@ mod tests { assert!(!runtime_dir.join("share/icu").exists()); } + #[test] + fn separate_postgis_artifacts_supply_every_module_profile_and_sql_member() { + let temp = TempTree::new("separate-postgis-artifact"); + let install_dir = temp.path().join("install"); + let artifact_dir = temp.path().join("extension/postgis"); + let contrib_dir = temp.path().join("extension/contrib"); + let embedded_modules = temp.path().join("embedded-modules"); + write_minimal_install(&install_dir); + write_extension_assets(&artifact_dir, Extension::POSTGIS); + write_extension_assets(&contrib_dir, Extension::FUZZYSTRMATCH); + write_extension_assets(&artifact_dir, Extension::HSTORE); + for extension in [Extension::POSTGIS, Extension::FUZZYSTRMATCH] { + let root = if extension == Extension::POSTGIS { + &artifact_dir + } else { + &contrib_dir + }; + for module in extension.native_module_files() { + write_file( + &root.join("lib/modules").join(&module), + b"selected embedded module", + ); + write_file( + &install_dir.join("lib/postgresql").join(&module), + b"unselected provider module", + ); + write_file( + &embedded_modules.join(&module), + b"unselected provider embedded module", + ); + } + } + for module in embedded_core_module_files() { + write_file(&embedded_modules.join(module), b"core embedded module"); + } + let artifacts = [artifact_dir.clone(), contrib_dir]; + let extensions = resolve_extensions(&[Extension::POSTGIS]).unwrap(); + let missing_dependency = install_cached_runtime( + NativeRuntimeProfile::PostgresServer, + &install_dir, + None, + std::slice::from_ref(&artifact_dir), + &temp.path().join("missing-dependency"), + &extensions, + ) + .unwrap_err(); + assert!(missing_dependency.to_string().contains("fuzzystrmatch")); + for profile in [ + NativeRuntimeProfile::PostgresServer, + NativeRuntimeProfile::OliphauntEmbedded, + ] { + let runtime_dir = temp.path().join(profile.cache_id()); + install_cached_runtime( + profile, + &install_dir, + Some(&embedded_modules), + &artifacts, + &runtime_dir, + &extensions, + ) + .unwrap(); + for module in Extension::POSTGIS.native_module_files() { + let expected: &[u8] = if profile.needs_embedded_modules() { + b"selected embedded module" + } else { + b"postgis module\n" + }; + assert_eq!( + fs::read(runtime_dir.join("lib/postgresql").join(module)).unwrap(), + expected + ); + } + for member in Extension::POSTGIS.sql_members() { + assert!( + runtime_dir + .join("share/postgresql/extension") + .join(format!("{member}.control")) + .is_file() + ); + assert!( + runtime_dir + .join("share/postgresql/extension") + .join(format!("{member}--1.0.sql")) + .is_file() + ); + } + assert!( + !runtime_dir + .join("lib/postgresql") + .join(Extension::HSTORE.native_module_file().unwrap()) + .exists() + ); + } + + let companion = format!("postgis_topology-3{}", std::env::consts::DLL_SUFFIX); + for (profile, directory) in [ + (NativeRuntimeProfile::PostgresServer, "lib/postgresql"), + (NativeRuntimeProfile::OliphauntEmbedded, "lib/modules"), + ] { + fs::remove_file(artifact_dir.join(directory).join(&companion)).unwrap(); + let error = install_cached_runtime( + profile, + &install_dir, + Some(&embedded_modules), + &artifacts, + &temp.path().join("missing-module"), + &extensions, + ) + .unwrap_err(); + assert!(error.to_string().contains(&companion), "{error}"); + write_file( + &artifact_dir.join(directory).join(&companion), + b"restored selected companion", + ); + } + fs::remove_file( + artifact_dir.join("share/postgresql/extension/postgis_tiger_geocoder.control"), + ) + .unwrap(); + let error = install_cached_runtime( + NativeRuntimeProfile::PostgresServer, + &install_dir, + None, + &artifacts, + &temp.path().join("missing-control"), + &extensions, + ) + .unwrap_err(); + assert!( + error.to_string().contains("postgis_tiger_geocoder"), + "{error}" + ); + } + + #[test] + fn all_in_one_provider_does_not_install_unselected_companion_modules_or_data() { + let temp = TempTree::new("unselected-postgis-provider"); + let install_dir = temp.path().join("install"); + let runtime_dir = temp.path().join("runtime"); + write_minimal_install(&install_dir); + write_extension_assets(&install_dir, Extension::POSTGIS); + install_cached_runtime( + NativeRuntimeProfile::PostgresServer, + &install_dir, + None, + &[], + &runtime_dir, + &[], + ) + .unwrap(); + for module in Extension::POSTGIS.native_module_files() { + assert!(!runtime_dir.join("lib/postgresql").join(module).exists()); + } + for relative in crate::extension::extension_data_files(Extension::POSTGIS) { + assert!(!runtime_dir.join("share/postgresql").join(relative).exists()); + } + } + + #[test] + fn separate_core_only_postgis_artifact_is_incomplete() { + let temp = TempTree::new("incomplete-postgis-artifact"); + let install_dir = temp.path().join("install"); + let artifact_dir = temp.path().join("extension/postgis"); + let contrib_dir = temp.path().join("extension/contrib"); + write_minimal_install(&install_dir); + write_extension_assets(&contrib_dir, Extension::FUZZYSTRMATCH); + write_file( + &artifact_dir.join("share/postgresql/extension/postgis.control"), + b"default_version = '3.6.4'\n", + ); + write_file( + &artifact_dir.join("share/postgresql/extension/postgis--3.6.4.sql"), + b"select 'core-only postgis';\n", + ); + write_file( + &artifact_dir + .join("lib/postgresql") + .join(Extension::POSTGIS.native_module_file().unwrap()), + b"primary module", + ); + let extensions = resolve_extensions(&[Extension::POSTGIS]).unwrap(); + let error = install_cached_runtime( + NativeRuntimeProfile::PostgresServer, + &install_dir, + None, + &[artifact_dir, contrib_dir], + &temp.path().join("runtime"), + &extensions, + ) + .unwrap_err(); + assert!(error.to_string().contains("postgis_raster"), "{error}"); + assert!( + error.to_string().contains("missing control file"), + "{error}" + ); + } + struct TempTree { path: PathBuf, } @@ -555,24 +761,32 @@ mod tests { } fn write_extension_assets(install_dir: &Path, extension: Extension) { - write_file( - &install_dir - .join("share/postgresql/extension") - .join(format!("{}.control", extension.sql_name())), - format!("comment = '{}'\n", extension.sql_name()).as_bytes(), - ); - write_file( - &install_dir - .join("share/postgresql/extension") - .join(format!("{}--1.0.sql", extension.sql_name())), - format!("select '{} install';\n", extension.sql_name()).as_bytes(), - ); - if let Some(module) = extension.native_module_file() { + for member in extension.sql_members() { + write_file( + &install_dir + .join("share/postgresql/extension") + .join(format!("{member}.control")), + format!("comment = '{member}'\n").as_bytes(), + ); + write_file( + &install_dir + .join("share/postgresql/extension") + .join(format!("{member}--1.0.sql")), + format!("select '{member} install';\n").as_bytes(), + ); + } + for module in extension.native_module_files() { write_file( &install_dir.join("lib/postgresql").join(module), format!("{} module\n", extension.sql_name()).as_bytes(), ); } + for relative in crate::extension::extension_data_files(extension) { + write_file( + &install_dir.join("share/postgresql").join(relative), + b"extension data", + ); + } } fn write_file(path: &Path, contents: &[u8]) { @@ -639,35 +853,36 @@ fn install_native_library_tree( } } for extension in extensions { - let Some(module) = extension.native_module_file() else { - continue; - }; let extension_root = extension_artifact_root_for(install_dir, extension_artifact_dirs, *extension); let extension_lib = extension_root.join("lib/postgresql"); - match profile { - NativeRuntimeProfile::OliphauntEmbedded => { - let embedded_extension_lib = extension_root.join("lib/modules"); - if embedded_extension_lib.join(&module).is_file() { - copy_file_preserving_permissions( - &embedded_extension_lib.join(&module), - &target_lib.join(&module), - )?; - } else { - let embedded_modules = embedded_modules.ok_or_else(|| { + for module in extension.native_module_files() { + match profile { + NativeRuntimeProfile::OliphauntEmbedded => { + let embedded_extension_lib = extension_root.join("lib/modules"); + if extension_root != install_dir + || embedded_extension_lib.join(&module).is_file() + { + copy_file_preserving_permissions( + &embedded_extension_lib.join(&module), + &target_lib.join(&module), + )?; + } else { + let embedded_modules = embedded_modules.ok_or_else(|| { Error::Engine( "native liboliphaunt runtime requires embedded PostgreSQL extension modules" .to_owned(), ) })?; - copy_embedded_module(embedded_modules, &target_lib, &module)?; + copy_embedded_module(embedded_modules, &target_lib, &module)?; + } + } + NativeRuntimeProfile::PostgresServer => { + copy_file_preserving_permissions( + &extension_lib.join(&module), + &target_lib.join(&module), + )?; } - } - NativeRuntimeProfile::PostgresServer => { - copy_file_preserving_permissions( - &extension_lib.join(&module), - &target_lib.join(&module), - )?; } } } diff --git a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/OliphauntExtensionCatalog.java b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/OliphauntExtensionCatalog.java index 6dd1f2e1e..cdb4de417 100644 --- a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/OliphauntExtensionCatalog.java +++ b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/OliphauntExtensionCatalog.java @@ -202,6 +202,9 @@ static Map load(InputStream stream) { "bundled Oliphaunt extension catalog has invalid Maven coordinates for " + sqlName); } List dependencies = portableList(properties.getProperty(prefix + "dependencies", "")); + if (!properties.containsKey(prefix + "sqlMembers")) { + throw new GradleException("bundled Oliphaunt extension catalog is missing " + prefix + "sqlMembers"); + } entries.put( sqlName, new Entry( @@ -211,7 +214,8 @@ static Map load(InputStream stream) { mavenGroup, mavenArtifact, runtimeBound, - dependencies)); + dependencies, + portableList(properties.getProperty(prefix + "sqlMembers")))); } for (Entry entry : entries.values()) { for (String dependency : entry.dependencies()) { @@ -292,7 +296,8 @@ record Entry( String mavenGroup, String mavenArtifact, boolean runtimeBound, - List dependencies) { + List dependencies, + List sqlMembers) { String mavenCoordinate() { return mavenGroup + ":" + mavenArtifact; } diff --git a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/ResolveOliphauntAndroidAssetsTask.java b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/ResolveOliphauntAndroidAssetsTask.java index bae95737d..5c5eddd0d 100644 --- a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/ResolveOliphauntAndroidAssetsTask.java +++ b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/ResolveOliphauntAndroidAssetsTask.java @@ -109,6 +109,8 @@ public abstract class ResolveOliphauntAndroidAssetsTask extends DefaultTask { "staticSymbolAliases", "licenseFiles", "licenseProfile", + "sqlMembers", + "nativeModuleStems", "files"); private static final List TARGET_INVARIANT_EXTENSION_PROPERTY_KEYS = List.of( @@ -130,6 +132,8 @@ public abstract class ResolveOliphauntAndroidAssetsTask extends DefaultTask { "staticSymbolAliases", "licenseFiles", "licenseProfile", + "sqlMembers", + "nativeModuleStems", "files"); private final ArchiveOperations archiveOperations; private final FileSystemOperations fileSystemOperations; @@ -1440,6 +1444,7 @@ private static void validateExtensionManifestSemantics( String moduleFile = manifest.getProperty("nativeModuleFile"); String mobilePrebuilt = manifest.getProperty("mobilePrebuilt"); String symbolPrefix = manifest.getProperty("staticSymbolPrefix"); + extensionSqlMembers(manifest, assetName); List staticArchiveValues = strictManifestCsv(manifest, "mobileStaticArchives", assetName); List dependencyArchiveValues = @@ -1768,6 +1773,7 @@ private static void validateExtensionArtifactInventory( } String extensionDirectory = "files/share/postgresql/extension/"; + List sqlMembers = extensionSqlMembers(manifest, assetName); String controlFile = extensionDirectory + sqlName + ".control"; boolean hasControl = actualFiles.contains(controlFile); boolean hasInstallSql = false; @@ -1779,7 +1785,7 @@ private static void validateExtensionArtifactInventory( continue; } if (isOwnedExtensionSqlOrControl( - actual, sqlName, extensionSqlFileNames, extensionSqlFilePrefixes)) { + actual, sqlMembers, extensionSqlFileNames, extensionSqlFilePrefixes)) { expectedFiles.add(actual); hasInstallSql |= isInstallSql(actual, sqlName); hasMainExtensionCreationFile |= isMainExtensionCreationFile(actual, sqlName); @@ -1803,6 +1809,13 @@ private static void validateExtensionArtifactInventory( + " declares createsExtension=no but contains extension creation files for " + sqlName); } + for (String member : sqlMembers) { + if (!actualFiles.contains(extensionDirectory + member + ".control") + || actualFiles.stream().noneMatch(file -> file.startsWith(extensionDirectory) && isInstallSql(file, member))) { + throw new GradleException("Oliphaunt extension artifact " + assetName + + " must contain " + member + ".control and a canonical install SQL file for " + member); + } + } TreeSet missing = new TreeSet<>(declaredFiles); missing.removeAll(actualFiles); if (!missing.isEmpty() || !unexpected.isEmpty()) { @@ -1943,9 +1956,25 @@ private static String canonicalDeclaredPath( return normalized; } + private static List extensionSqlMembers(Properties manifest, String source) { + String sqlName = manifest.getProperty("sqlName"); + List members = strictManifestCsv(manifest, "sqlMembers", source); + List modules = strictManifestCsv(manifest, "nativeModuleStems", source); + requireSortedManifestList(members, source, "sqlMembers"); + requireSortedManifestList(modules, source, "nativeModuleStems"); + if (!OliphauntExtensionCatalog.require(sqlName).sqlMembers().equals(members)) { + throw new GradleException(source + " sqlMembers must identify the owned SQL closure"); + } + String stem = manifest.getProperty("nativeModuleStem", ""); + if (!modules.equals(stem.isEmpty() ? List.of() : List.of(stem))) { + throw new GradleException(source + " nativeModuleStems must contain only the merged primary mobile module"); + } + return members; + } + private static boolean isOwnedExtensionSqlOrControl( String path, - String sqlName, + List sqlMembers, List extensionSqlFileNames, List extensionSqlFilePrefixes) { String prefix = "files/share/postgresql/extension/"; @@ -1956,14 +1985,13 @@ private static boolean isOwnedExtensionSqlOrControl( if (name.indexOf('/') >= 0) { return false; } - if (name.equals(sqlName + ".control")) { + if (sqlMembers.stream().anyMatch(member -> name.equals(member + ".control"))) { return true; } if (!name.endsWith(".sql")) { return false; } - return name.equals(sqlName + ".sql") - || name.startsWith(sqlName + "--") + return sqlMembers.stream().anyMatch(member -> name.equals(member + ".sql") || name.startsWith(member + "--")) || extensionSqlFileNames.contains(name) || extensionSqlFilePrefixes.stream().anyMatch(name::startsWith); } @@ -2187,7 +2215,8 @@ private List mergeExtensionRuntimeArtifacts( : dashToNull(row.get("static_symbol_aliases")), "selected " + sqlName + " Android artifact"), splitCsv(dashToNull(row.get("archive_targets"))), - splitCsv(dashToNull(row.get("dependency_archives"))))); + splitCsv(dashToNull(row.get("dependency_archives"))), + extensionSqlMembers(artifactManifest, artifact.getName()))); } validateSelectedExtensionRuntimeFiles(runtimeFiles, artifacts); List nativeArtifacts = @@ -2268,12 +2297,14 @@ private static void validateSelectedExtensionRuntimeFiles(File runtimeFiles, Lis if (!artifact.createsExtension) { continue; } - File control = new File(extensionDir, artifact.sqlName + ".control"); - if (!control.isFile()) { - throw new GradleException( - "selected extension " + artifact.sqlName + " is missing packaged control file " + control); + for (String member : artifact.sqlMembers) { + File control = new File(extensionDir, member + ".control"); + if (!control.isFile()) { + throw new GradleException( + "selected extension " + artifact.sqlName + " is missing packaged control file " + control); + } + extensionSqlFiles(runtimeFiles, member, List.of(member)); } - extensionSqlFiles(runtimeFiles, artifact.sqlName); } } @@ -2767,7 +2798,7 @@ private static List staticRegistryModules( prefix, artifact.createsExtension ? collectExtensionSqlSymbols( - runtimeFiles, artifact.sqlName, artifact.nativeModuleStem) + runtimeFiles, artifact.sqlName, artifact.nativeModuleStem, artifact.sqlMembers) : List.of(), Collections.unmodifiableMap(aliases))); } @@ -2890,7 +2921,8 @@ static String staticRegistrySourceForContractTest( symbolPrefix, parseStaticSymbolAliases(symbolAliases, "contract-test " + sqlName), List.of(), - List.of()); + List.of(), + OliphauntExtensionCatalog.require(sqlName).sqlMembers()); return staticRegistrySourceText(staticRegistryModules(runtimeFiles, List.of(artifact))); } @@ -2913,7 +2945,8 @@ static String staticRegistryManifestForContractTest( symbolPrefix, parseStaticSymbolAliases(symbolAliases, "contract-test " + sqlName), archiveTargets, - dependencyArchives); + dependencyArchives, + OliphauntExtensionCatalog.require(sqlName).sqlMembers()); List modules = staticRegistryModules(runtimeFiles, List.of(artifact)); return staticRegistryManifestText(List.of(artifact), modules); @@ -2941,8 +2974,8 @@ private void copyMobileStaticTree(File source, File target) { } private static List collectExtensionSqlSymbols( - File runtimeFiles, String sqlName, String moduleStem) { - List sqlFiles = extensionSqlFiles(runtimeFiles, sqlName); + File runtimeFiles, String sqlName, String moduleStem, List sqlMembers) { + List sqlFiles = extensionSqlFiles(runtimeFiles, sqlName, sqlMembers); TreeSet symbols = new TreeSet<>(); for (File file : sqlFiles) { try { @@ -2956,11 +2989,12 @@ private static List collectExtensionSqlSymbols( return new ArrayList<>(symbols); } - private static List extensionSqlFiles(File runtimeFiles, String sqlName) { + private static List extensionSqlFiles(File runtimeFiles, String sqlName, List sqlMembers) { File extensionDir = new File(runtimeFiles, "share/postgresql/extension"); File[] sqlFiles = extensionDir.listFiles( - file -> file.isFile() && file.getName().startsWith(sqlName + "--") && file.getName().endsWith(".sql")); + file -> file.isFile() && file.getName().endsWith(".sql") + && sqlMembers.stream().anyMatch(member -> file.getName().startsWith(member + "--"))); if (sqlFiles == null || sqlFiles.length == 0) { throw new GradleException("selected extension " + sqlName + " has no packaged SQL files in " + extensionDir); } @@ -3790,7 +3824,8 @@ private record ExtensionRuntimeArtifact( String staticSymbolPrefix, List staticSymbolAliases, List archiveTargets, - List dependencyArchives) {} + List dependencyArchives, + List sqlMembers) {} private record StaticRegistryModule( String moduleStem, diff --git a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extension-legal-catalog.json b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extension-legal-catalog.json index 38a7cedd5..d334f12b6 100644 --- a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extension-legal-catalog.json +++ b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extension-legal-catalog.json @@ -1,6 +1,6 @@ { "schema": "oliphaunt-android-extension-legal-catalog-v1", - "sourceCatalogSha256": "26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a", + "sourceCatalogSha256": "dbe1ab284693ef923cb12e35e1c75bcc7fc1ed42af1fecf9fe9fee0aaf0ef8d0", "contracts": [ { "scope": "aggregate", diff --git a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extensions.properties b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extensions.properties index 9d3c2a03a..391ccf6fc 100644 --- a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extensions.properties +++ b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/resources/dev/oliphaunt/android/extensions.properties @@ -1,238 +1,277 @@ # This file is generated by src/extensions/tools/check-extension-model.sh. # Do not edit by hand. schema=oliphaunt-android-extension-catalog-v2 -catalogSha256=26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a +catalogSha256=dbe1ab284693ef923cb12e35e1c75bcc7fc1ed42af1fecf9fe9fee0aaf0ef8d0 extension.amcheck.artifactProduct=oliphaunt-extension-contrib-pg18 extension.amcheck.releaseProduct=liboliphaunt-native extension.amcheck.mavenGroup=dev.oliphaunt.extensions extension.amcheck.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.amcheck.runtimeBound=true extension.amcheck.dependencies= +extension.amcheck.sqlMembers=amcheck extension.auto_explain.artifactProduct=oliphaunt-extension-contrib-pg18 extension.auto_explain.releaseProduct=liboliphaunt-native extension.auto_explain.mavenGroup=dev.oliphaunt.extensions extension.auto_explain.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.auto_explain.runtimeBound=true extension.auto_explain.dependencies= +extension.auto_explain.sqlMembers= extension.bloom.artifactProduct=oliphaunt-extension-contrib-pg18 extension.bloom.releaseProduct=liboliphaunt-native extension.bloom.mavenGroup=dev.oliphaunt.extensions extension.bloom.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.bloom.runtimeBound=true extension.bloom.dependencies= +extension.bloom.sqlMembers=bloom extension.btree_gin.artifactProduct=oliphaunt-extension-contrib-pg18 extension.btree_gin.releaseProduct=liboliphaunt-native extension.btree_gin.mavenGroup=dev.oliphaunt.extensions extension.btree_gin.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.btree_gin.runtimeBound=true extension.btree_gin.dependencies= +extension.btree_gin.sqlMembers=btree_gin extension.btree_gist.artifactProduct=oliphaunt-extension-contrib-pg18 extension.btree_gist.releaseProduct=liboliphaunt-native extension.btree_gist.mavenGroup=dev.oliphaunt.extensions extension.btree_gist.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.btree_gist.runtimeBound=true extension.btree_gist.dependencies= +extension.btree_gist.sqlMembers=btree_gist extension.citext.artifactProduct=oliphaunt-extension-contrib-pg18 extension.citext.releaseProduct=liboliphaunt-native extension.citext.mavenGroup=dev.oliphaunt.extensions extension.citext.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.citext.runtimeBound=true extension.citext.dependencies= +extension.citext.sqlMembers=citext extension.cube.artifactProduct=oliphaunt-extension-contrib-pg18 extension.cube.releaseProduct=liboliphaunt-native extension.cube.mavenGroup=dev.oliphaunt.extensions extension.cube.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.cube.runtimeBound=true extension.cube.dependencies= +extension.cube.sqlMembers=cube extension.dict_int.artifactProduct=oliphaunt-extension-contrib-pg18 extension.dict_int.releaseProduct=liboliphaunt-native extension.dict_int.mavenGroup=dev.oliphaunt.extensions extension.dict_int.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.dict_int.runtimeBound=true extension.dict_int.dependencies= +extension.dict_int.sqlMembers=dict_int extension.dict_xsyn.artifactProduct=oliphaunt-extension-contrib-pg18 extension.dict_xsyn.releaseProduct=liboliphaunt-native extension.dict_xsyn.mavenGroup=dev.oliphaunt.extensions extension.dict_xsyn.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.dict_xsyn.runtimeBound=true extension.dict_xsyn.dependencies= +extension.dict_xsyn.sqlMembers=dict_xsyn extension.earthdistance.artifactProduct=oliphaunt-extension-contrib-pg18 extension.earthdistance.releaseProduct=liboliphaunt-native extension.earthdistance.mavenGroup=dev.oliphaunt.extensions extension.earthdistance.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.earthdistance.runtimeBound=true extension.earthdistance.dependencies=cube +extension.earthdistance.sqlMembers=earthdistance extension.file_fdw.artifactProduct=oliphaunt-extension-contrib-pg18 extension.file_fdw.releaseProduct=liboliphaunt-native extension.file_fdw.mavenGroup=dev.oliphaunt.extensions extension.file_fdw.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.file_fdw.runtimeBound=true extension.file_fdw.dependencies= +extension.file_fdw.sqlMembers=file_fdw extension.fuzzystrmatch.artifactProduct=oliphaunt-extension-contrib-pg18 extension.fuzzystrmatch.releaseProduct=liboliphaunt-native extension.fuzzystrmatch.mavenGroup=dev.oliphaunt.extensions extension.fuzzystrmatch.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.fuzzystrmatch.runtimeBound=true extension.fuzzystrmatch.dependencies= +extension.fuzzystrmatch.sqlMembers=fuzzystrmatch extension.hstore.artifactProduct=oliphaunt-extension-contrib-pg18 extension.hstore.releaseProduct=liboliphaunt-native extension.hstore.mavenGroup=dev.oliphaunt.extensions extension.hstore.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.hstore.runtimeBound=true extension.hstore.dependencies= +extension.hstore.sqlMembers=hstore extension.intarray.artifactProduct=oliphaunt-extension-contrib-pg18 extension.intarray.releaseProduct=liboliphaunt-native extension.intarray.mavenGroup=dev.oliphaunt.extensions extension.intarray.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.intarray.runtimeBound=true extension.intarray.dependencies= +extension.intarray.sqlMembers=intarray extension.isn.artifactProduct=oliphaunt-extension-contrib-pg18 extension.isn.releaseProduct=liboliphaunt-native extension.isn.mavenGroup=dev.oliphaunt.extensions extension.isn.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.isn.runtimeBound=true extension.isn.dependencies= +extension.isn.sqlMembers=isn extension.lo.artifactProduct=oliphaunt-extension-contrib-pg18 extension.lo.releaseProduct=liboliphaunt-native extension.lo.mavenGroup=dev.oliphaunt.extensions extension.lo.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.lo.runtimeBound=true extension.lo.dependencies= +extension.lo.sqlMembers=lo extension.ltree.artifactProduct=oliphaunt-extension-contrib-pg18 extension.ltree.releaseProduct=liboliphaunt-native extension.ltree.mavenGroup=dev.oliphaunt.extensions extension.ltree.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.ltree.runtimeBound=true extension.ltree.dependencies= +extension.ltree.sqlMembers=ltree extension.pageinspect.artifactProduct=oliphaunt-extension-contrib-pg18 extension.pageinspect.releaseProduct=liboliphaunt-native extension.pageinspect.mavenGroup=dev.oliphaunt.extensions extension.pageinspect.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.pageinspect.runtimeBound=true extension.pageinspect.dependencies= +extension.pageinspect.sqlMembers=pageinspect extension.pg_buffercache.artifactProduct=oliphaunt-extension-contrib-pg18 extension.pg_buffercache.releaseProduct=liboliphaunt-native extension.pg_buffercache.mavenGroup=dev.oliphaunt.extensions extension.pg_buffercache.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.pg_buffercache.runtimeBound=true extension.pg_buffercache.dependencies= +extension.pg_buffercache.sqlMembers=pg_buffercache extension.pg_freespacemap.artifactProduct=oliphaunt-extension-contrib-pg18 extension.pg_freespacemap.releaseProduct=liboliphaunt-native extension.pg_freespacemap.mavenGroup=dev.oliphaunt.extensions extension.pg_freespacemap.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.pg_freespacemap.runtimeBound=true extension.pg_freespacemap.dependencies= +extension.pg_freespacemap.sqlMembers=pg_freespacemap extension.pg_hashids.artifactProduct=oliphaunt-extension-pg-hashids extension.pg_hashids.releaseProduct=oliphaunt-extension-pg-hashids extension.pg_hashids.mavenGroup=dev.oliphaunt.extensions extension.pg_hashids.mavenArtifact=oliphaunt-extension-pg-hashids extension.pg_hashids.runtimeBound=false extension.pg_hashids.dependencies= +extension.pg_hashids.sqlMembers=pg_hashids extension.pg_ivm.artifactProduct=oliphaunt-extension-pg-ivm extension.pg_ivm.releaseProduct=oliphaunt-extension-pg-ivm extension.pg_ivm.mavenGroup=dev.oliphaunt.extensions extension.pg_ivm.mavenArtifact=oliphaunt-extension-pg-ivm extension.pg_ivm.runtimeBound=false extension.pg_ivm.dependencies= +extension.pg_ivm.sqlMembers=pg_ivm extension.pg_surgery.artifactProduct=oliphaunt-extension-contrib-pg18 extension.pg_surgery.releaseProduct=liboliphaunt-native extension.pg_surgery.mavenGroup=dev.oliphaunt.extensions extension.pg_surgery.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.pg_surgery.runtimeBound=true extension.pg_surgery.dependencies= +extension.pg_surgery.sqlMembers=pg_surgery extension.pg_textsearch.artifactProduct=oliphaunt-extension-pg-textsearch extension.pg_textsearch.releaseProduct=oliphaunt-extension-pg-textsearch extension.pg_textsearch.mavenGroup=dev.oliphaunt.extensions extension.pg_textsearch.mavenArtifact=oliphaunt-extension-pg-textsearch extension.pg_textsearch.runtimeBound=false extension.pg_textsearch.dependencies= +extension.pg_textsearch.sqlMembers=pg_textsearch extension.pg_trgm.artifactProduct=oliphaunt-extension-contrib-pg18 extension.pg_trgm.releaseProduct=liboliphaunt-native extension.pg_trgm.mavenGroup=dev.oliphaunt.extensions extension.pg_trgm.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.pg_trgm.runtimeBound=true extension.pg_trgm.dependencies= +extension.pg_trgm.sqlMembers=pg_trgm extension.pg_uuidv7.artifactProduct=oliphaunt-extension-pg-uuidv7 extension.pg_uuidv7.releaseProduct=oliphaunt-extension-pg-uuidv7 extension.pg_uuidv7.mavenGroup=dev.oliphaunt.extensions extension.pg_uuidv7.mavenArtifact=oliphaunt-extension-pg-uuidv7 extension.pg_uuidv7.runtimeBound=false extension.pg_uuidv7.dependencies= +extension.pg_uuidv7.sqlMembers=pg_uuidv7 extension.pg_visibility.artifactProduct=oliphaunt-extension-contrib-pg18 extension.pg_visibility.releaseProduct=liboliphaunt-native extension.pg_visibility.mavenGroup=dev.oliphaunt.extensions extension.pg_visibility.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.pg_visibility.runtimeBound=true extension.pg_visibility.dependencies= +extension.pg_visibility.sqlMembers=pg_visibility extension.pg_walinspect.artifactProduct=oliphaunt-extension-contrib-pg18 extension.pg_walinspect.releaseProduct=liboliphaunt-native extension.pg_walinspect.mavenGroup=dev.oliphaunt.extensions extension.pg_walinspect.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.pg_walinspect.runtimeBound=true extension.pg_walinspect.dependencies= +extension.pg_walinspect.sqlMembers=pg_walinspect extension.pgcrypto.artifactProduct=oliphaunt-extension-contrib-pg18 extension.pgcrypto.releaseProduct=liboliphaunt-native extension.pgcrypto.mavenGroup=dev.oliphaunt.extensions extension.pgcrypto.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.pgcrypto.runtimeBound=true extension.pgcrypto.dependencies= +extension.pgcrypto.sqlMembers=pgcrypto extension.pgtap.artifactProduct=oliphaunt-extension-pgtap extension.pgtap.releaseProduct=oliphaunt-extension-pgtap extension.pgtap.mavenGroup=dev.oliphaunt.extensions extension.pgtap.mavenArtifact=oliphaunt-extension-pgtap extension.pgtap.runtimeBound=false extension.pgtap.dependencies= +extension.pgtap.sqlMembers=pgtap extension.postgis.artifactProduct=oliphaunt-extension-postgis extension.postgis.releaseProduct=oliphaunt-extension-postgis extension.postgis.mavenGroup=dev.oliphaunt.extensions extension.postgis.mavenArtifact=oliphaunt-extension-postgis extension.postgis.runtimeBound=false -extension.postgis.dependencies= +extension.postgis.dependencies=fuzzystrmatch +extension.postgis.sqlMembers=postgis,postgis_raster,postgis_tiger_geocoder,postgis_topology extension.seg.artifactProduct=oliphaunt-extension-contrib-pg18 extension.seg.releaseProduct=liboliphaunt-native extension.seg.mavenGroup=dev.oliphaunt.extensions extension.seg.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.seg.runtimeBound=true extension.seg.dependencies= +extension.seg.sqlMembers=seg extension.tablefunc.artifactProduct=oliphaunt-extension-contrib-pg18 extension.tablefunc.releaseProduct=liboliphaunt-native extension.tablefunc.mavenGroup=dev.oliphaunt.extensions extension.tablefunc.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.tablefunc.runtimeBound=true extension.tablefunc.dependencies= +extension.tablefunc.sqlMembers=tablefunc extension.tcn.artifactProduct=oliphaunt-extension-contrib-pg18 extension.tcn.releaseProduct=liboliphaunt-native extension.tcn.mavenGroup=dev.oliphaunt.extensions extension.tcn.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.tcn.runtimeBound=true extension.tcn.dependencies= +extension.tcn.sqlMembers=tcn extension.tsm_system_rows.artifactProduct=oliphaunt-extension-contrib-pg18 extension.tsm_system_rows.releaseProduct=liboliphaunt-native extension.tsm_system_rows.mavenGroup=dev.oliphaunt.extensions extension.tsm_system_rows.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.tsm_system_rows.runtimeBound=true extension.tsm_system_rows.dependencies= +extension.tsm_system_rows.sqlMembers=tsm_system_rows extension.tsm_system_time.artifactProduct=oliphaunt-extension-contrib-pg18 extension.tsm_system_time.releaseProduct=liboliphaunt-native extension.tsm_system_time.mavenGroup=dev.oliphaunt.extensions extension.tsm_system_time.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.tsm_system_time.runtimeBound=true extension.tsm_system_time.dependencies= +extension.tsm_system_time.sqlMembers=tsm_system_time extension.unaccent.artifactProduct=oliphaunt-extension-contrib-pg18 extension.unaccent.releaseProduct=liboliphaunt-native extension.unaccent.mavenGroup=dev.oliphaunt.extensions extension.unaccent.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.unaccent.runtimeBound=true extension.unaccent.dependencies= +extension.unaccent.sqlMembers=unaccent extension.uuid-ossp.artifactProduct=oliphaunt-extension-contrib-pg18 extension.uuid-ossp.releaseProduct=liboliphaunt-native extension.uuid-ossp.mavenGroup=dev.oliphaunt.extensions extension.uuid-ossp.mavenArtifact=oliphaunt-extension-contrib-pg18 extension.uuid-ossp.runtimeBound=true extension.uuid-ossp.dependencies= +extension.uuid-ossp.sqlMembers=uuid-ossp extension.vector.artifactProduct=oliphaunt-extension-vector extension.vector.releaseProduct=oliphaunt-extension-vector extension.vector.mavenGroup=dev.oliphaunt.extensions extension.vector.mavenArtifact=oliphaunt-extension-vector extension.vector.runtimeBound=false extension.vector.dependencies= +extension.vector.sqlMembers=vector diff --git a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java index 0b6e3b128..9230c9267 100644 --- a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java +++ b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java @@ -784,7 +784,9 @@ private static void validatesPgcryptoAndPostgisLegalArtifacts() throws Exception Path pgcryptoRoot = Files.createDirectories(root.resolve("pgcrypto")); Properties pgcrypto = cubeArtifactManifest(); pgcrypto.setProperty("sqlName", "pgcrypto"); + pgcrypto.setProperty("sqlMembers", String.join(",", OliphauntExtensionCatalog.require("pgcrypto").sqlMembers())); pgcrypto.setProperty("nativeModuleStem", "pgcrypto"); + pgcrypto.setProperty("nativeModuleStems", "pgcrypto"); pgcrypto.setProperty("nativeModuleFile", "pgcrypto.so"); pgcrypto.setProperty("staticSymbolPrefix", "oliphaunt_static_pgcrypto"); pgcrypto.setProperty( @@ -830,7 +832,9 @@ private static void validatesPgcryptoAndPostgisLegalArtifacts() throws Exception Path postgisRoot = Files.createDirectories(root.resolve("postgis")); Properties postgis = cubeArtifactManifest(); postgis.setProperty("sqlName", "postgis"); + postgis.setProperty("sqlMembers", String.join(",", OliphauntExtensionCatalog.require("postgis").sqlMembers())); postgis.setProperty("nativeModuleStem", "postgis-3"); + postgis.setProperty("nativeModuleStems", "postgis-3"); postgis.setProperty("nativeModuleFile", "postgis-3.so"); postgis.setProperty("staticSymbolPrefix", "oliphaunt_static_postgis_3"); postgis.setProperty( @@ -852,14 +856,49 @@ private static void validatesPgcryptoAndPostgisLegalArtifacts() throws Exception "mobile-static/android-arm64-v8a/extensions/postgis-3/liboliphaunt_extension_postgis-3.a", "archive\n"); stageCanonicalLegalFiles(postgisRoot, "postgis", "android-arm64-v8a"); + List postgisMembers = OliphauntExtensionCatalog.require("postgis").sqlMembers(); + if (!postgisMembers.contains("postgis_topology") || !postgisMembers.contains("postgis_tiger_geocoder")) { + throw new AssertionError("generated PostGIS SQL closure must include topology and tiger"); + } + postgis.setProperty("sqlMembers", String.join(",", postgisMembers)); + postgis.setProperty("nativeModuleStems", "postgis-3"); + postgis.setProperty("dependencies", String.join(",", OliphauntExtensionCatalog.require("postgis").dependencies())); + for (String member : postgisMembers) { + if (member.equals("postgis")) continue; + writeInventoryFile(postgisRoot, "files/share/postgresql/extension/" + member + ".control", "default_version='3.6.3'\n"); + writeInventoryFile(postgisRoot, "files/share/postgresql/extension/" + member + "--3.6.3.sql", "SELECT 1;\n"); + } + writeInventoryFile(postgisRoot, "manifest.properties", artifactManifestText(postgis)); ResolveOliphauntAndroidAssetsTask.validateExtensionManifestForContractTest( - "oliphaunt-extension-postgis", - "postgis", - "android-arm64-v8a", - postgis, - "1.2.3"); - ResolveOliphauntAndroidAssetsTask.validateExtensionArtifactInventoryForContractTest( - postgisRoot.toFile(), postgis); + "oliphaunt-extension-postgis", "postgis", "android-arm64-v8a", postgis, "1.2.3"); + ResolveOliphauntAndroidAssetsTask.validateExtensionArtifactInventoryForContractTest(postgisRoot.toFile(), postgis); + for (String field : List.of("sqlMembers", "nativeModuleStems")) { + Properties incomplete = new Properties(); + incomplete.putAll(postgis); + incomplete.remove(field); + expectFailure(() -> ResolveOliphauntAndroidAssetsTask.validateExtensionManifestForContractTest( + "oliphaunt-extension-postgis", "postgis", "android-arm64-v8a", incomplete, "1.2.3"), "property fields must be exactly"); + } + Properties incompletePostgis = new Properties(); + incompletePostgis.putAll(postgis); + incompletePostgis.setProperty("sqlMembers", "postgis"); + expectFailure(() -> ResolveOliphauntAndroidAssetsTask.validateExtensionManifestForContractTest( + "oliphaunt-extension-postgis", "postgis", "android-arm64-v8a", incompletePostgis, "1.2.3"), "sqlMembers must identify the owned SQL closure"); + incompletePostgis.setProperty("sqlMembers", String.join(",", postgisMembers)); + incompletePostgis.setProperty("dependencies", ""); + expectFailure(() -> ResolveOliphauntAndroidAssetsTask.validateExtensionManifestForContractTest( + "oliphaunt-extension-postgis", "postgis", "android-arm64-v8a", incompletePostgis, "1.2.3"), "must declare dependencies=fuzzystrmatch"); + incompletePostgis.setProperty("dependencies", String.join(",", OliphauntExtensionCatalog.require("postgis").dependencies())); + incompletePostgis.setProperty("nativeModuleStems", "postgis-3,postgis_raster-3,postgis_topology-3"); + expectFailure(() -> ResolveOliphauntAndroidAssetsTask.validateExtensionManifestForContractTest( + "oliphaunt-extension-postgis", "postgis", "android-arm64-v8a", incompletePostgis, "1.2.3"), "nativeModuleStems must contain only the merged primary mobile module"); + Path topologyControl = postgisRoot.resolve("files/share/postgresql/extension/postgis_topology.control"); + Files.delete(topologyControl); + expectFailure(() -> ResolveOliphauntAndroidAssetsTask.validateExtensionArtifactInventoryForContractTest(postgisRoot.toFile(), postgis), "postgis_topology.control and a canonical install SQL file"); + Files.writeString(topologyControl, "default_version='3.6.3'\n"); + Path foreignControl = writeInventoryFile(postgisRoot, "files/share/postgresql/extension/postgis_topology_extra.control", "foreign\n"); + expectFailure(() -> ResolveOliphauntAndroidAssetsTask.validateExtensionArtifactInventoryForContractTest(postgisRoot.toFile(), postgis), "unexpected=[files/share/postgresql/extension/postgis_topology_extra.control]"); + Files.delete(foreignControl); long upstreamFiles; try (var files = Files.walk(postgisRoot.resolve("files/share/licenses"))) { upstreamFiles = files.filter(Files::isRegularFile).count(); @@ -1075,7 +1114,9 @@ private static void validatesCarrierBoundAncillaryExtensionSqlInventory() Path root = Files.createTempDirectory("oliphaunt-ancillary-sql-inventory-"); Properties manifest = cubeArtifactManifest(); manifest.setProperty("sqlName", "pgtap"); + manifest.setProperty("sqlMembers", String.join(",", OliphauntExtensionCatalog.require("pgtap").sqlMembers())); manifest.setProperty("nativeModuleStem", ""); + manifest.setProperty("nativeModuleStems", ""); manifest.setProperty("nativeModuleFile", ""); manifest.setProperty("mobilePrebuilt", "no"); manifest.setProperty("mobileStaticArchives", ""); @@ -1932,6 +1973,7 @@ private static byte[] singleMemberExtensionArtifact( throws Exception { Properties manifest = cubeArtifactManifest(); manifest.setProperty("sqlName", sqlName); + manifest.setProperty("sqlMembers", String.join(",", OliphauntExtensionCatalog.require(sqlName).sqlMembers())); manifest.setProperty("nativeTarget", target); applyCanonicalLegalManifest(manifest, sqlName, target); manifest.setProperty("extensionSqlFileNames", extensionSqlFileNames); @@ -1939,6 +1981,7 @@ private static byte[] singleMemberExtensionArtifact( List entries = new ArrayList<>(); if (nativeModuleStem == null) { manifest.setProperty("nativeModuleStem", ""); + manifest.setProperty("nativeModuleStems", ""); manifest.setProperty("nativeModuleFile", ""); manifest.setProperty("mobilePrebuilt", "no"); manifest.setProperty("mobileStaticArchives", ""); @@ -1947,6 +1990,7 @@ private static byte[] singleMemberExtensionArtifact( manifest.setProperty("staticSymbolAliases", ""); } else { manifest.setProperty("nativeModuleStem", nativeModuleStem); + manifest.setProperty("nativeModuleStems", nativeModuleStem); manifest.setProperty("nativeModuleFile", nativeModuleStem + ".so"); manifest.setProperty("staticSymbolPrefix", "oliphaunt_static_" + nativeModuleStem); manifest.setProperty( @@ -2032,8 +2076,10 @@ private static void validatesNoCreateExtensionInventoryAndStaticRegistry() throw try { Properties manifest = cubeArtifactManifest(); manifest.setProperty("sqlName", "auto_explain"); + manifest.setProperty("sqlMembers", String.join(",", OliphauntExtensionCatalog.require("auto_explain").sqlMembers())); manifest.setProperty("createsExtension", "no"); manifest.setProperty("nativeModuleStem", "auto_explain"); + manifest.setProperty("nativeModuleStems", "auto_explain"); manifest.setProperty("nativeModuleFile", "auto_explain.so"); manifest.setProperty("staticSymbolPrefix", "oliphaunt_static_auto_explain"); manifest.setProperty( @@ -2089,6 +2135,8 @@ private static void validatesStaticRegistrySymbolAliases() throws Exception { root, "share/postgresql/extension/postgis--3.6.3.sql", "CREATE FUNCTION public.difference() RETURNS integer AS 'MODULE_PATHNAME', 'difference' LANGUAGE C;\n"); + writeInventoryFile(root, "share/postgresql/extension/postgis_topology--3.6.3.sql", + "CREATE FUNCTION topology.fixture() RETURNS integer AS '$libdir/postgis-3', 'topology_fixture' LANGUAGE C;\n"); String source = ResolveOliphauntAndroidAssetsTask.staticRegistrySourceForContractTest( root.toFile(), @@ -2101,6 +2149,7 @@ private static void validatesStaticRegistrySymbolAliases() throws Exception { source, "extern void oliphaunt_static_postgis_3_difference(void);", "aliased SQL symbol declaration"); + requireContains(source, ".name = \"topology_fixture\"", "companion SQL symbols enter the merged PostGIS registry"); requireContains( source, ".name = \"difference\", .address = (void *)oliphaunt_static_postgis_3_difference", @@ -2777,8 +2826,10 @@ private static Properties extensionManifestFor( Properties result = new Properties(); result.putAll(template); result.setProperty("sqlName", sqlName); + result.setProperty("sqlMembers", String.join(",", OliphauntExtensionCatalog.require(sqlName).sqlMembers())); result.setProperty("dependencies", dependencies); result.setProperty("nativeModuleStem", sqlName); + result.setProperty("nativeModuleStems", sqlName); result.setProperty("nativeModuleFile", sqlName + ".so"); result.setProperty("staticSymbolPrefix", "oliphaunt_static_" + sqlName); result.setProperty( @@ -2918,8 +2969,10 @@ private static Properties cubeArtifactManifest() { manifest.setProperty("packageLayout", "oliphaunt-extension-artifact-v1"); manifest.setProperty("pgMajor", "18"); manifest.setProperty("sqlName", "cube"); + manifest.setProperty("sqlMembers", String.join(",", OliphauntExtensionCatalog.require("cube").sqlMembers())); manifest.setProperty("createsExtension", "yes"); manifest.setProperty("nativeModuleStem", "cube"); + manifest.setProperty("nativeModuleStems", "cube"); manifest.setProperty("nativeModuleFile", "cube.so"); manifest.setProperty("nativeTarget", "android-arm64-v8a"); manifest.setProperty("nativeRuntimeProduct", "liboliphaunt-native"); @@ -3299,6 +3352,7 @@ private static void rejectsMalformedGeneratedCatalog() { "extension.alpha.mavenGroup=dev.oliphaunt.extensions", "extension.alpha.mavenArtifact=oliphaunt-extension-alpha", "extension.alpha.runtimeBound=false", + "extension.alpha.sqlMembers=alpha", "extension.alpha.dependencies=missing") + "\n"; expectFailure( diff --git a/src/native/sdks/react-native/src/generated/extensions.ts b/src/native/sdks/react-native/src/generated/extensions.ts index 6ae9a6ab3..4570b8df3 100644 --- a/src/native/sdks/react-native/src/generated/extensions.ts +++ b/src/native/sdks/react-native/src/generated/extensions.ts @@ -15,9 +15,11 @@ export type GeneratedExtensionMetadata = { readonly runtimeBound: boolean; readonly createsExtension: boolean; readonly nativeModuleStem: string | null; + readonly nativeModuleStems: readonly string[]; + readonly sqlMembers: readonly string[]; + readonly iosStaticDependencies: readonly string[]; readonly dependencies: readonly string[]; readonly selectedExtensionDependencies: readonly string[]; - readonly iosStaticDependencies: readonly string[]; readonly sharedPreloadLibraries: readonly string[]; readonly dataFiles: readonly string[]; readonly runtimeShareDataFiles: readonly string[]; @@ -26,7 +28,7 @@ export type GeneratedExtensionMetadata = { readonly sourceKind: string; }; -export const GENERATED_EXTENSION_METADATA_SHA256 = "26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a" as const; +export const GENERATED_EXTENSION_METADATA_SHA256 = "dbe1ab284693ef923cb12e35e1c75bcc7fc1ed42af1fecf9fe9fee0aaf0ef8d0" as const; export const GENERATED_EXTENSION_METADATA = [ { @@ -43,6 +45,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "amcheck", + "nativeModuleStems": [ + "amcheck" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -51,6 +56,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "amcheck" + ], "sqlName": "amcheck" }, { @@ -67,6 +75,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "auto_explain", + "nativeModuleStems": [ + "auto_explain" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -75,6 +86,7 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [], "sqlName": "auto_explain" }, { @@ -91,6 +103,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "bloom", + "nativeModuleStems": [ + "bloom" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -99,6 +114,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "bloom" + ], "sqlName": "bloom" }, { @@ -115,6 +133,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "btree_gin", + "nativeModuleStems": [ + "btree_gin" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -123,6 +144,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "btree_gin" + ], "sqlName": "btree_gin" }, { @@ -139,6 +163,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "btree_gist", + "nativeModuleStems": [ + "btree_gist" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -147,6 +174,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "btree_gist" + ], "sqlName": "btree_gist" }, { @@ -163,6 +193,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "citext", + "nativeModuleStems": [ + "citext" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -171,6 +204,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "citext" + ], "sqlName": "citext" }, { @@ -187,6 +223,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "cube", + "nativeModuleStems": [ + "cube" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -195,6 +234,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "cube" + ], "sqlName": "cube" }, { @@ -211,6 +253,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "dict_int", + "nativeModuleStems": [ + "dict_int" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -219,6 +264,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "dict_int" + ], "sqlName": "dict_int" }, { @@ -237,6 +285,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "dict_xsyn", + "nativeModuleStems": [ + "dict_xsyn" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -247,6 +298,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "dict_xsyn" + ], "sqlName": "dict_xsyn" }, { @@ -265,6 +319,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "earthdistance", + "nativeModuleStems": [ + "earthdistance" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -275,6 +332,9 @@ export const GENERATED_EXTENSION_METADATA = [ ], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "earthdistance" + ], "sqlName": "earthdistance" }, { @@ -291,6 +351,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "file_fdw", + "nativeModuleStems": [ + "file_fdw" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -299,6 +362,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "file_fdw" + ], "sqlName": "file_fdw" }, { @@ -315,6 +381,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "fuzzystrmatch", + "nativeModuleStems": [ + "fuzzystrmatch" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -323,6 +392,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "fuzzystrmatch" + ], "sqlName": "fuzzystrmatch" }, { @@ -339,6 +411,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "hstore", + "nativeModuleStems": [ + "hstore" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -347,6 +422,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "hstore" + ], "sqlName": "hstore" }, { @@ -363,6 +441,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "_int", + "nativeModuleStems": [ + "_int" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -371,6 +452,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "intarray" + ], "sqlName": "intarray" }, { @@ -387,6 +471,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "isn", + "nativeModuleStems": [ + "isn" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -395,6 +482,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "isn" + ], "sqlName": "isn" }, { @@ -411,6 +501,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "lo", + "nativeModuleStems": [ + "lo" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -419,6 +512,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "lo" + ], "sqlName": "lo" }, { @@ -435,6 +531,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "ltree", + "nativeModuleStems": [ + "ltree" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -443,6 +542,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "ltree" + ], "sqlName": "ltree" }, { @@ -459,6 +561,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pageinspect", + "nativeModuleStems": [ + "pageinspect" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -467,6 +572,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pageinspect" + ], "sqlName": "pageinspect" }, { @@ -483,6 +591,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_buffercache", + "nativeModuleStems": [ + "pg_buffercache" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -491,6 +602,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_buffercache" + ], "sqlName": "pg_buffercache" }, { @@ -507,6 +621,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_freespacemap", + "nativeModuleStems": [ + "pg_freespacemap" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -515,6 +632,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_freespacemap" + ], "sqlName": "pg_freespacemap" }, { @@ -531,6 +651,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-pg-hashids", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_hashids", + "nativeModuleStems": [ + "pg_hashids" + ], "npmPackage": "@oliphaunt/extension-pg-hashids", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-pg-hashids", @@ -539,6 +662,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "pg_hashids" + ], "sqlName": "pg_hashids" }, { @@ -555,6 +681,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-pg-ivm", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_ivm", + "nativeModuleStems": [ + "pg_ivm" + ], "npmPackage": "@oliphaunt/extension-pg-ivm", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-pg-ivm", @@ -563,6 +692,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "pg_ivm" + ], "sqlName": "pg_ivm" }, { @@ -579,6 +711,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_surgery", + "nativeModuleStems": [ + "pg_surgery" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -587,6 +722,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_surgery" + ], "sqlName": "pg_surgery" }, { @@ -603,6 +741,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-pg-textsearch", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_textsearch", + "nativeModuleStems": [ + "pg_textsearch" + ], "npmPackage": "@oliphaunt/extension-pg-textsearch", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-pg-textsearch", @@ -613,6 +754,9 @@ export const GENERATED_EXTENSION_METADATA = [ "pg_textsearch" ], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "pg_textsearch" + ], "sqlName": "pg_textsearch" }, { @@ -629,6 +773,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_trgm", + "nativeModuleStems": [ + "pg_trgm" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -637,6 +784,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_trgm" + ], "sqlName": "pg_trgm" }, { @@ -653,6 +803,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-pg-uuidv7", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_uuidv7", + "nativeModuleStems": [ + "pg_uuidv7" + ], "npmPackage": "@oliphaunt/extension-pg-uuidv7", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-pg-uuidv7", @@ -661,6 +814,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "pg_uuidv7" + ], "sqlName": "pg_uuidv7" }, { @@ -677,6 +833,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_visibility", + "nativeModuleStems": [ + "pg_visibility" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -685,6 +844,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_visibility" + ], "sqlName": "pg_visibility" }, { @@ -701,6 +863,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_walinspect", + "nativeModuleStems": [ + "pg_walinspect" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -709,6 +874,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_walinspect" + ], "sqlName": "pg_walinspect" }, { @@ -727,6 +895,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pgcrypto", + "nativeModuleStems": [ + "pgcrypto" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -735,6 +906,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pgcrypto" + ], "sqlName": "pgcrypto" }, { @@ -758,6 +932,7 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-pgtap", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": null, + "nativeModuleStems": [], "npmPackage": "@oliphaunt/extension-pgtap", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-pgtap", @@ -766,6 +941,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "pgtap" + ], "sqlName": "pgtap" }, { @@ -809,7 +987,9 @@ export const GENERATED_EXTENSION_METADATA = [ "share/postgresql/gdal/tms_NZTM2000.json", "share/postgresql/proj/proj.db" ], - "dependencies": [], + "dependencies": [ + "fuzzystrmatch" + ], "displayName": "PostGIS", "extensionSqlFileNames": [ "uninstall_postgis.sql" @@ -818,6 +998,8 @@ export const GENERATED_EXTENSION_METADATA = [ "postgis_comments", "postgis_proc_set_search_path", "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology", "rtpostgis" ], "id": "postgis", @@ -833,6 +1015,11 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-postgis", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "postgis-3", + "nativeModuleStems": [ + "postgis-3", + "postgis_raster-3", + "postgis_topology-3" + ], "npmPackage": "@oliphaunt/extension-postgis", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-postgis", @@ -874,9 +1061,17 @@ export const GENERATED_EXTENSION_METADATA = [ "gdal/tms_NZTM2000.json", "proj/proj.db" ], - "selectedExtensionDependencies": [], + "selectedExtensionDependencies": [ + "fuzzystrmatch" + ], "sharedPreloadLibraries": [], "sourceKind": "postgis", + "sqlMembers": [ + "postgis", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology" + ], "sqlName": "postgis" }, { @@ -893,6 +1088,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "seg", + "nativeModuleStems": [ + "seg" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -901,6 +1099,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "seg" + ], "sqlName": "seg" }, { @@ -917,6 +1118,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "tablefunc", + "nativeModuleStems": [ + "tablefunc" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -925,6 +1129,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tablefunc" + ], "sqlName": "tablefunc" }, { @@ -941,6 +1148,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "tcn", + "nativeModuleStems": [ + "tcn" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -949,6 +1159,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tcn" + ], "sqlName": "tcn" }, { @@ -965,6 +1178,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "tsm_system_rows", + "nativeModuleStems": [ + "tsm_system_rows" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -973,6 +1189,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tsm_system_rows" + ], "sqlName": "tsm_system_rows" }, { @@ -989,6 +1208,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "tsm_system_time", + "nativeModuleStems": [ + "tsm_system_time" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -997,6 +1219,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tsm_system_time" + ], "sqlName": "tsm_system_time" }, { @@ -1015,6 +1240,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "unaccent", + "nativeModuleStems": [ + "unaccent" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -1025,6 +1253,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "unaccent" + ], "sqlName": "unaccent" }, { @@ -1043,6 +1274,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "uuid-ossp", + "nativeModuleStems": [ + "uuid-ossp" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -1051,6 +1285,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "uuid-ossp" + ], "sqlName": "uuid-ossp" }, { @@ -1067,6 +1304,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-vector", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "vector", + "nativeModuleStems": [ + "vector" + ], "npmPackage": "@oliphaunt/extension-vector", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-vector", @@ -1075,6 +1315,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "vector" + ], "sqlName": "vector" } ] as const satisfies readonly GeneratedExtensionMetadata[]; diff --git a/src/native/sdks/react-native/tools/mobile-extension-artifact-paths.mts b/src/native/sdks/react-native/tools/mobile-extension-artifact-paths.mts index 10f5775a7..bf73e04f3 100644 --- a/src/native/sdks/react-native/tools/mobile-extension-artifact-paths.mts +++ b/src/native/sdks/react-native/tools/mobile-extension-artifact-paths.mts @@ -50,6 +50,8 @@ const EXTENSION_MEMBER_KEYS = new Set([ 'extensionSqlFileNames', 'extensionSqlFilePrefixes', 'nativeModuleStem', + 'sqlMembers', + 'nativeModuleStems', 'iosNativeDependencies', 'iosRegistration', 'wasixInstall', @@ -420,6 +422,8 @@ async function loadRepositoryContract(root) { } const canonical = { sqlName, + sqlMembers: row['sql-members'], + nativeModuleStems: row['native-module-stems'], createsExtension: row['creates-extension'], dependencies, dataFiles: canonicalStringList( @@ -595,6 +599,8 @@ function validateExactMobileRoles(member, target, assets, context) { function validateMemberContract(member, expected, context) { for (const field of [ + 'sqlMembers', + 'nativeModuleStems', 'sqlName', 'createsExtension', 'dependencies', diff --git a/src/native/sdks/react-native/tools/mobile-extension-artifact-paths.test.mts b/src/native/sdks/react-native/tools/mobile-extension-artifact-paths.test.mts index 3c23f3d5a..9c6505cf1 100644 --- a/src/native/sdks/react-native/tools/mobile-extension-artifact-paths.test.mts +++ b/src/native/sdks/react-native/tools/mobile-extension-artifact-paths.test.mts @@ -198,6 +198,8 @@ function extensionMember(sqlName, stagesIos = true) { return { sqlName, createsExtension: row['creates-extension'], + sqlMembers: row['sql-members'], + nativeModuleStems: row['native-module-stems'], dependencies: [...row['selected-extension-dependencies']].sort(), dataFiles: [...row['runtime-share-data-files']].sort(), extensionSqlFileNames: [...row['extension-sql-file-names']].sort(), diff --git a/src/native/sdks/react-native/tools/stage-ios-app.mts b/src/native/sdks/react-native/tools/stage-ios-app.mts index d161a3710..575d08ce2 100755 --- a/src/native/sdks/react-native/tools/stage-ios-app.mts +++ b/src/native/sdks/react-native/tools/stage-ios-app.mts @@ -65,6 +65,8 @@ const EXTENSION_ARTIFACT_PROPERTY_KEYS = new Set([ 'staticSymbolAliases', 'licenseFiles', 'licenseProfile', + 'sqlMembers', + 'nativeModuleStems', 'files', ]); const GENERATED_EXTENSION_CATALOG = JSON.parse( @@ -243,6 +245,11 @@ function generatedExtensionCatalog(value) { artifactProduct, releaseProduct, runtimeBound: row['runtime-bound'], + sqlMembers: canonicalPortableList(row['sql-members'], `${sqlName}.sql-members`), + dependencies: canonicalPortableList( + row['selected-extension-dependencies'], + `${sqlName}.selected-extension-dependencies`, + ), }); } return rows; @@ -696,6 +703,8 @@ function validateExtension(value, label, carriers) { 'sqlName', 'tag', 'version', + 'sqlMembers', + 'nativeModuleStems', ], label, ); @@ -740,6 +749,26 @@ function validateExtension(value, label, carriers) { root.nativeModuleStem === null ? null : portable(root.nativeModuleStem, `${label}.nativeModuleStem`); + const sqlMembers = canonicalPortableList(root.sqlMembers, `${label}.sqlMembers`); + const nativeModuleStems = canonicalPortableList( + root.nativeModuleStems, + `${label}.nativeModuleStems`, + ); + if (root.createsExtension ? !sqlMembers.includes(sqlName) : sqlMembers.length !== 0) + fail( + `${label}.sqlMembers must contain the primary extension exactly when createsExtension is true`, + ); + if ( + JSON.stringify(nativeModuleStems) !== + JSON.stringify(nativeModuleStem === null ? [] : [nativeModuleStem]) + ) + fail(`${label}.nativeModuleStems must contain only the merged primary mobile module`); + for (const [field, value] of Object.entries( + generated.sqlMembers.length > 1 ? { sqlMembers, dependencies } : { sqlMembers }, + )) { + if (JSON.stringify(value) !== JSON.stringify(generated[field])) + fail(`${label}.${field} must match generated canonical metadata`); + } const sharedPreloadLibraries = canonicalPortableList( root.sharedPreloadLibraries, `${label}.sharedPreloadLibraries`, @@ -806,6 +835,8 @@ function validateExtension(value, label, carriers) { return { assets: { dependencyFrameworks, extension, runtime }, createsExtension: root.createsExtension, + sqlMembers, + nativeModuleStems, dataFiles, dependencies, extensionSqlFileNames, @@ -1797,17 +1828,20 @@ async function validateExactExtensionArtifactInventory( for (const legalFile of carrier.legal.files) expectedFiles.add(legalFile.member); if (carrier.createsExtension) { const extensionRoot = 'files/share/postgresql/extension'; + const sqlMembers = carrier.sqlMembers; const control = `${extensionRoot}/${carrier.sqlName}.control`; if (!actualFiles.has(control)) fail(`${source} is missing canonical control file ${control}`); expectedFiles.add(control); const ownedSqlFiles = [...actualFiles].filter((file) => { if (path.posix.dirname(file) !== extensionRoot) return false; const name = path.posix.basename(file); - if (name === `${carrier.sqlName}.sql`) return true; - const prefix = `${carrier.sqlName}--`; - if (!name.startsWith(prefix) || !name.endsWith('.sql')) return false; - const versionPath = name.slice(prefix.length, -'.sql'.length); - return /^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(versionPath); + return sqlMembers.some((member) => { + if (name === `${member}.control` || name === `${member}.sql`) return true; + const prefix = `${member}--`; + if (!name.startsWith(prefix) || !name.endsWith('.sql')) return false; + const versionPath = name.slice(prefix.length, -'.sql'.length); + return /^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(versionPath); + }); }); const installSqlFiles = ownedSqlFiles.filter((file) => { const name = path.posix.basename(file); @@ -1828,6 +1862,22 @@ async function validateExactExtensionArtifactInventory( ); }); for (const file of [...ownedSqlFiles, ...ancillarySqlFiles]) expectedFiles.add(file); + for (const member of sqlMembers) { + if (!actualFiles.has(`${extensionRoot}/${member}.control`)) + fail(`${source} is missing canonical control file ${member}.control`); + if ( + ![...actualFiles].some((file) => { + if (path.posix.dirname(file) !== extensionRoot) return false; + const name = path.posix.basename(file); + if (name === `${member}.sql`) return true; + const prefix = `${member}--`; + if (!name.startsWith(prefix) || !name.endsWith('.sql')) return false; + const version = name.slice(prefix.length, -'.sql'.length); + return !version.includes('--') && /^[0-9][A-Za-z0-9._-]*$/u.test(version); + }) + ) + fail(`${source} is missing an install SQL file owned by ${member}`); + } } for (const dataFile of dataFiles) { expectedFiles.add(`files/share/postgresql/${dataFile}`); @@ -1880,6 +1930,8 @@ async function extensionResourceRoot(carrier, base, cacheDir) { const manifestFile = path.join(root, 'manifest.properties'); const manifest = parseProperties(await fs.readFile(manifestFile, 'utf8'), manifestFile); rejectUnsupportedProperties(manifest, EXTENSION_ARTIFACT_PROPERTY_KEYS, manifestFile); + requireProperty(manifest, 'sqlMembers', carrier.sqlMembers.join(','), manifestFile); + requireProperty(manifest, 'nativeModuleStems', carrier.nativeModuleStems.join(','), manifestFile); requireProperty(manifest, 'packageLayout', 'oliphaunt-extension-artifact-v1', manifestFile); requireProperty(manifest, 'pgMajor', '18', manifestFile); requireProperty(manifest, 'sqlName', carrier.sqlName, manifestFile); @@ -1971,7 +2023,7 @@ async function extensionResourceRoot(carrier, base, cacheDir) { `${manifestFile} dataFiles must exactly match the frozen carrier contract for ${carrier.sqlName}`, ); } - requireExactPropertySet(manifest, EXTENSION_ARTIFACT_PROPERTY_KEYS, manifestFile); + requireExactPropertySet(manifest, [...EXTENSION_ARTIFACT_PROPERTY_KEYS], manifestFile); const mobileStaticArchives = mobileStaticArchivePaths(manifest, carrier, manifestFile); const mobileStaticDependencyArchives = mobileStaticDependencyArchivePaths( manifest, @@ -2321,17 +2373,19 @@ async function stage(args, base, selected) { const frozen = { base: { product: base.product, version: base.version, assets: base.assets }, cacheKey: `react-native-ios-${selectionHash.slice(0, 32)}`, - extensions: selected.map((carrier) => ({ - assets: carrier.assets, - createsExtension: carrier.createsExtension, - dependencies: carrier.dependencies, - nativeDependencies: carrier.nativeDependencies, - nativeModuleStem: carrier.nativeModuleStem, - product: carrier.product, - releaseProduct: carrier.releaseProduct, - sqlName: carrier.sqlName, - version: carrier.version, - })), + extensions: selected + .map((carrier) => ({ + assets: carrier.assets, + createsExtension: carrier.createsExtension, + dependencies: carrier.dependencies, + nativeDependencies: carrier.nativeDependencies, + nativeModuleStem: carrier.nativeModuleStem, + product: carrier.product, + releaseProduct: carrier.releaseProduct, + sqlName: carrier.sqlName, + version: carrier.version, + })) + .sort((left, right) => compareText(left.sqlName, right.sqlName)), requestedExtensions: args.extensions, icu: args.icu, seedProfile: args.seedProfile, @@ -2427,7 +2481,9 @@ export async function stageIosApp(options) { ); } } - const selected = selectedClosure(args.extensions, bySqlName); + const selected = selectedClosure(args.extensions, bySqlName).sort((left, right) => + compareText(left.sqlName, right.sqlName), + ); await stage(args, base, selected); return { outputDir: args.outputDir, selected: selected.map(({ sqlName }) => sqlName) }; } diff --git a/src/native/sdks/react-native/tools/stage-ios-app.test.mts b/src/native/sdks/react-native/tools/stage-ios-app.test.mts index a325f8903..da918e62f 100755 --- a/src/native/sdks/react-native/tools/stage-ios-app.test.mts +++ b/src/native/sdks/react-native/tools/stage-ios-app.test.mts @@ -532,11 +532,23 @@ async function extensionRow(root, config) { `staticSymbolAliases=${staticSymbolAliases.join(',')}`, `licenseFiles=${licenseFiles.join(',')}`, `licenseProfile=${profile}`, + `sqlMembers=${config.sqlMembers.join(',')}`, + `nativeModuleStems=${config.nativeModuleStem ?? ''}`, 'files=files', '', ].join('\n'), ); if (createsExtension) { + for (const member of config.sqlMembers.filter((name) => name !== config.sqlName)) { + await write( + path.join(source, 'files/share/postgresql/extension', `${member}.control`), + "default_version = '1.0'\n", + ); + await write( + path.join(source, 'files/share/postgresql/extension', `${member}--1.0.sql`), + 'SELECT 1;\n', + ); + } await write( path.join(source, 'files', 'share', 'postgresql', 'extension', `${config.sqlName}.control`), `comment = '${config.sqlName} fixture'\n`, @@ -676,6 +688,8 @@ async function extensionRow(root, config) { extension: { assets: logicalAssets.map(({ locator }) => locator), createsExtension, + sqlMembers: config.sqlMembers, + nativeModuleStems: config.nativeModuleStem === null ? [] : [config.nativeModuleStem], dataFiles, dependencies: config.dependencies, extensionSqlFileNames: config.extensionSqlFileNames, @@ -769,6 +783,7 @@ async function createFixture(root) { { sqlName: 'auto_explain' }, { sqlName: 'cube' }, { sqlName: 'earthdistance' }, + { sqlName: 'fuzzystrmatch' }, { sqlName: 'pgcrypto' }, { sqlName: 'pgtap', includeUpdateSql: true }, { @@ -802,6 +817,7 @@ async function createFixture(root) { const config = { ...fixture, createsExtension: generated['creates-extension'], + sqlMembers: generated['sql-members'], dataFiles: generated['runtime-share-data-files'], dependencies: generated['selected-extension-dependencies'], extensionSqlFileNames: generated['extension-sql-file-names'], @@ -905,11 +921,20 @@ async function main() { 'auto_explain', 'cube', 'earthdistance', + 'fuzzystrmatch', 'pgcrypto', 'pgtap', 'postgis', ]); await validateStagedPackage(output, false); + for (const member of GENERATED_EXTENSION_BY_SQL_NAME.get('postgis')['sql-members']) { + const directory = path.join( + output, + 'resources/OliphauntReactNativeResources.bundle/oliphaunt/runtime/files/share/postgresql/extension', + ); + await fs.access(path.join(directory, `${member}.control`)); + await fs.access(path.join(directory, `${member}--1.0.sql`)); + } const payloadPodspec = await fs.readFile( path.join(output, 'OliphauntReactNativePayload.podspec'), @@ -995,6 +1020,7 @@ async function main() { 'liboliphaunt_extension_auto_explain.xcframework', 'liboliphaunt_extension_cube.xcframework', 'liboliphaunt_extension_earthdistance.xcframework', + 'liboliphaunt_extension_fuzzystrmatch.xcframework', 'liboliphaunt_extension_pgcrypto.xcframework', 'liboliphaunt_extension_postgis-3.xcframework', ]); @@ -1063,12 +1089,15 @@ async function main() { ); assert.match( runtimeManifest, - /^selectedExtensions=auto_explain,cube,earthdistance,pgcrypto,pgtap,postgis$/mu, + /^selectedExtensions=auto_explain,cube,earthdistance,fuzzystrmatch,pgcrypto,pgtap,postgis$/mu, + ); + assert.match( + runtimeManifest, + /^extensions=cube,earthdistance,fuzzystrmatch,pgcrypto,pgtap,postgis$/mu, ); - assert.match(runtimeManifest, /^extensions=cube,earthdistance,pgcrypto,pgtap,postgis$/mu); assert.match( runtimeManifest, - /^mobileStaticRegistryRegistered=auto_explain,cube,earthdistance,pgcrypto,postgis$/mu, + /^mobileStaticRegistryRegistered=auto_explain,cube,earthdistance,fuzzystrmatch,pgcrypto,postgis$/mu, ); const packageSize = await fs.readFile( path.join( @@ -1083,7 +1112,7 @@ async function main() { assert.match(packageSize, /^extension\tauto_explain\t-\t0\t0$/mu); assert.match( packageSize, - /^extensions\tselected\tauto_explain,cube,earthdistance,pgcrypto,pgtap,postgis\t/mu, + /^extensions\tselected\tauto_explain,cube,earthdistance,fuzzystrmatch,pgcrypto,pgtap,postgis\t/mu, ); const registry = await fs.readFile( path.join(output, 'generated', 'static-registry', 'oliphaunt_static_registry.c'), @@ -1182,8 +1211,8 @@ async function main() { await fs.writeFile( missingCreateableManifest, (await fs.readFile(missingCreateableManifest, 'utf8')).replace( - /^extensions=cube,earthdistance,pgcrypto,pgtap,postgis$/mu, - 'extensions=cube,earthdistance,pgcrypto,postgis', + /^extensions=cube,earthdistance,fuzzystrmatch,pgcrypto,pgtap,postgis$/mu, + 'extensions=cube,earthdistance,fuzzystrmatch,pgcrypto,postgis', ), ); await assert.rejects( @@ -1204,8 +1233,8 @@ async function main() { await fs.writeFile( missingNativeRegistrationManifest, (await fs.readFile(missingNativeRegistrationManifest, 'utf8')).replace( - /^mobileStaticRegistryRegistered=auto_explain,cube,earthdistance,pgcrypto,postgis$/mu, - 'mobileStaticRegistryRegistered=cube,earthdistance,pgcrypto,postgis', + /^mobileStaticRegistryRegistered=auto_explain,cube,earthdistance,fuzzystrmatch,pgcrypto,postgis$/mu, + 'mobileStaticRegistryRegistered=cube,earthdistance,fuzzystrmatch,pgcrypto,postgis', ), ); await assert.rejects( @@ -1544,6 +1573,43 @@ async function main() { /fields must be exactly bytes,carrier,format,member,path,role,sha256; got bytes,carrier,format,path,role,sha256/u, ); + for (const field of ['sqlMembers', 'nativeModuleStems']) { + const incomplete = structuredClone(carrier); + delete incomplete.extensions.find(({ sqlName }) => sqlName === 'postgis')[field]; + await expectCarrierFailure( + `missing-${field}`, + incomplete, + ['postgis'], + /fields must be exactly/u, + ); + } + for (const [field, value] of [ + ['sqlMembers', ['postgis']], + ['dependencies', []], + ]) { + const incomplete = structuredClone(carrier); + incomplete.extensions.find(({ sqlName }) => sqlName === 'postgis')[field] = value; + await expectCarrierFailure( + `incomplete-postgis-${field}`, + incomplete, + ['postgis'], + /must match generated canonical metadata/u, + ); + } + + const unmergedPostgis = structuredClone(carrier); + unmergedPostgis.extensions.find(({ sqlName }) => sqlName === 'postgis').nativeModuleStems = [ + 'postgis-3', + 'postgis_raster-3', + 'postgis_topology-3', + ]; + await expectCarrierFailure( + 'unmerged-postgis-modules', + unmergedPostgis, + ['postgis'], + /nativeModuleStems must contain only the merged primary mobile module/u, + ); + const missingFrozenContentField = structuredClone(carrier); delete missingFrozenContentField.extensions.find(({ sqlName }) => sqlName === 'pgtap') .dataFiles; @@ -1803,6 +1869,28 @@ async function main() { /missing an install SQL file owned by pgtap/u, ); + const missingCompanion = structuredClone(carrier); + replaceExtensionRuntimeAsset( + missingCompanion, + 'postgis', + await mutatedExtensionRuntime( + root, + 'postgis', + 'missing-topology-control.tar.gz', + async (stage) => { + await fs.rm( + path.join(stage, 'files/share/postgresql/extension/postgis_topology.control'), + ); + }, + ), + ); + await expectCarrierFailure( + 'missing-topology-control', + missingCompanion, + ['postgis'], + /missing canonical control file postgis_topology\.control/u, + ); + const wrongDependencyArchive = structuredClone(carrier); replaceExtensionRuntimeAsset( wrongDependencyArchive, diff --git a/src/native/sdks/react-native/tools/stage-mobile-qualification.test.mts b/src/native/sdks/react-native/tools/stage-mobile-qualification.test.mts index 4b604cb1c..45241100b 100644 --- a/src/native/sdks/react-native/tools/stage-mobile-qualification.test.mts +++ b/src/native/sdks/react-native/tools/stage-mobile-qualification.test.mts @@ -79,6 +79,8 @@ test('iOS qualification keeps the Swift base, independent carriers and binary ch }, sqlName: 'pgtap', createsExtension: true, + sqlMembers: ['pgtap'], + nativeModuleStems: [], dependencies: [], dataFiles: [], extensionSqlFileNames: [], diff --git a/src/native/sdks/react-native/tools/validate-mobile-runtime-files.mts b/src/native/sdks/react-native/tools/validate-mobile-runtime-files.mts index 16454b1fe..5af524a82 100644 --- a/src/native/sdks/react-native/tools/validate-mobile-runtime-files.mts +++ b/src/native/sdks/react-native/tools/validate-mobile-runtime-files.mts @@ -81,6 +81,9 @@ function extensionContracts(document, label) { } contracts.push({ createsExtension: row['creates-extension'], + sqlMembers: canonicalStringList(row['sql-members'], `${rowLabel}.sql-members`, (value) => + PORTABLE_SQL_NAME.test(value), + ), dataFiles: canonicalStringList( row['data-files'], `${rowLabel}.data-files`, @@ -149,11 +152,13 @@ function isBaselineExtensionAsset(fileName) { function ownsExtensionAsset(contract, fileName) { return ( - (contract.createsExtension && fileName === `${contract.sqlName}.control`) || - (contract.createsExtension && fileName === `${contract.sqlName}.sql`) || (contract.createsExtension && - fileName.startsWith(`${contract.sqlName}--`) && - fileName.endsWith('.sql')) || + contract.sqlMembers.some( + (member) => + fileName === `${member}.control` || + fileName === `${member}.sql` || + (fileName.startsWith(`${member}--`) && fileName.endsWith('.sql')), + )) || contract.extensionSqlFileNames.includes(fileName) || (fileName.endsWith('.sql') && contract.extensionSqlFilePrefixes.some((prefix) => fileName.startsWith(prefix))) diff --git a/src/native/sdks/rust/src/server.rs b/src/native/sdks/rust/src/server.rs index f3c962357..15df82307 100644 --- a/src/native/sdks/rust/src/server.rs +++ b/src/native/sdks/rust/src/server.rs @@ -848,7 +848,6 @@ mod tests { .map(PathBuf::from), Some(proj_data) ); - let mut unselected = Command::new("postgres"); configure_extension_runtime_env(&mut unselected, &runtime_dir, &[]); assert_eq!( diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/cube/manifest.properties b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/cube/manifest.properties index 5f26dce00..e4ed9258f 100644 --- a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/cube/manifest.properties +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/cube/manifest.properties @@ -19,4 +19,6 @@ staticSymbolPrefix=oliphaunt_static_cube staticSymbolAliases= licenseFiles= licenseProfile=contrib-native +sqlMembers=cube +nativeModuleStems=cube files=files diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/earthdistance/manifest.properties b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/earthdistance/manifest.properties index 070c0a87d..621c60044 100644 --- a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/earthdistance/manifest.properties +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/earthdistance/manifest.properties @@ -19,4 +19,6 @@ staticSymbolPrefix=oliphaunt_static_earthdistance staticSymbolAliases= licenseFiles= licenseProfile=contrib-native +sqlMembers=earthdistance +nativeModuleStems=earthdistance files=files diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/LICENSE b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/LICENSE new file mode 100644 index 000000000..92f26b96f --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/LICENSE @@ -0,0 +1 @@ +Oliphaunt license test fixture. diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/THIRD_PARTY_LICENSES/PostgreSQL-COPYRIGHT b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/THIRD_PARTY_LICENSES/PostgreSQL-COPYRIGHT new file mode 100644 index 000000000..e353489a4 --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/THIRD_PARTY_LICENSES/PostgreSQL-COPYRIGHT @@ -0,0 +1 @@ +PostgreSQL license test fixture. diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/THIRD_PARTY_NOTICES.md b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/THIRD_PARTY_NOTICES.md new file mode 100644 index 000000000..045acf78a --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/THIRD_PARTY_NOTICES.md @@ -0,0 +1 @@ +Oliphaunt third-party notices test fixture. diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/lib/postgresql/fuzzystrmatch.dylib b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/lib/postgresql/fuzzystrmatch.dylib new file mode 100644 index 000000000..c878da482 --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/lib/postgresql/fuzzystrmatch.dylib @@ -0,0 +1 @@ +fixture fuzzystrmatch native module diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/share/postgresql/extension/fuzzystrmatch--1.5.sql b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/share/postgresql/extension/fuzzystrmatch--1.5.sql new file mode 100644 index 000000000..e010ec423 --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/share/postgresql/extension/fuzzystrmatch--1.5.sql @@ -0,0 +1 @@ +CREATE FUNCTION fuzzystrmatch_in(cstring) RETURNS fuzzystrmatch AS 'MODULE_PATHNAME', 'fuzzystrmatch_in' LANGUAGE C IMMUTABLE STRICT; diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/share/postgresql/extension/fuzzystrmatch.control b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/share/postgresql/extension/fuzzystrmatch.control new file mode 100644 index 000000000..a0ad60ad5 --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/files/share/postgresql/extension/fuzzystrmatch.control @@ -0,0 +1,2 @@ +default_version = '1.5' +module_pathname = '$libdir/fuzzystrmatch' diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/manifest.properties b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/manifest.properties new file mode 100644 index 000000000..ccf66dd69 --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/fuzzystrmatch/manifest.properties @@ -0,0 +1,24 @@ +packageLayout=oliphaunt-extension-artifact-v1 +pgMajor=18 +sqlName=fuzzystrmatch +createsExtension=yes +nativeModuleStem=fuzzystrmatch +nativeModuleFile=fuzzystrmatch.dylib +nativeTarget=ios-xcframework +nativeRuntimeProduct=liboliphaunt-native +nativeRuntimeVersion=0.1.0 +dependencies= +dataFiles= +extensionSqlFileNames= +extensionSqlFilePrefixes= +sharedPreloadLibraries= +mobilePrebuilt=yes +mobileStaticArchives= +mobileStaticDependencyArchives= +staticSymbolPrefix=oliphaunt_static_fuzzystrmatch +staticSymbolAliases= +licenseFiles= +licenseProfile=contrib-native +sqlMembers=fuzzystrmatch +nativeModuleStems=fuzzystrmatch +files=files diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/pgtap/manifest.properties b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/pgtap/manifest.properties index 55e744fd8..bb647a9b6 100644 --- a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/pgtap/manifest.properties +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/pgtap/manifest.properties @@ -19,4 +19,6 @@ staticSymbolPrefix= staticSymbolAliases= licenseFiles=share/licenses/pgtap/README.md licenseProfile=external-native +sqlMembers=pgtap +nativeModuleStems= files=files diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_raster--3.6.1.sql b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_raster--3.6.1.sql new file mode 100644 index 000000000..319974100 --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_raster--3.6.1.sql @@ -0,0 +1,2 @@ +-- Companion SQL package fixture. +SELECT 1; diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_raster.control b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_raster.control new file mode 100644 index 000000000..3c4c27033 --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_raster.control @@ -0,0 +1 @@ +default_version = '3.6.1' diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_tiger_geocoder--3.6.1.sql b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_tiger_geocoder--3.6.1.sql new file mode 100644 index 000000000..319974100 --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_tiger_geocoder--3.6.1.sql @@ -0,0 +1,2 @@ +-- Companion SQL package fixture. +SELECT 1; diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_tiger_geocoder.control b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_tiger_geocoder.control new file mode 100644 index 000000000..3c4c27033 --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_tiger_geocoder.control @@ -0,0 +1 @@ +default_version = '3.6.1' diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_topology--3.6.1.sql b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_topology--3.6.1.sql new file mode 100644 index 000000000..319974100 --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_topology--3.6.1.sql @@ -0,0 +1,2 @@ +-- Companion SQL package fixture. +SELECT 1; diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_topology.control b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_topology.control new file mode 100644 index 000000000..3c4c27033 --- /dev/null +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/files/share/postgresql/extension/postgis_topology.control @@ -0,0 +1 @@ +default_version = '3.6.1' diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/manifest.properties b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/manifest.properties index 5d9d6f439..86c07bacd 100644 --- a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/manifest.properties +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/postgis/manifest.properties @@ -7,7 +7,7 @@ nativeModuleFile=postgis-3.dylib nativeTarget=ios-xcframework nativeRuntimeProduct=liboliphaunt-native nativeRuntimeVersion=0.1.0 -dependencies= +dependencies=fuzzystrmatch dataFiles=contrib/postgis-3.6/legacy.sql,contrib/postgis-3.6/legacy_gist.sql,contrib/postgis-3.6/legacy_minimal.sql,contrib/postgis-3.6/postgis.sql,contrib/postgis-3.6/postgis_upgrade.sql,contrib/postgis-3.6/spatial_ref_sys.sql,contrib/postgis-3.6/uninstall_legacy.sql,contrib/postgis-3.6/uninstall_postgis.sql,proj/proj.db extensionSqlFileNames=uninstall_postgis.sql extensionSqlFilePrefixes=postgis_comments,postgis_proc_set_search_path,rtpostgis @@ -19,4 +19,6 @@ staticSymbolPrefix=oliphaunt_static_postgis_3 staticSymbolAliases=difference:oliphaunt_static_postgis_3_difference,pg_finfo_difference:pg_finfo_oliphaunt_static_postgis_3_difference licenseFiles=share/licenses/geos/COPYING,share/licenses/geos/src/deps/ryu/LICENSE,share/licenses/geos/src/deps/ryu/LICENSE-Apache2,share/licenses/geos/src/deps/ryu/LICENSE-Boost,share/licenses/json-c/COPYING,share/licenses/libcharset/COPYING.LIB,share/licenses/libiconv/COPYING.LIB,share/licenses/libxml2/Copyright,share/licenses/postgis/COPYING,share/licenses/postgis/LICENSE.TXT,share/licenses/postgis/deps/flatgeobuf/flatbuffers/LICENSE,share/licenses/postgis/deps/ryu/LICENSE,share/licenses/postgis/deps/ryu/LICENSE-Apache2,share/licenses/postgis/deps/ryu/LICENSE-Boost,share/licenses/proj/COPYING,share/licenses/sqlite/LICENSE.md licenseProfile=external-native +sqlMembers=postgis,postgis_raster,postgis_tiger_geocoder,postgis_topology +nativeModuleStems=postgis-3 files=files diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-selection.json b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-selection.json index 6bc963bb5..3f453c500 100644 --- a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-selection.json +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-selection.json @@ -17,7 +17,9 @@ "sqlName": "earthdistance", "createsExtension": true, "dataFiles": [], - "dependencies": ["cube"], + "dependencies": [ + "cube" + ], "extensionSqlFileNames": [], "extensionSqlFilePrefixes": [], "nativeModuleStem": "earthdistance", @@ -37,7 +39,13 @@ "address": "geo_distance" } ] - } + }, + "sqlMembers": [ + "earthdistance" + ], + "nativeModuleStems": [ + "earthdistance" + ] }, { "product": "oliphaunt-extension-contrib-pg18", @@ -70,7 +78,13 @@ "address": "pg_finfo_cube_in" } ] - } + }, + "sqlMembers": [ + "cube" + ], + "nativeModuleStems": [ + "cube" + ] }, { "product": "oliphaunt-extension-postgis", @@ -89,8 +103,12 @@ "contrib/postgis-3.6/uninstall_postgis.sql", "proj/proj.db" ], - "dependencies": [], - "extensionSqlFileNames": ["uninstall_postgis.sql"], + "dependencies": [ + "fuzzystrmatch" + ], + "extensionSqlFileNames": [ + "uninstall_postgis.sql" + ], "extensionSqlFilePrefixes": [ "postgis_comments", "postgis_proc_set_search_path", @@ -126,7 +144,16 @@ "address": "pg_finfo_oliphaunt_static_postgis_3_difference" } ] - } + }, + "sqlMembers": [ + "postgis", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology" + ], + "nativeModuleStems": [ + "postgis-3" + ] }, { "product": "oliphaunt-extension-pgtap", @@ -136,14 +163,53 @@ "createsExtension": true, "dataFiles": [], "dependencies": [], - "extensionSqlFileNames": ["uninstall_pgtap.sql"], - "extensionSqlFilePrefixes": ["pgtap-core", "pgtap-schema"], + "extensionSqlFileNames": [ + "uninstall_pgtap.sql" + ], + "extensionSqlFilePrefixes": [ + "pgtap-core", + "pgtap-schema" + ], "nativeModuleStem": null, "nativeDependencies": [], "resourceRoot": "swiftpm-extension-resources/pgtap", "sharedPreloadLibraries": [], "asset": null, - "registration": null + "registration": null, + "sqlMembers": [ + "pgtap" + ], + "nativeModuleStems": [] + }, + { + "product": "oliphaunt-extension-contrib-pg18", + "releaseProduct": "liboliphaunt-native", + "version": "0.1.0", + "sqlName": "fuzzystrmatch", + "createsExtension": true, + "dataFiles": [], + "dependencies": [], + "extensionSqlFileNames": [], + "extensionSqlFilePrefixes": [], + "nativeModuleStem": "fuzzystrmatch", + "nativeDependencies": [], + "resourceRoot": "swiftpm-extension-resources/fuzzystrmatch", + "sharedPreloadLibraries": [], + "asset": { + "name": "liboliphaunt-0.1.0-apple-spm-extension-fuzzystrmatch.zip", + "url": "https://github.com/f0rr0/oliphaunt/releases/download/oliphaunt-extension-contrib-pg18-v0.1.0/liboliphaunt-0.1.0-apple-spm-extension-fuzzystrmatch.zip", + "checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "registration": { + "hasInit": false, + "symbols": [] + }, + "sqlMembers": [ + "fuzzystrmatch" + ], + "nativeModuleStems": [ + "fuzzystrmatch" + ] } ] } diff --git a/src/native/sdks/swift/tools/extension-resource-inventory.mts b/src/native/sdks/swift/tools/extension-resource-inventory.mts index beedaf487..bdea65534 100644 --- a/src/native/sdks/swift/tools/extension-resource-inventory.mts +++ b/src/native/sdks/swift/tools/extension-resource-inventory.mts @@ -26,6 +26,8 @@ const PROPERTY_KEYS = Object.freeze([ 'staticSymbolAliases', 'licenseFiles', 'licenseProfile', + 'sqlMembers', + 'nativeModuleStems', 'files', ]); const LEGAL_MEMBERS_BY_PROFILE = Object.freeze({ @@ -140,6 +142,7 @@ function normalizeCanonicalRow(row, label) { } return { sqlName, + sqlMembers: canonicalList(row?.['sql-members'], `${label}.sql-members`, portable), product, releaseProduct, createsExtension, @@ -171,6 +174,22 @@ function normalizeFrozenRuntimeContract(row, label) { if (typeof row?.createsExtension !== 'boolean') fail(label, 'createsExtension must be boolean'); const nativeModuleStem = row?.nativeModuleStem ?? null; if (nativeModuleStem !== null) portable(nativeModuleStem, `${label}.nativeModuleStem`); + const sqlMembers = canonicalList(row.sqlMembers, `${label}.sqlMembers`, portable); + const nativeModuleStems = canonicalList( + row.nativeModuleStems, + `${label}.nativeModuleStems`, + portable, + ); + if (row.createsExtension ? !sqlMembers.includes(sqlName) : sqlMembers.length !== 0) + fail( + label, + 'sqlMembers must contain the primary extension exactly when createsExtension is true', + ); + if ( + JSON.stringify(nativeModuleStems) !== + JSON.stringify(nativeModuleStem === null ? [] : [nativeModuleStem]) + ) + fail(label, 'mobile nativeModuleStems must contain only the merged primary module'); const dependencies = canonicalList(row?.dependencies, `${label}.dependencies`, portable); if (dependencies.includes(sqlName)) fail(label, 'dependencies must not include the extension itself'); @@ -190,6 +209,8 @@ function normalizeFrozenRuntimeContract(row, label) { fail(label, 'extensionSqlFilePrefixes must contain dot-free portable basename prefixes'); } return { + sqlMembers, + nativeModuleStems, createsExtension: row.createsExtension, dataFiles: canonicalList(row?.dataFiles, `${label}.dataFiles`, canonicalRelativePath), dependencies, @@ -596,11 +617,13 @@ export async function snapshotSafeFileTree( function sqlOwned(fileName, canonical) { return ( - (canonical.createsExtension && fileName === `${canonical.sqlName}.control`) || - (canonical.createsExtension && fileName === `${canonical.sqlName}.sql`) || (canonical.createsExtension && - fileName.startsWith(`${canonical.sqlName}--`) && - fileName.endsWith('.sql')) || + canonical.sqlMembers.some( + (member) => + fileName === `${member}.control` || + fileName === `${member}.sql` || + (fileName.startsWith(`${member}--`) && fileName.endsWith('.sql')), + )) || canonical.extensionSqlFileNames.includes(fileName) || (fileName.endsWith('.sql') && canonical.extensionSqlFilePrefixes.some((prefix) => fileName.startsWith(prefix))) @@ -635,6 +658,12 @@ export async function validateSwiftExtensionResourceArtifact({ }) { assertSwiftExtensionMatchesCanonical(extension, canonical, label); const contract = normalizeFrozenRuntimeContract(extension, `${label} frozen carrier contract`); + for (const field of canonical.sqlMembers.length > 1 + ? ['sqlMembers', 'dependencies'] + : ['sqlMembers']) { + if (JSON.stringify(contract[field]) !== JSON.stringify(canonical[field])) + fail(label, `${field} must match generated canonical metadata`); + } if ( nativeRuntime?.product !== 'liboliphaunt-native' || !STABLE_SEMVER.test(nativeRuntime?.version ?? '') @@ -673,6 +702,8 @@ export async function validateSwiftExtensionResourceArtifact({ ['mobilePrebuilt', contract.nativeModuleStem === null ? 'no' : 'yes'], ['files', 'files'], ]); + expected.set('sqlMembers', contract.sqlMembers.join(',')); + expected.set('nativeModuleStems', contract.nativeModuleStems.join(',')); for (const [key, value] of expected) { if (properties.get(key) !== value) fail(label, `manifest ${key} must be ${JSON.stringify(value)}`); @@ -730,6 +761,17 @@ export async function validateSwiftExtensionResourceArtifact({ if (contract.createsExtension && (!hasControl || !hasSql)) { fail(label, `must contain ${contract.sqlName}.control and canonical base installation SQL`); } + for (const member of contract.sqlMembers) { + if ( + !byPath.has(`files/share/postgresql/extension/${member}.control`) || + !files.some( + ({ relative }) => + relative.startsWith('files/share/postgresql/extension/') && + isCanonicalInstallSql(relative.slice('files/share/postgresql/extension/'.length), member), + ) + ) + fail(label, `must contain ${member}.control and canonical base installation SQL`); + } for (const dataFile of contract.dataFiles) allowed.add(`files/share/postgresql/${dataFile}`); if (contract.nativeModuleStem !== null) { allowed.add(`files/lib/postgresql/${contract.nativeModuleStem}.dylib`); diff --git a/src/native/sdks/swift/tools/extension-resource-inventory.test.mts b/src/native/sdks/swift/tools/extension-resource-inventory.test.mts index 77cba817d..7f5c2f1ea 100644 --- a/src/native/sdks/swift/tools/extension-resource-inventory.test.mts +++ b/src/native/sdks/swift/tools/extension-resource-inventory.test.mts @@ -119,6 +119,99 @@ async function main() { const postgis = extensions.get('postgis'); const postgisManifest = path.join(postgis.resourceRoot, 'manifest.properties'); const postgisManifestText = await fs.readFile(postgisManifest, 'utf8'); + const sqlMembers = catalog.get('postgis').sqlMembers; + assert.ok( + sqlMembers.includes('postgis_topology') && sqlMembers.includes('postgis_tiger_geocoder'), + ); + for (const missing of ['sqlMembers', 'nativeModuleStems']) { + const incomplete = { ...postgis }; + delete incomplete[missing]; + await assert.rejects( + () => + validateSwiftExtensionResourceArtifact({ + extension: incomplete, + canonical: catalog.get('postgis'), + nativeRuntime: document.nativeRuntime, + }), + /must be a string array/u, + ); + } + await assert.rejects( + () => + validateSwiftExtensionResourceArtifact({ + extension: { ...postgis, sqlMembers: ['postgis'] }, + canonical: catalog.get('postgis'), + nativeRuntime: document.nativeRuntime, + }), + /sqlMembers must match generated canonical metadata/u, + ); + await assert.rejects( + () => + validateSwiftExtensionResourceArtifact({ + extension: { ...postgis, dependencies: [] }, + canonical: catalog.get('postgis'), + nativeRuntime: document.nativeRuntime, + }), + /dependencies must match generated canonical metadata/u, + ); + await assert.rejects( + () => + validateSwiftExtensionResourceArtifact({ + extension: { + ...postgis, + nativeModuleStems: ['postgis-3', 'postgis_raster-3', 'postgis_topology-3'], + }, + canonical: catalog.get('postgis'), + nativeRuntime: document.nativeRuntime, + }), + /mobile nativeModuleStems must contain only the merged primary module/u, + ); + const expandedPostgis = postgis; + const companionFiles = sqlMembers + .filter((name) => name !== 'postgis') + .map((member) => + path.join(postgis.resourceRoot, 'files/share/postgresql/extension', `${member}.control`), + ); + const expandedResources = await validateSwiftExtensionResourceArtifact({ + extension: expandedPostgis, + canonical: catalog.get('postgis'), + nativeRuntime: document.nativeRuntime, + }); + assert.ok( + expandedResources.files.some( + ({ relative }) => relative === 'extension/postgis_topology.control', + ), + ); + const missingCompanion = companionFiles.find((file) => + file.endsWith('/postgis_topology.control'), + ); + await fs.rm(missingCompanion); + await assert.rejects( + () => + validateSwiftExtensionResourceArtifact({ + extension: expandedPostgis, + canonical: catalog.get('postgis'), + nativeRuntime: document.nativeRuntime, + }), + /postgis_topology\.control and canonical base installation SQL/u, + ); + await fs.writeFile(missingCompanion, '-- companion fixture\n'); + const foreignControl = path.join( + postgis.resourceRoot, + 'files/share/postgresql/extension/postgis_topology_extra.control', + ); + await fs.writeFile(foreignControl, '-- unowned lookalike\n'); + await assert.rejects( + () => + validateSwiftExtensionResourceArtifact({ + extension: expandedPostgis, + canonical: catalog.get('postgis'), + nativeRuntime: document.nativeRuntime, + }), + /undeclared extension SQL\/control file/u, + ); + await fs.rm(foreignControl); + await fs.writeFile(postgisManifest, postgisManifestText); await assert.rejects( () => validateSwiftExtensionResourceArtifact({ diff --git a/src/native/sdks/swift/tools/ios-carrier-manifest.mts b/src/native/sdks/swift/tools/ios-carrier-manifest.mts index 7ccaef4ba..752351e9f 100644 --- a/src/native/sdks/swift/tools/ios-carrier-manifest.mts +++ b/src/native/sdks/swift/tools/ios-carrier-manifest.mts @@ -646,6 +646,8 @@ function extensionCarrier( ); } const sqlName = portableIdentifier(manifest.sqlName, `${manifestPath}.sqlName`); + const sqlMembers = canonicalStringList(manifest.sqlMembers, `${manifestPath}.sqlMembers`); + canonicalStringList(manifest.nativeModuleStems, `${manifestPath}.nativeModuleStems`); if (typeof manifest.createsExtension !== 'boolean') { throw error(`${manifestPath} ${sqlName}.createsExtension must be boolean`); } @@ -844,6 +846,8 @@ function extensionCarrier( tag, sqlName, createsExtension: manifest.createsExtension, + sqlMembers, + nativeModuleStems: nativeModuleStem === null ? [] : [nativeModuleStem], dataFiles, dependencies, extensionSqlFileNames, diff --git a/src/native/sdks/swift/tools/ios-carrier-manifest.test.mts b/src/native/sdks/swift/tools/ios-carrier-manifest.test.mts index b9fb1cc61..52d0a1499 100644 --- a/src/native/sdks/swift/tools/ios-carrier-manifest.test.mts +++ b/src/native/sdks/swift/tools/ios-carrier-manifest.test.mts @@ -121,6 +121,8 @@ function writeManifest(root, product, body) { version: currentProductVersionSync(product, 'ios-carrier-manifest.test'), compatibility: compatibility(), createsExtension: true, + sqlMembers: [body.sqlName], + nativeModuleStems: body.nativeModuleStem === null ? [] : [body.nativeModuleStem], dataFiles: [], dependencies: [], extensionSqlFileNames: [], @@ -274,6 +276,8 @@ test('produces exact local and GitHub carrier envelopes ', async () => { ); const postgis = writeManifest(root, 'oliphaunt-extension-postgis', { sqlName: 'postgis', + sqlMembers: ['postgis', 'postgis_raster', 'postgis_tiger_geocoder', 'postgis_topology'], + nativeModuleStems: ['postgis-3', 'postgis_raster-3', 'postgis_topology-3'], nativeModuleStem: 'postgis-3', iosNativeDependencies: ['geos'], iosRegistration: { @@ -296,6 +300,15 @@ test('produces exact local and GitHub carrier envelopes ', async () => { extensionManifests: [postgis, pgtap], localUrls: true, }); + assert.deepEqual(local.extensions.find((row) => row.sqlName === 'postgis').sqlMembers, [ + 'postgis', + 'postgis_raster', + 'postgis_tiger_geocoder', + 'postgis_topology', + ]); + assert.deepEqual(local.extensions.find((row) => row.sqlName === 'postgis').nativeModuleStems, [ + 'postgis-3', + ]); assert.deepEqual( local.base.assets.map(({ role }) => role), ['base-xcframework', 'runtime-resources'], @@ -515,6 +528,8 @@ test('bundle carriers verify exact nested bytes ', async () => { extensions.push({ sqlName, createsExtension: true, + sqlMembers: [sqlName], + nativeModuleStems: [], dataFiles: [], dependencies: [], extensionSqlFileNames: [], diff --git a/src/native/sdks/swift/tools/render-extension-products.mts b/src/native/sdks/swift/tools/render-extension-products.mts index c9915e835..ffc2b9292 100755 --- a/src/native/sdks/swift/tools/render-extension-products.mts +++ b/src/native/sdks/swift/tools/render-extension-products.mts @@ -438,6 +438,8 @@ export function validateSelection( 'sharedPreloadLibraries', 'sqlName', 'version', + 'sqlMembers', + 'nativeModuleStems', ], `extensions[${index}]`, ); @@ -498,6 +500,11 @@ export function validateSelection( cFunction: nativeModuleStem === null ? null : `oliphaunt_extension_${cModuleStem}_descriptor`, cTarget: nativeModuleStem === null ? null : `COliphauntExtension${suffix}`, createsExtension: row.createsExtension, + sqlMembers: uniquePortableList(row.sqlMembers, `extensions[${index}].sqlMembers`), + nativeModuleStems: uniquePortableList( + row.nativeModuleStems, + `extensions[${index}].nativeModuleStems`, + ), dataFiles: frozenDataFiles(row.dataFiles, `extensions[${index}].dataFiles`), dependencies, extensionSqlFileNames: frozenSqlFileNames( diff --git a/src/native/sdks/swift/tools/swift-carrier-resolver.mts b/src/native/sdks/swift/tools/swift-carrier-resolver.mts index b0f3eb594..4b36785dc 100644 --- a/src/native/sdks/swift/tools/swift-carrier-resolver.mts +++ b/src/native/sdks/swift/tools/swift-carrier-resolver.mts @@ -463,6 +463,8 @@ function validateExtension(value, label, carriers) { 'sqlName', 'tag', 'version', + 'sqlMembers', + 'nativeModuleStems', ], label, ); @@ -483,6 +485,8 @@ function validateExtension(value, label, carriers) { if (dependencies.includes(sqlName)) fail(`${label}.dependencies must not include ${sqlName} itself`); return { + sqlMembers: canonicalIds(row.sqlMembers, `${label}.sqlMembers`), + nativeModuleStems: canonicalIds(row.nativeModuleStems, `${label}.nativeModuleStems`), assets: assetLocators(row.assets, `${label}.assets`, carriers), createsExtension: row.createsExtension, dataFiles: canonicalRelativeFiles(row.dataFiles, `${label}.dataFiles`), @@ -1029,6 +1033,8 @@ export async function resolveSwiftCarrierSelection({ version: row.version, sqlName: row.sqlName, createsExtension: row.createsExtension, + sqlMembers: row.sqlMembers, + nativeModuleStems: row.nativeModuleStems, dataFiles: row.dataFiles, dependencies: row.dependencies, extensionSqlFileNames: row.extensionSqlFileNames, diff --git a/src/native/sdks/swift/tools/swift-carrier-resolver.test.mts b/src/native/sdks/swift/tools/swift-carrier-resolver.test.mts index 4a8c4fa24..95dbc99d5 100755 --- a/src/native/sdks/swift/tools/swift-carrier-resolver.test.mts +++ b/src/native/sdks/swift/tools/swift-carrier-resolver.test.mts @@ -14,12 +14,14 @@ import { metadataZip, } from '../../../../../tools/packaging/testdata/zip-fixture.mts'; import { renderExtensionProducts } from './render-extension-products.mts'; +import { loadSwiftExtensionInventoryCatalog } from './extension-resource-inventory.mts'; import { extractVerifiedZipArchive, resolveSwiftCarrierSelection, } from './swift-carrier-resolver.mts'; const sdk = path.resolve(import.meta.dirname, '..'); +const canonicalCatalog = await loadSwiftExtensionInventoryCatalog(); const root = path.resolve( process.argv[2] ?? path.join(sdk, '../../../../target/liboliphaunt-sdk-check/swift-carrier-test'), ); @@ -295,6 +297,7 @@ async function productionExtensionResource(sqlName, stem, nativeDependencies) { } async function extension(sqlName, stem, dependencies = [], nativeDependencies = []) { + dependencies = canonicalCatalog.get(sqlName).dependencies; const resource = await productionExtensionResource(sqlName, stem, nativeDependencies); const runtime = path.join(root, 'archives', `${sqlName}-runtime.tar.gz`); await fs.mkdir(path.dirname(runtime), { recursive: true }); @@ -319,7 +322,7 @@ async function extension(sqlName, stem, dependencies = [], nativeDependencies = } const prefix = stem === null ? null : `oliphaunt_static_${stem.replaceAll(/[^A-Za-z0-9_]/gu, '_')}`; - const product = new Set(['cube', 'earthdistance', 'pg_trgm']).has(sqlName) + const product = new Set(['cube', 'earthdistance', 'fuzzystrmatch', 'pg_trgm']).has(sqlName) ? 'oliphaunt-extension-contrib-pg18' : `oliphaunt-extension-${sqlName.replaceAll('_', '-')}`; const releaseProduct = @@ -337,6 +340,8 @@ async function extension(sqlName, stem, dependencies = [], nativeDependencies = tag: `${releaseProduct}-v${version}`, sqlName, createsExtension: true, + sqlMembers: canonicalCatalog.get(sqlName).sqlMembers, + nativeModuleStems: stem === null ? [] : [stem], dataFiles: content.dataFiles, dependencies, extensionSqlFileNames: content.extensionSqlFileNames, @@ -484,6 +489,7 @@ async function main() { await fs.mkdir(root, { recursive: true }); const carrierized = carrierize([ await extension('cube', 'cube'), + await extension('fuzzystrmatch', 'fuzzystrmatch'), await extension('earthdistance', 'earthdistance', ['cube']), await extension('pgtap', null), await extension( @@ -532,6 +538,7 @@ async function main() { 'artifact-product': 'oliphaunt-extension-pgtap', 'release-product': 'oliphaunt-extension-pgtap', 'creates-extension': false, + 'sql-members': ['pgtap'], 'native-module-stem': 'future_pgtap', 'selected-extension-dependencies': ['cube'], 'runtime-share-data-files': ['future/data.dat'], @@ -609,6 +616,19 @@ async function main() { '0.1.0', ]; await render([...common, '--output-dir', output]); + const renderedFiles = await fs.readdir(output, { recursive: true }); + for (const member of canonicalCatalog.get('postgis').sqlMembers) { + assert.ok( + renderedFiles.some((file) => file.endsWith(`/extension/${member}.control`)), + `clean Swift package must contain ${member}.control`, + ); + assert.ok( + renderedFiles.some( + (file) => file.includes(`/extension/${member}--`) && file.endsWith('.sql'), + ), + `clean Swift package must contain ${member} install SQL`, + ); + } assert.deepEqual( (await fs.readdir(output, { recursive: true })).filter((entry) => entry.endsWith('.a')), [], @@ -623,7 +643,7 @@ async function main() { }); assert.deepEqual( products.selected.map(({ sqlName }) => sqlName), - ['cube', 'earthdistance', 'pgtap', 'postgis'], + ['cube', 'earthdistance', 'fuzzystrmatch', 'pgtap', 'postgis'], ); assert.equal(products.selected.find(({ sqlName }) => sqlName === 'pgtap').version, '1.1.0'); const selectedPostgis = products.selected.find(({ sqlName }) => sqlName === 'postgis'); @@ -1310,7 +1330,7 @@ async function main() { 'mismatched-creates-extension', mismatchedCreatesExtension, 'pgtap', - /manifest createsExtension must be "no"/u, + /sqlMembers must contain the primary extension exactly when createsExtension is true/u, ); const duplicateName = structuredClone(manifest); diff --git a/src/native/sdks/ts/src/__tests__/asset-resolver.test.ts b/src/native/sdks/ts/src/__tests__/asset-resolver.test.ts index d7c315165..73b376d55 100644 --- a/src/native/sdks/ts/src/__tests__/asset-resolver.test.ts +++ b/src/native/sdks/ts/src/__tests__/asset-resolver.test.ts @@ -36,6 +36,8 @@ type FixtureExtensionContract = { sqlName: string; createsExtension: boolean; nativeModuleStem: string | null; + sqlMembers: string[]; + nativeModuleStems: string[]; dependencies: string[]; dataFiles: string[]; extensionSqlFileNames: string[]; @@ -70,6 +72,8 @@ function fixtureExtensionContract( sqlName: extension.sqlName, createsExtension: extension.createsExtension, nativeModuleStem: extension.nativeModuleStem, + sqlMembers: [...extension.sqlMembers], + nativeModuleStems: [...extension.nativeModuleStems], dependencies: [...extension.selectedExtensionDependencies], dataFiles: [...extension.runtimeShareDataFiles], extensionSqlFileNames: [...extension.extensionSqlFileNames], @@ -398,7 +402,7 @@ async function nodeExtensionMaterializationAcceptsBuiltInPostgresDependency(): P nodeAssets.materializeExtensionInstall({ libraryPath, runtimeDirectory: installRuntime }, [ 'pgtap', ]), - /member pgtap is incompatible with the SDK dependency contract/, + /member pgtap is incompatible with the SDK extension contract/, ); } finally { if (installedRuntime !== undefined) { @@ -653,6 +657,10 @@ async function nodeExtensionMaterializationCopiesPackagePayloads(): Promise @@ -792,7 +800,7 @@ async function nodeExtensionMaterializationCopiesPackagePayloads(): Promise { + const target = liboliphauntPackageTarget(platform(), arch()); + const { liboliphauntVersion } = await readTypeScriptPackageVersions(); + const extension = GENERATED_EXTENSION_METADATA.find((row) => row.sqlName === 'postgis')!; + const basePackageName = extension.npmPackage; + const targetPackageName = `${basePackageName}-${target.id}`; + const version = '9.9.7'; + await writeFixturePackage(basePackageName, createdPackageRoots, { + name: basePackageName, + version, + oliphaunt: { + product: extension.artifactProduct, + kind: 'exact-extension', + sqlName: 'postgis', + targetPackageNames: { [target.id]: targetPackageName }, + }, + }); + const targetRoot = await writeFixturePackage(targetPackageName, createdPackageRoots, { + name: targetPackageName, + version, + oliphaunt: { + product: extension.artifactProduct, + kind: 'exact-extension-target', + sqlName: 'postgis', + target: target.id, + liboliphauntVersion, + extensionContract: 'extension-contract.json', + runtimeRelativePath: 'runtime', + moduleRelativePath: 'runtime/lib/modules', + }, + }); + const runtime = join(targetRoot, 'runtime'); + const sqlRoot = join(runtime, 'share/postgresql/extension'); + const suffix = nativeModuleSuffixForTarget(target.id); + const contractPath = join(targetRoot, 'extension-contract.json'); + const roots = new Set(); + const writeContract = async (contract: Partial) => + writeFile( + contractPath, + JSON.stringify( + fixtureExtensionContractManifest(extension.artifactProduct, version, target.id, [ + contract as FixtureExtensionContract, + ]), + ), + ); + const complete = fixtureExtensionContract(extension); + await mkdir(sqlRoot, { recursive: true }); + for (const file of complete.dataFiles) { + const destination = join(runtime, 'share/postgresql', file); + await mkdir(dirname(destination), { recursive: true }); + await writeFile(destination, `data:${file}`); + } + for (const profile of ['postgresql', 'modules']) { + await mkdir(join(runtime, 'lib', profile), { recursive: true }); + } + const materialize = async () => { + const result = await nodeAssets.materializeExtensionInstall(install, ['postgis']); + roots.add(dirname(result.runtimeDirectory!)); + return result; + }; + try { + for (const omitted of [ + ['sqlMembers'], + ['nativeModuleStems'], + ['sqlMembers', 'nativeModuleStems'], + ] as const) { + const incomplete: Partial = { ...complete }; + for (const field of omitted) delete incomplete[field]; + await writeContract(incomplete); + await assert.rejects(materialize, /fields must be exactly/); + } + for (const incomplete of [ + { ...complete, dependencies: [] }, + { ...complete, sqlMembers: ['postgis'] }, + { ...complete, nativeModuleStems: ['postgis-3'] }, + ]) { + await writeContract(incomplete); + await assert.rejects(materialize, /incompatible with the SDK extension contract/); + } + await writeContract(complete); + for (const member of extension.sqlMembers) { + await writeFile( + join(sqlRoot, `${member}.control`), + member === 'postgis_tiger_geocoder' + ? "default_version='3.6.4'\nrequires='postgis,fuzzystrmatch'\n" + : "default_version='3.6.4'\n", + ); + await writeFile(join(sqlRoot, `${member}--3.6.4.sql`), 'install'); + } + for (const stem of extension.nativeModuleStems) { + for (const profile of ['postgresql', 'modules']) { + await writeFile(join(runtime, 'lib', profile, `${stem}${suffix}`), `complete:${stem}`); + } + } + for (const [file, expected] of [ + [join(sqlRoot, 'postgis_topology.control'), /postgis_topology\.control and canonical/], + [join(sqlRoot, 'postgis_topology--3.6.4.sql'), /postgis_topology\.control and canonical/], + [ + join(runtime, 'lib/postgresql', `postgis_topology-3${suffix}`), + /missing declared native module/, + ], + [ + join(runtime, 'lib/modules', `postgis_topology-3${suffix}`), + /missing declared embedded native module/, + ], + ] as const) { + const bytes = await readFile(file); + await rm(file); + await assert.rejects(materialize, expected); + await writeFile(file, bytes); + } + const tigerControl = join(sqlRoot, 'postgis_tiger_geocoder.control'); + const controlBytes = await readFile(tigerControl); + await writeFile(tigerControl, "default_version='3.6.4'\nrequires='cube'\n"); + await assert.rejects(materialize, /requires undeclared dependency cube/); + await writeFile(tigerControl, controlBytes); + const installed = await materialize(); + assert.equal( + await readFile(join(installed.moduleDirectory!, `postgis_topology-3${suffix}`), 'utf8'), + 'complete:postgis_topology-3', + ); + assert.equal( + await readFile(join(installed.moduleDirectory!, `fuzzystrmatch${suffix}`), 'utf8'), + 'embedded-module:fuzzystrmatch', + ); + assert.equal( + await readFile( + join(installed.runtimeDirectory!, 'share/postgresql/extension/postgis_topology--3.6.4.sql'), + 'utf8', + ), + 'install', + ); + await writeFile( + join(runtime, 'lib/modules', `postgis_topology-3${suffix}`), + 'updated topology', + ); + const updated = await materialize(); + assert.notEqual(updated.runtimeDirectory, installed.runtimeDirectory); + assert.equal( + await readFile(join(updated.moduleDirectory!, `postgis_topology-3${suffix}`), 'utf8'), + 'updated topology', + ); + } finally { + for (const root of roots) await rm(root, { recursive: true, force: true }); + } +} + async function writePreparedHstoreRuntime(runtimeDirectory: string, target: string): Promise { const moduleSuffix = nativeModuleSuffixForTarget(target); await mkdir(join(runtimeDirectory, 'share/postgresql/extension'), { diff --git a/src/native/sdks/ts/src/generated/extensions.ts b/src/native/sdks/ts/src/generated/extensions.ts index 6192662a7..24342e949 100644 --- a/src/native/sdks/ts/src/generated/extensions.ts +++ b/src/native/sdks/ts/src/generated/extensions.ts @@ -15,6 +15,8 @@ export type GeneratedExtensionMetadata = { readonly runtimeBound: boolean; readonly createsExtension: boolean; readonly nativeModuleStem: string | null; + readonly nativeModuleStems: readonly string[]; + readonly sqlMembers: readonly string[]; readonly dependencies: readonly string[]; readonly selectedExtensionDependencies: readonly string[]; readonly sharedPreloadLibraries: readonly string[]; @@ -25,7 +27,7 @@ export type GeneratedExtensionMetadata = { readonly sourceKind: string; }; -export const GENERATED_EXTENSION_METADATA_SHA256 = "26ce911f641272aa18249e7c7d044dfa5ea7061d07119bb275544e164bb9249a" as const; +export const GENERATED_EXTENSION_METADATA_SHA256 = "dbe1ab284693ef923cb12e35e1c75bcc7fc1ed42af1fecf9fe9fee0aaf0ef8d0" as const; export const GENERATED_EXTENSION_METADATA = [ { @@ -41,6 +43,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "amcheck", + "nativeModuleStems": [ + "amcheck" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -49,6 +54,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "amcheck" + ], "sqlName": "amcheck" }, { @@ -64,6 +72,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "auto_explain", + "nativeModuleStems": [ + "auto_explain" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -72,6 +83,7 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [], "sqlName": "auto_explain" }, { @@ -87,6 +99,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "bloom", + "nativeModuleStems": [ + "bloom" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -95,6 +110,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "bloom" + ], "sqlName": "bloom" }, { @@ -110,6 +128,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "btree_gin", + "nativeModuleStems": [ + "btree_gin" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -118,6 +139,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "btree_gin" + ], "sqlName": "btree_gin" }, { @@ -133,6 +157,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "btree_gist", + "nativeModuleStems": [ + "btree_gist" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -141,6 +168,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "btree_gist" + ], "sqlName": "btree_gist" }, { @@ -156,6 +186,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "citext", + "nativeModuleStems": [ + "citext" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -164,6 +197,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "citext" + ], "sqlName": "citext" }, { @@ -179,6 +215,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "cube", + "nativeModuleStems": [ + "cube" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -187,6 +226,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "cube" + ], "sqlName": "cube" }, { @@ -202,6 +244,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "dict_int", + "nativeModuleStems": [ + "dict_int" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -210,6 +255,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "dict_int" + ], "sqlName": "dict_int" }, { @@ -227,6 +275,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "dict_xsyn", + "nativeModuleStems": [ + "dict_xsyn" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -237,6 +288,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "dict_xsyn" + ], "sqlName": "dict_xsyn" }, { @@ -254,6 +308,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "earthdistance", + "nativeModuleStems": [ + "earthdistance" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -264,6 +321,9 @@ export const GENERATED_EXTENSION_METADATA = [ ], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "earthdistance" + ], "sqlName": "earthdistance" }, { @@ -279,6 +339,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "file_fdw", + "nativeModuleStems": [ + "file_fdw" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -287,6 +350,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "file_fdw" + ], "sqlName": "file_fdw" }, { @@ -302,6 +368,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "fuzzystrmatch", + "nativeModuleStems": [ + "fuzzystrmatch" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -310,6 +379,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "fuzzystrmatch" + ], "sqlName": "fuzzystrmatch" }, { @@ -325,6 +397,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "hstore", + "nativeModuleStems": [ + "hstore" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -333,6 +408,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "hstore" + ], "sqlName": "hstore" }, { @@ -348,6 +426,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "_int", + "nativeModuleStems": [ + "_int" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -356,6 +437,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "intarray" + ], "sqlName": "intarray" }, { @@ -371,6 +455,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "isn", + "nativeModuleStems": [ + "isn" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -379,6 +466,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "isn" + ], "sqlName": "isn" }, { @@ -394,6 +484,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "lo", + "nativeModuleStems": [ + "lo" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -402,6 +495,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "lo" + ], "sqlName": "lo" }, { @@ -417,6 +513,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "ltree", + "nativeModuleStems": [ + "ltree" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -425,6 +524,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "ltree" + ], "sqlName": "ltree" }, { @@ -440,6 +542,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pageinspect", + "nativeModuleStems": [ + "pageinspect" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -448,6 +553,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pageinspect" + ], "sqlName": "pageinspect" }, { @@ -463,6 +571,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_buffercache", + "nativeModuleStems": [ + "pg_buffercache" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -471,6 +582,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_buffercache" + ], "sqlName": "pg_buffercache" }, { @@ -486,6 +600,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_freespacemap", + "nativeModuleStems": [ + "pg_freespacemap" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -494,6 +611,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_freespacemap" + ], "sqlName": "pg_freespacemap" }, { @@ -509,6 +629,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-pg-hashids", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_hashids", + "nativeModuleStems": [ + "pg_hashids" + ], "npmPackage": "@oliphaunt/extension-pg-hashids", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-pg-hashids", @@ -517,6 +640,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "pg_hashids" + ], "sqlName": "pg_hashids" }, { @@ -532,6 +658,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-pg-ivm", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_ivm", + "nativeModuleStems": [ + "pg_ivm" + ], "npmPackage": "@oliphaunt/extension-pg-ivm", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-pg-ivm", @@ -540,6 +669,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "pg_ivm" + ], "sqlName": "pg_ivm" }, { @@ -555,6 +687,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_surgery", + "nativeModuleStems": [ + "pg_surgery" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -563,6 +698,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_surgery" + ], "sqlName": "pg_surgery" }, { @@ -578,6 +716,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-pg-textsearch", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_textsearch", + "nativeModuleStems": [ + "pg_textsearch" + ], "npmPackage": "@oliphaunt/extension-pg-textsearch", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-pg-textsearch", @@ -588,6 +729,9 @@ export const GENERATED_EXTENSION_METADATA = [ "pg_textsearch" ], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "pg_textsearch" + ], "sqlName": "pg_textsearch" }, { @@ -603,6 +747,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_trgm", + "nativeModuleStems": [ + "pg_trgm" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -611,6 +758,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_trgm" + ], "sqlName": "pg_trgm" }, { @@ -626,6 +776,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-pg-uuidv7", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_uuidv7", + "nativeModuleStems": [ + "pg_uuidv7" + ], "npmPackage": "@oliphaunt/extension-pg-uuidv7", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-pg-uuidv7", @@ -634,6 +787,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "pg_uuidv7" + ], "sqlName": "pg_uuidv7" }, { @@ -649,6 +805,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_visibility", + "nativeModuleStems": [ + "pg_visibility" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -657,6 +816,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_visibility" + ], "sqlName": "pg_visibility" }, { @@ -672,6 +834,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pg_walinspect", + "nativeModuleStems": [ + "pg_walinspect" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -680,6 +845,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pg_walinspect" + ], "sqlName": "pg_walinspect" }, { @@ -695,6 +863,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "pgcrypto", + "nativeModuleStems": [ + "pgcrypto" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -703,6 +874,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "pgcrypto" + ], "sqlName": "pgcrypto" }, { @@ -725,6 +899,7 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-pgtap", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": null, + "nativeModuleStems": [], "npmPackage": "@oliphaunt/extension-pgtap", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-pgtap", @@ -733,6 +908,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "pgtap" + ], "sqlName": "pgtap" }, { @@ -776,7 +954,9 @@ export const GENERATED_EXTENSION_METADATA = [ "share/postgresql/gdal/tms_NZTM2000.json", "share/postgresql/proj/proj.db" ], - "dependencies": [], + "dependencies": [ + "fuzzystrmatch" + ], "displayName": "PostGIS", "extensionSqlFileNames": [ "uninstall_postgis.sql" @@ -785,12 +965,19 @@ export const GENERATED_EXTENSION_METADATA = [ "postgis_comments", "postgis_proc_set_search_path", "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology", "rtpostgis" ], "id": "postgis", "mavenArtifact": "oliphaunt-extension-postgis", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "postgis-3", + "nativeModuleStems": [ + "postgis-3", + "postgis_raster-3", + "postgis_topology-3" + ], "npmPackage": "@oliphaunt/extension-postgis", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-postgis", @@ -832,9 +1019,17 @@ export const GENERATED_EXTENSION_METADATA = [ "gdal/tms_NZTM2000.json", "proj/proj.db" ], - "selectedExtensionDependencies": [], + "selectedExtensionDependencies": [ + "fuzzystrmatch" + ], "sharedPreloadLibraries": [], "sourceKind": "postgis", + "sqlMembers": [ + "postgis", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology" + ], "sqlName": "postgis" }, { @@ -850,6 +1045,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "seg", + "nativeModuleStems": [ + "seg" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -858,6 +1056,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "seg" + ], "sqlName": "seg" }, { @@ -873,6 +1074,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "tablefunc", + "nativeModuleStems": [ + "tablefunc" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -881,6 +1085,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tablefunc" + ], "sqlName": "tablefunc" }, { @@ -896,6 +1103,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "tcn", + "nativeModuleStems": [ + "tcn" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -904,6 +1114,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tcn" + ], "sqlName": "tcn" }, { @@ -919,6 +1132,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "tsm_system_rows", + "nativeModuleStems": [ + "tsm_system_rows" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -927,6 +1143,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tsm_system_rows" + ], "sqlName": "tsm_system_rows" }, { @@ -942,6 +1161,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "tsm_system_time", + "nativeModuleStems": [ + "tsm_system_time" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -950,6 +1172,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tsm_system_time" + ], "sqlName": "tsm_system_time" }, { @@ -967,6 +1192,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "unaccent", + "nativeModuleStems": [ + "unaccent" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -977,6 +1205,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "unaccent" + ], "sqlName": "unaccent" }, { @@ -992,6 +1223,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-contrib-pg18", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "uuid-ossp", + "nativeModuleStems": [ + "uuid-ossp" + ], "npmPackage": "@oliphaunt/extension-contrib-pg18", "postgresMajor": 18, "releaseProduct": "liboliphaunt-native", @@ -1000,6 +1234,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "uuid-ossp" + ], "sqlName": "uuid-ossp" }, { @@ -1015,6 +1252,9 @@ export const GENERATED_EXTENSION_METADATA = [ "mavenArtifact": "oliphaunt-extension-vector", "mavenGroup": "dev.oliphaunt.extensions", "nativeModuleStem": "vector", + "nativeModuleStems": [ + "vector" + ], "npmPackage": "@oliphaunt/extension-vector", "postgresMajor": 18, "releaseProduct": "oliphaunt-extension-vector", @@ -1023,6 +1263,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "oliphaunt-other-extension", + "sqlMembers": [ + "vector" + ], "sqlName": "vector" } ] as const satisfies readonly GeneratedExtensionMetadata[]; diff --git a/src/native/sdks/ts/src/native/assets.ts b/src/native/sdks/ts/src/native/assets.ts index c90cef39a..a2cb5ef2c 100644 --- a/src/native/sdks/ts/src/native/assets.ts +++ b/src/native/sdks/ts/src/native/assets.ts @@ -100,6 +100,8 @@ type NpmExtensionMemberContract = { sqlName: string; createsExtension: boolean; nativeModuleStem: string | null; + nativeModuleStems: string[]; + sqlMembers: string[]; dependencies: string[]; dataFiles: string[]; extensionSqlFileNames: string[]; @@ -142,7 +144,9 @@ const NPM_EXTENSION_CONTRACT_MEMBER_FIELDS = [ 'extensionSqlFilePrefixes', 'licenseFiles', 'nativeModuleStem', + 'nativeModuleStems', 'sharedPreloadLibraries', + 'sqlMembers', 'sqlName', ] as const; @@ -236,7 +240,6 @@ export async function materializeExtensionInstall( ); } const installRuntimeDirectory = install.runtimeDirectory; - const versions = await packageVersions(); const target = liboliphauntPackageTarget(platform(), arch()); const packages = await Promise.all( @@ -257,6 +260,7 @@ export async function materializeExtensionInstall( name: entry.name, version: entry.version, contract: entry.contract, + payloadDigest: entry.payloadDigest, runtimeDirectories: entry.runtimeDirectories, moduleDirectories: entry.moduleDirectories, })), @@ -275,6 +279,7 @@ export async function materializeExtensionInstall( version: entry.version, sqlName: entry.sqlName, contract: entry.contract, + payloadDigest: entry.payloadDigest, })), }, null, @@ -341,6 +346,7 @@ type ResolvedExtensionPackage = { version: string; sqlName: string; contract: NpmExtensionMemberContract; + payloadDigest: string; runtimeDirectories: string[]; moduleDirectories: string[]; }; @@ -474,6 +480,7 @@ async function resolveExtensionPackage( version: packageJson.version, sqlName, contract: selectedContract, + payloadDigest: await extensionPayloadDigest(runtimeDirectories), runtimeDirectories, moduleDirectories, }; @@ -621,6 +628,30 @@ function parseExtensionMemberContract(value: unknown, label: string): NpmExtensi ) { throw new Error(`${label}.nativeModuleStem must be null or a portable identifier`); } + const sqlMembers = parseExtensionContractStringList(row.sqlMembers, 'sqlMembers', label); + const nativeModuleStems = parseExtensionContractStringList( + row.nativeModuleStems, + 'nativeModuleStems', + label, + ); + for (const [field, values] of [ + ['sqlMembers', sqlMembers], + ['nativeModuleStems', nativeModuleStems], + ] as const) { + if (values.some((value) => !/^[A-Za-z0-9._-]{1,128}$/u.test(value))) { + throw new Error(`${label}.${field} contains an invalid identifier`); + } + } + if (row.createsExtension ? !sqlMembers.includes(row.sqlName) : sqlMembers.length !== 0) { + throw new Error(`${label}.sqlMembers must preserve its primary extension identity`); + } + if ( + row.nativeModuleStem === null + ? nativeModuleStems.length !== 0 + : !nativeModuleStems.includes(row.nativeModuleStem as string) + ) { + throw new Error(`${label}.nativeModuleStems must preserve its primary module identity`); + } const dependencies = parseExtensionContractStringList(row.dependencies, 'dependencies', label); if ( dependencies.includes(row.sqlName) || @@ -663,6 +694,8 @@ function parseExtensionMemberContract(value: unknown, label: string): NpmExtensi sqlName: row.sqlName, createsExtension: row.createsExtension, nativeModuleStem: row.nativeModuleStem as string | null, + nativeModuleStems, + sqlMembers, dependencies, dataFiles, extensionSqlFileNames, @@ -742,10 +775,15 @@ async function loadExtensionPackageContract(config: { if ( current === undefined || current.artifactProduct !== config.extension.artifactProduct || - JSON.stringify(current.selectedExtensionDependencies) !== JSON.stringify(member.dependencies) + member.createsExtension !== current.createsExtension || + member.nativeModuleStem !== current.nativeModuleStem || + JSON.stringify(member.dependencies) !== + JSON.stringify(current.selectedExtensionDependencies) || + JSON.stringify(member.sqlMembers) !== JSON.stringify(current.sqlMembers) || + JSON.stringify(member.nativeModuleStems) !== JSON.stringify(current.nativeModuleStems) ) { throw new Error( - `${config.packageName} extension contract member ${member.sqlName} is incompatible with the SDK dependency contract`, + `${config.packageName} extension contract member ${member.sqlName} is incompatible with the SDK extension contract`, ); } } @@ -1072,11 +1110,12 @@ function extensionRuntimeSqlFileOwned( fileName: string, ): boolean { return ( - (extension.createsExtension && fileName === `${extension.sqlName}.control`) || - (extension.createsExtension && fileName === `${extension.sqlName}.sql`) || - (extension.createsExtension && - fileName.startsWith(`${extension.sqlName}--`) && - fileName.endsWith('.sql')) || + extension.sqlMembers.some( + (sqlName) => + fileName === `${sqlName}.control` || + fileName === `${sqlName}.sql` || + (fileName.startsWith(`${sqlName}--`) && fileName.endsWith('.sql')), + ) || extension.extensionSqlFileNames.includes(fileName) || (fileName.endsWith('.sql') && extension.extensionSqlFilePrefixes.some((prefix) => fileName.startsWith(prefix))) @@ -1144,6 +1183,18 @@ async function exactRuntimeLeafPaths(root: string, source: string): Promise { + const digest = createHash('sha256'); + for (const root of [...roots].sort(compareText)) { + const files = await exactRuntimeLeafPaths(root, 'extension cache inputs'); + for (const file of files.sort(compareText)) { + const identity = await fileSha256AndBytes(join(root, file)); + digest.update(JSON.stringify([file, identity.sha256, identity.bytes])); + } + } + return digest.digest('hex'); +} + async function requireExactExtensionRuntimeInventory(config: { contract: NpmExtensionMemberContract; runtimeDirectory: string; @@ -1153,16 +1204,14 @@ async function requireExactExtensionRuntimeInventory(config: { const files = await exactRuntimeLeafPaths(config.runtimeDirectory, config.source); const dataFiles = new Set(config.contract.dataFiles.map((file) => `share/postgresql/${file}`)); const licenseFiles = new Map(config.contract.licenseFiles.map((file) => [file.path, file])); - const moduleFile = - config.contract.nativeModuleStem === null - ? undefined - : `lib/postgresql/${config.contract.nativeModuleStem}${nativeModuleSuffixForTarget(config.target)}`; - const embeddedModuleFile = - config.contract.nativeModuleStem === null - ? undefined - : `lib/modules/${config.contract.nativeModuleStem}${nativeModuleSuffixForTarget(config.target)}`; - let hasControl = false; - let hasSql = false; + const moduleFiles = config.contract.nativeModuleStems.map( + (stem) => `lib/postgresql/${stem}${nativeModuleSuffixForTarget(config.target)}`, + ); + const embeddedModuleFiles = config.contract.nativeModuleStems.map( + (stem) => `lib/modules/${stem}${nativeModuleSuffixForTarget(config.target)}`, + ); + const controls = new Set(); + const installs = new Set(); for (const file of files) { const extensionPrefix = 'share/postgresql/extension/'; if (file.startsWith(extensionPrefix)) { @@ -1170,15 +1219,37 @@ async function requireExactExtensionRuntimeInventory(config: { if (fileName.includes('/') || !extensionRuntimeSqlFileOwned(config.contract, fileName)) { throw new Error(`${config.source} contains undeclared extension SQL/control file ${file}`); } - if (fileName === `${config.contract.sqlName}.control`) hasControl = true; - if (isCanonicalExtensionInstallSql(fileName, config.contract.sqlName)) hasSql = true; + for (const sqlName of config.contract.sqlMembers) { + if (fileName === `${sqlName}.control`) { + controls.add(sqlName); + const control = await readFile(join(config.runtimeDirectory, file), 'utf8'); + const requires = + /^\s*requires\s*=\s*'([^']*)'/mu + .exec(control)?.[1] + ?.split(',') + .map((value) => value.trim()) + .filter(Boolean) ?? []; + for (const dependency of requires) { + if ( + dependency !== 'plpgsql' && + !config.contract.sqlMembers.includes(dependency) && + !config.contract.dependencies.includes(dependency) + ) { + throw new Error( + `${config.source} ${sqlName}.control requires undeclared dependency ${dependency}`, + ); + } + } + } + if (isCanonicalExtensionInstallSql(fileName, sqlName)) installs.add(sqlName); + } continue; } if ( dataFiles.has(file) || licenseFiles.has(file) || - file === moduleFile || - file === embeddedModuleFile + moduleFiles.includes(file) || + embeddedModuleFiles.includes(file) ) { continue; } @@ -1223,18 +1294,23 @@ async function requireExactExtensionRuntimeInventory(config: { ); } } - if (moduleFile !== undefined && !files.includes(moduleFile)) { - throw new Error(`${config.source} is missing declared native module ${moduleFile}`); + for (const moduleFile of moduleFiles) { + if (!files.includes(moduleFile)) + throw new Error(`${config.source} is missing declared native module ${moduleFile}`); } - if (embeddedModuleFile !== undefined && !files.includes(embeddedModuleFile)) { - throw new Error( - `${config.source} is missing declared embedded native module ${embeddedModuleFile}`, - ); + for (const embeddedModuleFile of embeddedModuleFiles) { + if (!files.includes(embeddedModuleFile)) { + throw new Error( + `${config.source} is missing declared embedded native module ${embeddedModuleFile}`, + ); + } } - if (config.contract.createsExtension && (!hasControl || !hasSql)) { - throw new Error( - `${config.source} must contain ${config.contract.sqlName}.control and canonical base installation SQL`, - ); + for (const sqlName of config.contract.sqlMembers) { + if (!controls.has(sqlName) || !installs.has(sqlName)) { + throw new Error( + `${config.source} must contain ${sqlName}.control and canonical base installation SQL`, + ); + } } } diff --git a/src/test-fixtures/extensions/postgis.sql b/src/test-fixtures/extensions/postgis.sql index 289ff1463..4d0a92dfa 100644 --- a/src/test-fixtures/extensions/postgis.sql +++ b/src/test-fixtures/extensions/postgis.sql @@ -1,5 +1,19 @@ CREATE EXTENSION IF NOT EXISTS postgis_raster; -- oliphaunt-statement +CREATE EXTENSION IF NOT EXISTS postgis_topology; +-- oliphaunt-statement +CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; +-- oliphaunt-statement +CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; +-- oliphaunt-statement +DO $$ BEGIN + IF EXISTS (SELECT 1 FROM topology.topology WHERE name = 'oliphaunt_postgis_topology') THEN + PERFORM topology.DropTopology('oliphaunt_postgis_topology'); + END IF; + PERFORM topology.CreateTopology('oliphaunt_postgis_topology', 4326); + PERFORM topology.ST_AddIsoNode('oliphaunt_postgis_topology', NULL, ST_SetSRID(ST_MakePoint(1, 1), 4326)); +END $$; +-- oliphaunt-statement DROP TABLE IF EXISTS oliphaunt_postgis_points; -- oliphaunt-statement CREATE TABLE oliphaunt_postgis_points(id int PRIMARY KEY, geom geometry(Point, 4326)); @@ -21,7 +35,21 @@ DECLARE gdal_drivers int; raster_roundtrip raster; raster_value float8; + topology_nodes int; + normalized tiger.norm_addy; BEGIN + SELECT count(*) INTO topology_nodes FROM oliphaunt_postgis_topology.node + WHERE ST_Equals(geom, ST_SetSRID(ST_MakePoint(1, 1), 4326)); + IF topology_nodes <> 1 OR EXISTS (SELECT 1 FROM topology.ValidateTopology('oliphaunt_postgis_topology')) THEN + RAISE EXCEPTION 'postgis topology persistence or validation failed: % nodes', topology_nodes; + END IF; + normalized := tiger.normalize_address('1 Devonshire Place, Boston, MA 02109'); + IF normalized.address IS DISTINCT FROM 1 OR normalized.streetname IS DISTINCT FROM 'Devonshire' + OR normalized.streettypeabbrev IS DISTINCT FROM 'Pl' OR normalized.location IS DISTINCT FROM 'Boston' + OR normalized.stateabbrev IS DISTINCT FROM 'MA' OR normalized.zip IS DISTINCT FROM '02109' + OR normalized.parsed IS DISTINCT FROM true THEN + RAISE EXCEPTION 'postgis tiger address normalization failed: %', normalized; + END IF; SET LOCAL postgis.gdal_enabled_drivers = 'GTiff'; SELECT ST_Distance(ST_GeomFromText('POINT(0 0)'), ST_GeomFromText('POINT(3 4)')) INTO distance; IF distance <> 5 THEN diff --git a/src/wasix/sdks/rust/src/oliphaunt/generated_extensions.rs b/src/wasix/sdks/rust/src/oliphaunt/generated_extensions.rs index dd1e2bc2e..daea34161 100644 --- a/src/wasix/sdks/rust/src/oliphaunt/generated_extensions.rs +++ b/src/wasix/sdks/rust/src/oliphaunt/generated_extensions.rs @@ -313,10 +313,14 @@ const DEFINITION_POSTGIS: Extension = Extension { runtime_path: "lib/postgresql/postgis_raster-3.so", aot_name: Some("extension:postgis:postgis_raster"), }, + super::ExtensionNativeModule { + runtime_path: "lib/postgresql/postgis_topology-3.so", + aot_name: Some("extension:postgis:postgis_topology"), + }, ], native_module_file: Some("postgis-3.so"), aot_name: Some("extension:postgis"), - dependencies: &[], + dependencies: &["fuzzystrmatch"], startup_config: &[], package: None, }; diff --git a/tools/release/publication-lock.mts b/tools/release/publication-lock.mts index acc164ff6..33abb9500 100644 --- a/tools/release/publication-lock.mts +++ b/tools/release/publication-lock.mts @@ -24,12 +24,11 @@ import { validateSelectionNeutralSwiftSourceCarrier, validateSwiftSourceReleaseContract, } from '../../src/native/sdks/swift/tools/swift-source-carrier-contract.mts'; -import { releaseJavaScript } from '../packaging/emit-javascript.mts'; import { assertWasixTypescriptManifest, assertWasixTypescriptNativeCarrier, } from '../../src/wasix/sdks/ts/tools/wasix-typescript-package.mts'; -import { requireMatchingWasixRuntime } from './compatibility-version-policy.mts'; +import { releaseJavaScript } from '../packaging/emit-javascript.mts'; import { parseMavenArtifactManifest } from '../packaging/maven-artifact-manifest.mts'; import { validateMavenCentralPublication } from '../packaging/maven-central-contract.mts'; import { validateNpmTrustedPublishingManifest } from '../packaging/npm-trusted-publishing.mts'; @@ -37,6 +36,7 @@ import { readFileOnlyTarGzipEntries, readPortableArchiveEntries, } from '../packaging/portable-archive.mts'; +import { requireMatchingWasixRuntime } from './compatibility-version-policy.mts'; import { loadPublicationCatalog, PUBLICATION_CATALOG_SCHEMA, @@ -919,6 +919,10 @@ function validateExtensionManifestRow(product, manifestPath, member) { if ( member.nativeModuleStem !== iosContract.nativeModuleStem || member.createsExtension !== (iosContract.metadata['creates-extension'] !== false) || + stableJson(sortedStrings(member.sqlMembers)) !== + stableJson(iosContract.metadata['sql-members']) || + stableJson(sortedStrings(member.nativeModuleStems)) !== + stableJson(iosContract.metadata['native-module-stems']) || stableJson(sortedStrings(member.dependencies)) !== stableJson( [...(iosContract.metadata['selected-extension-dependencies'] ?? [])].sort(compareText), @@ -981,6 +985,8 @@ function publicExtensionMember(member) { extensionSqlFileNames: member.extensionSqlFileNames, extensionSqlFilePrefixes: member.extensionSqlFilePrefixes, nativeModuleStem: member.nativeModuleStem, + sqlMembers: member.sqlMembers, + nativeModuleStems: member.nativeModuleStems, iosNativeDependencies: member.iosNativeDependencies, iosRegistration: member.iosRegistration, wasixInstall, @@ -1525,6 +1531,8 @@ function extensionGithubReleaseArtifacts(files, product) { extensionSqlFileNames: publicMember.extensionSqlFileNames, extensionSqlFilePrefixes: publicMember.extensionSqlFilePrefixes, nativeModuleStem: publicMember.nativeModuleStem, + sqlMembers: publicMember.sqlMembers, + nativeModuleStems: publicMember.nativeModuleStems, iosNativeDependencies: publicMember.iosNativeDependencies, iosRegistration: publicMember.iosRegistration, wasixInstall: publicMember.wasixInstall, diff --git a/tools/release/publication-lock.test.mts b/tools/release/publication-lock.test.mts index 5a49749d8..3cc4d70ca 100644 --- a/tools/release/publication-lock.test.mts +++ b/tools/release/publication-lock.test.mts @@ -17,12 +17,12 @@ import { extensionCarrierLegalContract, stageExtensionUpstreamLicenses, } from '../../src/extensions/tools/extension-upstream-licenses.mts'; -import { extensionDependencyRequirement } from '../../src/wasix/runtime/tools/package_liboliphaunt_wasix_cargo_artifacts.mts'; import { buildSwiftExtensionCarrierManifest, iosBaseLegalMetadata, swiftExtensionCarrierAssetName, } from '../../src/native/sdks/swift/tools/ios-carrier-manifest.mts'; +import { extensionDependencyRequirement } from '../../src/wasix/runtime/tools/package_liboliphaunt_wasix_cargo_artifacts.mts'; import { createDeterministicTar } from '../packaging/cargo-source-package.mts'; import { releaseJavaScript } from '../packaging/emit-javascript.mts'; import { canonicalGzipSync } from '../packaging/portable-archive.mts'; @@ -512,6 +512,8 @@ function extensionGithubReleaseFixture( extensionSqlFileNames: [...extension['extension-sql-file-names']].sort(), extensionSqlFilePrefixes: [...extension['extension-sql-file-prefixes']].sort(), nativeModuleStem, + sqlMembers: [...extension['sql-members']], + nativeModuleStems: [...extension['native-module-stems']], iosNativeDependencies: stagesIos ? iosNativeDependencies : [], iosRegistration: nativeModuleStem === null || !stagesIos @@ -720,6 +722,8 @@ function extensionGithubReleaseFixture( extensionSqlFileNames: extensions[0].extensionSqlFileNames, extensionSqlFilePrefixes: extensions[0].extensionSqlFilePrefixes, nativeModuleStem: extensions[0].nativeModuleStem, + sqlMembers: extensions[0].sqlMembers, + nativeModuleStems: extensions[0].nativeModuleStems, iosNativeDependencies: extensions[0].iosNativeDependencies, iosRegistration: extensions[0].iosRegistration, wasixInstall: extensions[0].wasixInstall, From b5a90278e0fe7a657e0f85212369447b770485fc Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 07:14:55 +0000 Subject: [PATCH 10/29] fix(extensions): preserve pinned GDAL release metadata --- .../artifacts/native/tools/build-windows-extensions.sh | 4 ++++ src/native/runtime/bin/build-postgres18-linux.sh | 4 ++++ src/native/runtime/bin/build-postgres18-macos.sh | 4 ++++ src/native/runtime/bin/mobile-postgis-extensions.sh | 4 ++++ src/native/runtime/bin/mobile-postgis-extensions.test.sh | 9 +++++++++ src/wasix/runtime/assets/build/build_wasix_gdal.sh | 2 ++ 6 files changed, 27 insertions(+) diff --git a/src/extensions/artifacts/native/tools/build-windows-extensions.sh b/src/extensions/artifacts/native/tools/build-windows-extensions.sh index a0ab35b82..30be66c22 100644 --- a/src/extensions/artifacts/native/tools/build-windows-extensions.sh +++ b/src/extensions/artifacts/native/tools/build-windows-extensions.sh @@ -89,6 +89,10 @@ windows_extension_postgis_dependencies() { fi prefix="$dependency_root/gdal" if ! (first_file "$prefix" gdal.lib) >/dev/null 2>&1 || [ ! -f "$prefix/share/gdal/gdalvrt.xsd" ]; then + # Preserve the release date in the pinned header instead of probing the enclosing checkout. + local gdal_source_commit + gdal_source_commit="$(git -C "$external_checkout_root/gdal" rev-parse HEAD)" + GDAL_SHA1SUM="$gdal_source_commit" \ windows_extension_cmake gdal "$external_checkout_root/gdal" "$prefix" \ -DBUILD_SHARED_LIBS=OFF -DBUILD_APPS=OFF -DBUILD_TESTING=OFF \ -DBUILD_CSHARP_BINDINGS=OFF -DBUILD_JAVA_BINDINGS=OFF -DBUILD_PYTHON_BINDINGS=OFF \ diff --git a/src/native/runtime/bin/build-postgres18-linux.sh b/src/native/runtime/bin/build-postgres18-linux.sh index 75160e7cd..66f14fc04 100755 --- a/src/native/runtime/bin/build-postgres18-linux.sh +++ b/src/native/runtime/bin/build-postgres18-linux.sh @@ -1400,6 +1400,10 @@ build_native_postgis_gdal_dependency() { [ -f "$source_dir/CMakeLists.txt" ] || fail "missing GDAL checkout: $source_dir" [ -f "$proj_dir/lib/libproj.a" ] || fail "GDAL dependency requires PROJ first" rm -rf "$build_root" "$dependency_dir" + # Preserve the release date in the pinned header instead of probing the enclosing checkout. + local gdal_source_commit + gdal_source_commit="$(git -C "$source_dir" rev-parse HEAD)" + GDAL_SHA1SUM="$gdal_source_commit" \ native_postgis_cmake_install "$source_dir" "$build_root" "$dependency_dir" \ -DCMAKE_BUILD_TYPE=Release \ -DBASH_COMPLETIONS_DIR= \ diff --git a/src/native/runtime/bin/build-postgres18-macos.sh b/src/native/runtime/bin/build-postgres18-macos.sh index 7f53bd4f4..388c06d7c 100755 --- a/src/native/runtime/bin/build-postgres18-macos.sh +++ b/src/native/runtime/bin/build-postgres18-macos.sh @@ -1662,6 +1662,10 @@ build_native_postgis_gdal_dependency() { [ -f "$source_dir/CMakeLists.txt" ] || native_postgis_fail "missing GDAL checkout: $source_dir" [ -f "$proj_dir/lib/libproj.a" ] || native_postgis_fail "GDAL dependency requires PROJ first" rm -rf "$build_root" "$dependency_dir" + # Preserve the release date in the pinned header instead of probing the enclosing checkout. + local gdal_source_commit + gdal_source_commit="$(git -C "$source_dir" rev-parse HEAD)" + GDAL_SHA1SUM="$gdal_source_commit" \ native_postgis_cmake_install "$source_dir" "$build_root" "$dependency_dir" \ -DCMAKE_BUILD_TYPE=Release \ -DBASH_COMPLETIONS_DIR= \ diff --git a/src/native/runtime/bin/mobile-postgis-extensions.sh b/src/native/runtime/bin/mobile-postgis-extensions.sh index 5106cff89..11faa241f 100644 --- a/src/native/runtime/bin/mobile-postgis-extensions.sh +++ b/src/native/runtime/bin/mobile-postgis-extensions.sh @@ -377,6 +377,10 @@ build_postgis_gdal_dependency() { ;; esac rm -rf "$build_root" "$dependency_dir" + # Preserve the release date in the pinned header instead of probing the enclosing checkout. + local gdal_source_commit + gdal_source_commit="$(git -C "$source_dir" rev-parse HEAD)" + GDAL_SHA1SUM="$gdal_source_commit" \ oliphaunt_postgis_cmake_install "$source_dir" "$build_root" "$dependency_dir" \ "${cmake_args[@]}" [ -f "$archive" ] || oliphaunt_postgis_fail "GDAL build did not produce $archive" diff --git a/src/native/runtime/bin/mobile-postgis-extensions.test.sh b/src/native/runtime/bin/mobile-postgis-extensions.test.sh index 48c641faf..8f695f040 100755 --- a/src/native/runtime/bin/mobile-postgis-extensions.test.sh +++ b/src/native/runtime/bin/mobile-postgis-extensions.test.sh @@ -20,6 +20,11 @@ mkdir -p \ "$repo_root/target/oliphaunt-sources/checkouts/gdal" \ "$mobile_static_dependency_root/proj/lib" : > "$repo_root/target/oliphaunt-sources/checkouts/gdal/CMakeLists.txt" +gdal_checkout="$repo_root/target/oliphaunt-sources/checkouts/gdal" +git init --quiet "$gdal_checkout" +git -C "$gdal_checkout" -c user.name=Fixture -c user.email=fixture@example.invalid \ + commit --quiet --allow-empty -m fixture +gdal_fixture_commit="$(git -C "$gdal_checkout" rev-parse HEAD)" : > "$mobile_static_dependency_root/proj/lib/libproj.a" oliphaunt_postgis_selected() { @@ -31,6 +36,10 @@ dependency_builds=0 oliphaunt_postgis_cmake_install() { local dependency_dir="$3" shift 3 + [ "${GDAL_SHA1SUM:-}" = "$gdal_fixture_commit" ] || { + echo "GDAL version metadata did not use its pinned source revision" >&2 + return 1 + } printf '%s\n' "$@" > "$cmake_args_file" dependency_builds=$((dependency_builds + 1)) mkdir -p "$dependency_dir/lib" "$dependency_dir/include" "$dependency_dir/bin" "$dependency_dir/share/gdal" diff --git a/src/wasix/runtime/assets/build/build_wasix_gdal.sh b/src/wasix/runtime/assets/build/build_wasix_gdal.sh index babcb94e4..e0f11ca43 100755 --- a/src/wasix/runtime/assets/build/build_wasix_gdal.sh +++ b/src/wasix/runtime/assets/build/build_wasix_gdal.sh @@ -67,6 +67,8 @@ fi { rm -rf "$GDAL_BUILD_DIR" "$GDAL_PREFIX" mkdir -p "$GDAL_BUILD_DIR" "$(dirname "$GDAL_PREFIX")" + # Preserve the release date in the pinned header instead of probing the enclosing checkout. + GDAL_SHA1SUM="$source_commit" \ OLIPHAUNT_WASIX_CMAKE_C_FLAGS_EXTRA="-include $compat_header" \ OLIPHAUNT_WASIX_CMAKE_CXX_FLAGS_EXTRA="-include $compat_header" \ oliphaunt_wasix_static_cmake_build \ From 15d9d0126fbac5757feed61b755e78b10abd04e3 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 07:37:21 +0000 Subject: [PATCH 11/29] fix(native): invalidate stale extension dependency caches --- .../bin/build-postgres18-android-arm64.sh | 2 + .../bin/build-postgres18-ios-device.sh | 2 + .../bin/build-postgres18-ios-simulator.sh | 2 + .../runtime/bin/build-postgres18-linux.sh | 2 + src/native/runtime/bin/compiler-cache.test.sh | 51 +++++++++++++++++++ 5 files changed, 59 insertions(+) diff --git a/src/native/runtime/bin/build-postgres18-android-arm64.sh b/src/native/runtime/bin/build-postgres18-android-arm64.sh index 6a441bca3..169270c0c 100755 --- a/src/native/runtime/bin/build-postgres18-android-arm64.sh +++ b/src/native/runtime/bin/build-postgres18-android-arm64.sh @@ -689,6 +689,8 @@ build_uuid_dependency() { } build_mobile_static_dependencies() { + # Reaching this path invalidated the runtime fingerprint, including dependency sources and flags. + rm -rf "$mobile_static_dependency_root" build_openssl_dependency build_uuid_dependency build_postgis_mobile_static_dependencies diff --git a/src/native/runtime/bin/build-postgres18-ios-device.sh b/src/native/runtime/bin/build-postgres18-ios-device.sh index 24e68fc79..270af282c 100755 --- a/src/native/runtime/bin/build-postgres18-ios-device.sh +++ b/src/native/runtime/bin/build-postgres18-ios-device.sh @@ -726,6 +726,8 @@ build_uuid_dependency() { } build_mobile_static_dependencies() { + # Reaching this path invalidated the runtime fingerprint, including dependency sources and flags. + rm -rf "$mobile_static_dependency_root" build_openssl_dependency build_uuid_dependency build_postgis_mobile_static_dependencies diff --git a/src/native/runtime/bin/build-postgres18-ios-simulator.sh b/src/native/runtime/bin/build-postgres18-ios-simulator.sh index 678eb4c06..c823ad3f5 100755 --- a/src/native/runtime/bin/build-postgres18-ios-simulator.sh +++ b/src/native/runtime/bin/build-postgres18-ios-simulator.sh @@ -726,6 +726,8 @@ build_uuid_dependency() { } build_mobile_static_dependencies() { + # Reaching this path invalidated the runtime fingerprint, including dependency sources and flags. + rm -rf "$mobile_static_dependency_root" build_openssl_dependency build_uuid_dependency build_postgis_mobile_static_dependencies diff --git a/src/native/runtime/bin/build-postgres18-linux.sh b/src/native/runtime/bin/build-postgres18-linux.sh index 66f14fc04..f76df3146 100755 --- a/src/native/runtime/bin/build-postgres18-linux.sh +++ b/src/native/runtime/bin/build-postgres18-linux.sh @@ -1833,6 +1833,7 @@ case "$script_mode" in (cd "$build_dir" && backend_objects_ready && plpgsql_objects_ready && jit_objects_ready) && [ -f "$stamp" ] && [ "$(cat "$stamp")" = "$(desired_hash)" ]; then + build_native_extension_artifacts echo "$lib_out" exit 0 fi @@ -1861,6 +1862,7 @@ case "$script_mode" in if artifact_ready && (cd "$build_dir" && backend_objects_ready && plpgsql_objects_ready && jit_objects_ready) && runtime_installed && + { [ "${OLIPHAUNT_BUILD_EXTENSIONS:-0}" = "0" ] || native_extension_artifacts_current; } && [ -f "$stamp" ] && [ "$(cat "$stamp")" = "$(desired_hash)" ]; then echo "Linux $target_id liboliphaunt shared library is current" diff --git a/src/native/runtime/bin/compiler-cache.test.sh b/src/native/runtime/bin/compiler-cache.test.sh index d6ddee164..a0a141185 100644 --- a/src/native/runtime/bin/compiler-cache.test.sh +++ b/src/native/runtime/bin/compiler-cache.test.sh @@ -181,3 +181,54 @@ for builder in linux macos android-arm64; do done done printf 'Compiler cache setup, CMake launchers, overrides, and SQLite recipes passed\n' + +# A cached core must still build and validate the requested extension selection. +linux_dispatch="$(awk '/^case "\$script_mode" in$/ {copy=1} copy {print}' "$script_dir/build-postgres18-linux.sh")" +[ -n "$linux_dispatch" ] +for selected in postgis postgis,fuzzystrmatch; do + ( + for name in prepare_source build_icu configure_source install_runtime \ + prune_base_runtime_optional_extensions artifact_ready backend_objects_ready \ + plpgsql_objects_ready jit_objects_ready runtime_installed; do + eval "$name() { return 0; }" + done + desired_hash() { printf 'cached\n'; } + build_native_extension_artifacts() { printf '%s\n' "$OLIPHAUNT_NATIVE_EXTENSION_SQL_NAMES" >> "$fixture/extensions-built"; } + native_extension_artifacts_current() { return 1; } + build_dir="$fixture" + stamp="$fixture/core.stamp" + desired_hash > "$stamp" + lib_out="$fixture/liboliphaunt.so" + target_id=linux-x64-gnu + OLIPHAUNT_BUILD_EXTENSIONS=1 + OLIPHAUNT_NATIVE_EXTENSION_SQL_NAMES="$selected" + OLIPHAUNT_FORCE_EXTENSION_REBUILD=0 + script_mode=build + (eval "$linux_dispatch") >/dev/null + script_mode=--check-current + if (eval "$linux_dispatch") >/dev/null 2>&1; then + echo "Linux readiness accepted a stale extension selection" >&2 + exit 1 + fi + ) +done +[ "$(cat "$fixture/extensions-built")" = "$(printf 'postgis\npostgis,fuzzystrmatch')" ] +printf 'Linux cached-runtime extension selection checks passed\n' + +for target in android-arm64 ios-device ios-simulator; do + ( + load_recipe "$script_dir/build-postgres18-$target.sh" build_mobile_static_dependencies + mobile_static_dependency_root="$fixture/dependencies-$target" + mkdir -p "$mobile_static_dependency_root" + printf stale > "$mobile_static_dependency_root/old-source-archive.a" + build_openssl_dependency() { + [ ! -e "$mobile_static_dependency_root/old-source-archive.a" ] + mkdir -p "$mobile_static_dependency_root" + printf rebuilt > "$mobile_static_dependency_root/openssl.a" + } + build_uuid_dependency() { [ -s "$mobile_static_dependency_root/openssl.a" ]; } + build_postgis_mobile_static_dependencies() { [ -s "$mobile_static_dependency_root/openssl.a" ]; } + build_mobile_static_dependencies + ) +done +printf 'Mobile stale dependency archive invalidation checks passed\n' From 8c7aba2dc4d37fd7776900f1e7618fa161eb1245 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 07:37:54 +0000 Subject: [PATCH 12/29] fix(wasix): include browser extension dependency carriers --- src/examples/wasix/browser/vite.config.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/examples/wasix/browser/vite.config.ts b/src/examples/wasix/browser/vite.config.ts index 5c5a48669..16f9d558e 100644 --- a/src/examples/wasix/browser/vite.config.ts +++ b/src/examples/wasix/browser/vite.config.ts @@ -163,6 +163,7 @@ function wasixAssets(): Plugin { ['/extensions/pgtap', resolve(extensionAssetRoot, 'extensions/pgtap.tar.zst')], ['/extensions/pg_uuidv7', resolve(extensionAssetRoot, 'extensions/pg_uuidv7.tar.zst')], ['/extensions/postgis', resolve(extensionAssetRoot, 'extensions/postgis.tar.zst')], + ['/extensions/fuzzystrmatch', resolve(extensionAssetRoot, 'extensions/fuzzystrmatch.tar.zst')], ['/pglite.data', resolve(pgliteAssetRoot, 'pglite.data')], ['/pglite.wasm', resolve(pgliteAssetRoot, 'pglite.wasm')], ['/initdb.wasm', resolve(pgliteAssetRoot, 'initdb.wasm')], @@ -340,6 +341,15 @@ async function developmentDescriptor(packageName: string): Promise Date: Mon, 19 Oct 2026 07:40:10 +0000 Subject: [PATCH 13/29] fix(wasix): export PostgreSQL symbols required by topology --- src/wasix/runtime/assets/generated/wasix-dl.exports | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wasix/runtime/assets/generated/wasix-dl.exports b/src/wasix/runtime/assets/generated/wasix-dl.exports index 10cfd3d95..d5ad54bd6 100644 --- a/src/wasix/runtime/assets/generated/wasix-dl.exports +++ b/src/wasix/runtime/assets/generated/wasix-dl.exports @@ -315,6 +315,7 @@ RelationGetIndexScan RelationGetNumberOfBlocksInFork RelationIdGetRelation RelationIsVisible +RelationNameGetTupleDesc RelationTruncate ReleaseAllPlanCacheRefsInOwner ReleaseBuffer @@ -430,6 +431,7 @@ UnlockReleaseBuffer UnlockTuple UnpinPortal UnregisterSnapshot +UnregisterXactCallback UpdateActiveSnapshotCommandId WaitForParallelWorkersToAttach WaitForParallelWorkersToFinish From db4131d679558449aa3f76beba3620782975a2d0 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 07:58:07 +0000 Subject: [PATCH 14/29] fix(wasix): include tracked exports in compiler cache inputs --- src/wasix/runtime/moon.yml | 1 - tools/ci/ci-plan-node-products.test.mts | 10 ++++++++++ tools/ci/ci-plan-test-inputs.mts | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/wasix/runtime/moon.yml b/src/wasix/runtime/moon.yml index 79e79beab..3650c35c1 100644 --- a/src/wasix/runtime/moon.yml +++ b/src/wasix/runtime/moon.yml @@ -209,7 +209,6 @@ tasks: outputs: - /target/oliphaunt-wasix/wasix-build/build/package-stage/**/* - /target/oliphaunt-wasix/assets/**/* - - /src/wasix/runtime/assets/generated/**/* options: cache: local runFromWorkspaceRoot: true diff --git a/tools/ci/ci-plan-node-products.test.mts b/tools/ci/ci-plan-node-products.test.mts index a13598c82..308371e73 100644 --- a/tools/ci/ci-plan-node-products.test.mts +++ b/tools/ci/ci-plan-node-products.test.mts @@ -1152,6 +1152,16 @@ test('Android extension packaging selects matching dependency-complete Linux sup } }); +test('tracked WASIX exports invalidate compilation and cannot be restored by the package cache', () => { + const result = effects(paths.wasixExportList); + assert(result.directTasks.includes('liboliphaunt-wasix:compiler-output')); + const portable = taskRecord(taskRoots.liboliphauntWasixRuntimePortable); + assert( + portable.outputs.every((output) => (output.glob ?? output.file).startsWith('/target/')), + 'portable package cache must not restore source inputs', + ); +}); + test('WASIX package README and compiler unit fixtures do not select runtime compilers', () => { for (const file of [ paths.wasixSdkReadme, diff --git a/tools/ci/ci-plan-test-inputs.mts b/tools/ci/ci-plan-test-inputs.mts index f286e2b76..10a3e0759 100644 --- a/tools/ci/ci-plan-test-inputs.mts +++ b/tools/ci/ci-plan-test-inputs.mts @@ -19,6 +19,7 @@ export const paths = { wasixRustReadme: 'src/wasix/sdks/rust/README.md', nativeRustReadme: 'src/native/sdks/rust/README.md', wasixDockerTest: 'src/wasix/runtime/assets/build/docker/install-pinned-wasixcc.test.sh', + wasixExportList: 'src/wasix/runtime/assets/generated/wasix-dl.exports', nativeExtensionFixture: 'src/extensions/artifacts/native/tools/create-artifact.test.mts', windowsVcRuntimePolicy: 'tools/packaging/windows-vc-runtime-policy.json', wasixRuntimeCarrierSource: 'src/wasix/runtime/crates/assets/src/lib.rs', @@ -115,6 +116,7 @@ export const paths = { export const taskRoots = { oliphauntWasixRustTestAot: 'oliphaunt-wasix-rust:test-aot', liboliphauntWasixRuntimeAot: 'liboliphaunt-wasix:runtime-aot', + liboliphauntWasixRuntimePortable: 'liboliphaunt-wasix:runtime-portable', oliphauntSwiftPackageBindings: 'oliphaunt-swift:package-bindings', oliphauntSwiftPackage: 'oliphaunt-swift:package', oliphauntJsPackage: 'oliphaunt-js:package', From eeea0d00d8fc25804cedc3bc3f4a1d955b843c1a Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 08:11:43 +0000 Subject: [PATCH 15/29] fix(wasix): retain runtime-provided browser dependencies --- src/examples/wasix/browser/vite.config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/examples/wasix/browser/vite.config.ts b/src/examples/wasix/browser/vite.config.ts index 16f9d558e..c6ad6e620 100644 --- a/src/examples/wasix/browser/vite.config.ts +++ b/src/examples/wasix/browser/vite.config.ts @@ -341,11 +341,17 @@ async function developmentDescriptor(packageName: string): Promise requireRecord(entry, 'runtime support entry').name, + ), + ); const carriers: unknown[] = []; for (const dependency of requireArray(metadata.dependencies, 'extension dependencies')) { if (typeof dependency !== 'string') { throw new Error('extension dependency must be a SQL name'); } + if (runtimeSupport.has(dependency)) continue; const descriptor = await developmentDescriptor(dependency); carriers.push(...requireArray(descriptor.carriers, 'dependency carriers')); } From 1689128f67ccef7c9e1c2eb5f3f4c7d1af8ef2eb Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 08:39:15 +0000 Subject: [PATCH 16/29] fix(wasix): align GDAL resources with the artifact contract --- .../external/postgis/targets/wasix.toml | 56 +++++++++---------- .../tools/native-component-contract.test.mts | 7 +++ .../runtime/tools/xtask/src/asset_checks.rs | 4 +- .../sdks/rust/src/oliphaunt/postgres_mod.rs | 2 +- .../sdks/ts/src/__tests__/extensions.test.ts | 10 ++-- .../ts/src/hosts/browser/wasix-runtime.ts | 2 +- src/wasix/sdks/ts/src/resources/extensions.ts | 4 +- 7 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/extensions/external/postgis/targets/wasix.toml b/src/extensions/external/postgis/targets/wasix.toml index 8e17b0d73..f04c26d8e 100644 --- a/src/extensions/external/postgis/targets/wasix.toml +++ b/src/extensions/external/postgis/targets/wasix.toml @@ -9,32 +9,32 @@ module_files = [ "lib/postgresql/liboliphaunt_postgis_deps.so", ] data_files = [ - "share/postgresql/gdal/GDALLogoBW.svg", - "share/postgresql/gdal/GDALLogoColor.svg", - "share/postgresql/gdal/GDALLogoGS.svg", - "share/postgresql/gdal/LICENSE.TXT", - "share/postgresql/gdal/cubewerx_extra.wkt", - "share/postgresql/gdal/ecw_cs.wkt", - "share/postgresql/gdal/epsg.wkt", - "share/postgresql/gdal/esri_StatePlane_extra.wkt", - "share/postgresql/gdal/gdal_algorithm.schema.json", - "share/postgresql/gdal/gdalicon.png", - "share/postgresql/gdal/gdalinfo_output.schema.json", - "share/postgresql/gdal/gdalmdiminfo_output.schema.json", - "share/postgresql/gdal/gdalvrt.xsd", - "share/postgresql/gdal/leaflet_template.html", - "share/postgresql/gdal/ogr_fields_override.schema.json", - "share/postgresql/gdal/ogrinfo_output.schema.json", - "share/postgresql/gdal/ozi_datum.csv", - "share/postgresql/gdal/ozi_ellips.csv", - "share/postgresql/gdal/pci_datum.txt", - "share/postgresql/gdal/pci_ellips.txt", - "share/postgresql/gdal/stateplane.csv", - "share/postgresql/gdal/template_tiles.mapml", - "share/postgresql/gdal/tms_LINZAntarticaMapTileGrid.json", - "share/postgresql/gdal/tms_MapML_APSTILE.json", - "share/postgresql/gdal/tms_MapML_CBMTILE.json", - "share/postgresql/gdal/tms_NZTM2000.json", + "share/gdal/GDALLogoBW.svg", + "share/gdal/GDALLogoColor.svg", + "share/gdal/GDALLogoGS.svg", + "share/gdal/LICENSE.TXT", + "share/gdal/cubewerx_extra.wkt", + "share/gdal/ecw_cs.wkt", + "share/gdal/epsg.wkt", + "share/gdal/esri_StatePlane_extra.wkt", + "share/gdal/gdal_algorithm.schema.json", + "share/gdal/gdalicon.png", + "share/gdal/gdalinfo_output.schema.json", + "share/gdal/gdalmdiminfo_output.schema.json", + "share/gdal/gdalvrt.xsd", + "share/gdal/leaflet_template.html", + "share/gdal/ogr_fields_override.schema.json", + "share/gdal/ogrinfo_output.schema.json", + "share/gdal/ozi_datum.csv", + "share/gdal/ozi_ellips.csv", + "share/gdal/pci_datum.txt", + "share/gdal/pci_ellips.txt", + "share/gdal/stateplane.csv", + "share/gdal/template_tiles.mapml", + "share/gdal/tms_LINZAntarticaMapTileGrid.json", + "share/gdal/tms_MapML_APSTILE.json", + "share/gdal/tms_MapML_CBMTILE.json", + "share/gdal/tms_NZTM2000.json", "share/postgresql/contrib/postgis-3.6/legacy.sql", "share/postgresql/contrib/postgis-3.6/legacy_gist.sql", "share/postgresql/contrib/postgis-3.6/legacy_minimal.sql", @@ -43,7 +43,7 @@ data_files = [ "share/postgresql/contrib/postgis-3.6/spatial_ref_sys.sql", "share/postgresql/contrib/postgis-3.6/uninstall_legacy.sql", "share/postgresql/contrib/postgis-3.6/uninstall_postgis.sql", - "share/postgresql/proj/proj.db", + "share/proj/proj.db", ] configure_flags = [ "--without-sfcgal", @@ -104,7 +104,7 @@ destination = "share/proj" [[staging.data_dirs]] source = "postgis/share/gdal" -destination = "share/postgresql/gdal" +destination = "share/gdal" [[staging.data_dirs]] source = "postgis/share/extension-members" diff --git a/src/extensions/tools/native-component-contract.test.mts b/src/extensions/tools/native-component-contract.test.mts index 0861301d6..b9acb67ba 100644 --- a/src/extensions/tools/native-component-contract.test.mts +++ b/src/extensions/tools/native-component-contract.test.mts @@ -1,4 +1,5 @@ import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; import test from 'node:test'; import { @@ -37,6 +38,12 @@ test('PostGIS target differences are explicit', () => { assert.equal(android.components.at(-1), 'gdal'); assert.deepEqual(wasix.components, android.components); assert.deepEqual(android.linkUnits.slice(0, 2), ['libiconv', 'libcharset']); + const target = Bun.TOML.parse( + readFileSync(new URL('../external/postgis/targets/wasix.toml', import.meta.url), 'utf8'), + ); + for (const file of wasix.runtimeFiles) { + assert(target.data_files.includes(`share/${file}`), `WASIX data missing: ${file}`); + } }); test('contrib native dependencies share the same cross-platform contract', () => { diff --git a/src/wasix/runtime/tools/xtask/src/asset_checks.rs b/src/wasix/runtime/tools/xtask/src/asset_checks.rs index 4104c1bd1..311d3f6a0 100644 --- a/src/wasix/runtime/tools/xtask/src/asset_checks.rs +++ b/src/wasix/runtime/tools/xtask/src/asset_checks.rs @@ -386,7 +386,7 @@ fn check_extension_archive_layout(path: &Path) -> Result<()> { | "share" | "share/proj" | "share/postgresql" - | "share/postgresql/gdal" + | "share/gdal" | "share/postgresql/extension" | "share/postgresql/tsearch_data" ) { @@ -394,7 +394,7 @@ fn check_extension_archive_layout(path: &Path) -> Result<()> { } if entry.starts_with("lib/postgresql/") || entry.starts_with("share/proj/") - || entry.starts_with("share/postgresql/gdal/") + || entry.starts_with("share/gdal/") || entry.starts_with("share/postgresql/extension/") || entry.starts_with("share/postgresql/tsearch_data/") { diff --git a/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs b/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs index 373002157..47f9b80ab 100644 --- a/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs +++ b/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs @@ -1789,7 +1789,7 @@ fn add_oliphaunt_env( ("PGTZ", "UTC"), ("PG_COLOR", "never"), ("PROJ_DATA", "/share/proj"), - ("GDAL_DATA", "/share/postgresql/gdal"), + ("GDAL_DATA", "/share/gdal"), ] { builder.add_env(key, value); } diff --git a/src/wasix/sdks/ts/src/__tests__/extensions.test.ts b/src/wasix/sdks/ts/src/__tests__/extensions.test.ts index 21bea424a..cc19b2c0f 100644 --- a/src/wasix/sdks/ts/src/__tests__/extensions.test.ts +++ b/src/wasix/sdks/ts/src/__tests__/extensions.test.ts @@ -286,7 +286,7 @@ describe('WASIX TypeScript extensions', () => { it('mounts declared PostGIS raster modules and GDAL resources', () => { const files = new Map([ ['lib/postgresql/postgis_raster-3.so', Uint8Array.of(1)], - ['share/postgresql/gdal/gdalvrt.xsd', Uint8Array.of(2)], + ['share/gdal/gdalvrt.xsd', Uint8Array.of(2)], ]); const postgis = extension('postgis', { installedFiles: [...files.keys()] }); const layout = runtimeLayout(); @@ -294,24 +294,24 @@ describe('WASIX TypeScript extensions', () => { layout, { files, - directories: new Set(['lib/postgresql', 'share/postgresql/gdal']), + directories: new Set(['lib/postgresql', 'share/gdal']), }, postgis, ); expect(layout.mounts['/lib']?.files['postgresql/postgis_raster-3.so']).toEqual( Uint8Array.of(1), ); - expect(layout.mounts['/share']?.files['postgresql/gdal/gdalvrt.xsd']).toEqual(Uint8Array.of(2)); + expect(layout.mounts['/share']?.files['gdal/gdalvrt.xsd']).toEqual(Uint8Array.of(2)); expect(() => overlayExtensionArchive( runtimeLayout(), { - files: new Map([...files, ['share/postgresql/gdal/undeclared.csv', Uint8Array.of(3)]]), + files: new Map([...files, ['share/gdal/undeclared.csv', Uint8Array.of(3)]]), directories: new Set(), }, postgis, ), - ).toThrow('unexpected share/postgresql/gdal/undeclared.csv'); + ).toThrow('unexpected share/gdal/undeclared.csv'); }); it('rejects undeclared, foreign-root, and colliding extension files', () => { diff --git a/src/wasix/sdks/ts/src/hosts/browser/wasix-runtime.ts b/src/wasix/sdks/ts/src/hosts/browser/wasix-runtime.ts index c170fc21a..c496e44d3 100644 --- a/src/wasix/sdks/ts/src/hosts/browser/wasix-runtime.ts +++ b/src/wasix/sdks/ts/src/hosts/browser/wasix-runtime.ts @@ -183,7 +183,7 @@ export function wasixPostgresEnvironment( PGTZ: 'UTC', PG_COLOR: 'never', PROJ_DATA: '/share/proj', - GDAL_DATA: '/share/postgresql/gdal', + GDAL_DATA: '/share/gdal', ...(icuEnabled ? { ICU_DATA: '/share/icu' } : {}), // The canonical guest specializes backend atomics for a one-backend // WebAssembly instance. Every host placement must enforce that invariant. diff --git a/src/wasix/sdks/ts/src/resources/extensions.ts b/src/wasix/sdks/ts/src/resources/extensions.ts index 457b36f21..fee269d32 100644 --- a/src/wasix/sdks/ts/src/resources/extensions.ts +++ b/src/wasix/sdks/ts/src/resources/extensions.ts @@ -893,7 +893,7 @@ function extensionMountTarget( const allowed = [ 'lib/postgresql/', 'share/proj/', - 'share/postgresql/gdal/', + 'share/gdal/', 'share/postgresql/extension/', 'share/postgresql/tsearch_data/', ]; @@ -901,7 +901,7 @@ function extensionMountTarget( 'lib/postgresql', 'share/proj', 'share/postgresql', - 'share/postgresql/gdal', + 'share/gdal', 'share/postgresql/extension', 'share/postgresql/tsearch_data', ]); From 1ae94892c8e9b2292a8376f10a67e917bda29528 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 08:43:02 +0000 Subject: [PATCH 17/29] docs(extensions): describe complete PostGIS companion packaging --- .../maintainers/extension-packaging-policy.md | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/docs/maintainers/extension-packaging-policy.md b/src/docs/maintainers/extension-packaging-policy.md index 60f96138c..dddbeedc9 100644 --- a/src/docs/maintainers/extension-packaging-policy.md +++ b/src/docs/maintainers/extension-packaging-policy.md @@ -101,8 +101,9 @@ physical carrier manifest where the expanded npm index is required. Every direct or bundle npm platform package also carries and exports `extension-contract.json` with schema `oliphaunt-npm-extension-contract-v1`. -That manifest freezes each member's `createsExtension`, native module stem, -dependencies, data files, additional SQL names/prefixes, and preload libraries +That manifest freezes each member's `createsExtension`, primary and companion +native module stems, SQL members, dependencies, data files, additional SQL +names/prefixes, and preload libraries at the independently versioned extension release. It also freezes the exact, sorted `share/licenses/` file inventory derived from that member's canonical carrier legal metadata. Every row binds the portable path, canonical SHA-256, @@ -117,8 +118,8 @@ packed carrier check proves exact mode `0644` on every platform. The runtime also requires every declared legal file and no undeclared runtime leaf. It uses this frozen contract—not the SDK's current catalog snapshot—to qualify the installed package's runtime leaf inventory. The SDK catalog remains -authoritative for selection, release/package ownership, and dependency -compatibility. +authoritative for selection, release/package ownership, and compatibility of +primary identities, companion SQL/modules, and dependencies. ## Carrier license and notice contract @@ -460,14 +461,17 @@ OpenSSL checkout and links `pgcrypto` against the staged static `libcrypto`. source directly into the `uuid-ossp` module and installs the matching control/SQL files. The Windows native PostGIS producer builds the pinned GDAL, GEOS, PROJ, SQLite, json-c, and libxml2 dependency stack, links the generated -`postgis-3` and `postgis_raster-3` modules against those static archives, and stages -the matching extension SQL, GDAL data, and `proj/proj.db`. The PostGIS product -owns both SQL extensions; applications can enable raster with -`CREATE EXTENSION postgis_raster` after enabling `postgis`. Mobile carriers -register the combined core/raster archive. Desktop and WASIX carriers ship -separate modules. GDAL includes GTiff/VRT and GeoJSON/Shapefile drivers; raster -smoke tests explicitly enable GTiff and verify pixel values, dimensions, and -SRID through a GeoTIFF round trip after creation and restart. +`postgis-3`, `postgis_raster-3`, and `postgis_topology-3` modules against those +static archives, and stages the matching extension SQL, GDAL data, and +`proj/proj.db`. The PostGIS product owns the core, raster, topology, and tiger +geocoder SQL extensions and selects `fuzzystrmatch` from the contrib product. +Applications enable the companions with `CREATE EXTENSION postgis_raster`, +`CREATE EXTENSION postgis_topology`, and `CREATE EXTENSION postgis_tiger_geocoder` +after enabling their prerequisites. Mobile carriers register the combined +core/raster/topology archive. Desktop and WASIX carriers ship separate modules. +GDAL includes GTiff/VRT and GeoJSON/Shapefile drivers. Shared lifecycle fixtures +verify GeoTIFF pixel values, dimensions, and SRID, persistent topology, and +address normalization after creation, restart, and backup/restore. ## Target-Specific PG18.4 Support From 07efe6c2514024a151f11dbbff6e4dc9ad19f42b Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 09:00:21 +0000 Subject: [PATCH 18/29] fix(native): scope GDAL data to the embedded runtime --- src/native/runtime/smoke/liboliphaunt_smoke.c | 22 +++++++--- .../runtime/src/liboliphaunt_internal.h | 3 ++ src/native/runtime/src/liboliphaunt_native.c | 43 +++++++++++++++++++ .../sdks/rust/tests/native_extensions.rs | 36 +++++++++++++++- 4 files changed, 95 insertions(+), 9 deletions(-) diff --git a/src/native/runtime/smoke/liboliphaunt_smoke.c b/src/native/runtime/smoke/liboliphaunt_smoke.c index e47868b88..c98350120 100644 --- a/src/native/runtime/smoke/liboliphaunt_smoke.c +++ b/src/native/runtime/smoke/liboliphaunt_smoke.c @@ -280,13 +280,14 @@ static int verify_global_contract(void) { return 0; } -static int expect_pgdata_env(const char *context, const char *expected) { - const char *actual = getenv("PGDATA"); +static int expect_backend_env(const char *context, const char *name, const char *expected) { + const char *actual = getenv(name); if ((expected == NULL && actual != NULL) || (expected != NULL && (actual == NULL || strcmp(actual, expected) != 0))) { fprintf(stderr, - "%s left unexpected PGDATA environment: expected %s, got %s\n", + "%s left unexpected %s environment: expected %s, got %s\n", context, + name, expected ? expected : "(unset)", actual ? actual : "(unset)"); return 1; @@ -2776,7 +2777,7 @@ static int run_cycle(const char *pgdata, const char *runtime_dir) { oliphaunt_close(db); return 1; } - if (expect_pgdata_env("oliphaunt_init active backend", pgdata) != 0) { + if (expect_backend_env("oliphaunt_init active backend", "PGDATA", pgdata) != 0) { oliphaunt_close(db); return 1; } @@ -2950,7 +2951,7 @@ static int run_cycle(const char *pgdata, const char *runtime_dir) { oliphaunt_close(db); return 1; } - if (expect_pgdata_env("oliphaunt_detach resident backend", pgdata) != 0) { + if (expect_backend_env("oliphaunt_detach resident backend", "PGDATA", pgdata) != 0) { oliphaunt_close(db); return 1; } @@ -3052,19 +3053,26 @@ int main(int argc, char **argv) { } const char *host_pgdata = "/tmp/oliphaunt-host-pgdata-sentinel"; + const char *host_gdal_data = "/tmp/oliphaunt-host-gdal-data-sentinel"; + if (setenv("GDAL_DATA", host_gdal_data, 1) != 0) { + perror("set smoke GDAL_DATA environment"); + return 1; + } if (set_pgdata_env_for_smoke(host_pgdata) != 0) { return 1; } if (run_cycle(argv[1], argv[2]) != 0) { return 1; } - if (expect_pgdata_env("oliphaunt_close", host_pgdata) != 0) { + if (expect_backend_env("oliphaunt_close", "PGDATA", host_pgdata) != 0 || + expect_backend_env("oliphaunt_close", "GDAL_DATA", host_gdal_data) != 0) { return 1; } if (expect_terminal_shutdown_reopen_rejected(argv[1], argv[2]) != 0) { return 1; } - if (expect_pgdata_env("rejected oliphaunt_init", host_pgdata) != 0) { + if (expect_backend_env("rejected oliphaunt_init", "PGDATA", host_pgdata) != 0 || + expect_backend_env("rejected oliphaunt_init", "GDAL_DATA", host_gdal_data) != 0) { return 1; } diff --git a/src/native/runtime/src/liboliphaunt_internal.h b/src/native/runtime/src/liboliphaunt_internal.h index 8043dad96..95a3fb931 100644 --- a/src/native/runtime/src/liboliphaunt_internal.h +++ b/src/native/runtime/src/liboliphaunt_internal.h @@ -141,6 +141,7 @@ struct OliphauntHandle { char *postgres_path; char *previous_pgdata_env; char *previous_proj_data_env; + char *previous_gdal_data_env; char *previous_icu_data_env; char *previous_skip_system_collation_discovery_env; char *previous_skip_icu_collation_discovery_env; @@ -152,6 +153,8 @@ struct OliphauntHandle { bool pgdata_env_overridden; bool had_previous_proj_data_env; bool proj_data_env_overridden; + bool had_previous_gdal_data_env; + bool gdal_data_env_overridden; bool had_previous_icu_data_env; bool icu_data_env_overridden; bool had_previous_skip_system_collation_discovery_env; diff --git a/src/native/runtime/src/liboliphaunt_native.c b/src/native/runtime/src/liboliphaunt_native.c index 190843659..d34454fa6 100644 --- a/src/native/runtime/src/liboliphaunt_native.c +++ b/src/native/runtime/src/liboliphaunt_native.c @@ -277,6 +277,40 @@ static int set_backend_proj_data_env(OliphauntHandle *handle) { return rc; } +static int set_backend_gdal_data_env(OliphauntHandle *handle) { + if (handle->runtime_dir == NULL || handle->runtime_dir[0] == '\0') { + return 0; + } + char *gdal_dir = oliphaunt_join_path(handle->runtime_dir, "share/postgresql/gdal"); + if (gdal_dir == NULL) { + set_error(handle, "out of memory resolving PostGIS GDAL data directory"); + return -1; + } + char *gdal_schema = oliphaunt_join_path(gdal_dir, "gdalvrt.xsd"); + if (gdal_schema == NULL) { + free(gdal_dir); + set_error(handle, "out of memory resolving PostGIS gdalvrt.xsd path"); + return -1; + } + int has_gdal_schema = oliphaunt_path_exists(gdal_schema); + free(gdal_schema); + if (!has_gdal_schema) { + free(gdal_dir); + return 0; + } + + int rc = set_backend_env_var( + handle, + "GDAL_DATA", + gdal_dir, + &handle->previous_gdal_data_env, + &handle->had_previous_gdal_data_env, + &handle->gdal_data_env_overridden, + "PostGIS GDAL data"); + free(gdal_dir); + return rc; +} + static int set_backend_embedded_module_dir_env(OliphauntHandle *handle) { char *module_dir = oliphaunt_resolve_embedded_module_dir(handle->module_dir, handle->runtime_dir); if (module_dir == NULL) { @@ -311,6 +345,9 @@ static int set_backend_runtime_env(OliphauntHandle *handle) { if (set_backend_proj_data_env(handle) != 0) { return -1; } + if (set_backend_gdal_data_env(handle) != 0) { + return -1; + } if (set_backend_embedded_module_dir_env(handle) != 0) { return -1; } @@ -326,6 +363,11 @@ static void restore_backend_runtime_env(OliphauntHandle *handle) { &handle->previous_module_dir_env, &handle->had_previous_module_dir_env, &handle->module_dir_env_overridden); + restore_backend_env_var( + "GDAL_DATA", + &handle->previous_gdal_data_env, + &handle->had_previous_gdal_data_env, + &handle->gdal_data_env_overridden); restore_backend_env_var( "PROJ_DATA", &handle->previous_proj_data_env, @@ -791,6 +833,7 @@ int32_t oliphaunt_close_claimed_global_instance(OliphauntHandle *handle) { free(handle->postgres_path); free(handle->previous_pgdata_env); free(handle->previous_proj_data_env); + free(handle->previous_gdal_data_env); free(handle->previous_icu_data_env); free(handle->previous_skip_system_collation_discovery_env); free(handle->previous_skip_icu_collation_discovery_env); diff --git a/src/native/sdks/rust/tests/native_extensions.rs b/src/native/sdks/rust/tests/native_extensions.rs index fba094efc..be1bda2a3 100644 --- a/src/native/sdks/rust/tests/native_extensions.rs +++ b/src/native/sdks/rust/tests/native_extensions.rs @@ -293,6 +293,16 @@ fn run_direct_extension_child( if let Some(path) = backup_path { command.env(DIRECT_CHILD_BACKUP_ENV, path); } + if extension == Extension::POSTGIS { + match action { + DirectExtensionChildAction::InstallBackup => { + command.env("GDAL_DATA", "oliphaunt-test-inherited-gdal-data"); + } + DirectExtensionChildAction::AssertExisting => { + command.env_remove("GDAL_DATA"); + } + } + } let output = command .output() @@ -344,13 +354,16 @@ fn run_direct_extension_child_install_backup( .extension(extension) .open(), )?); + assert_direct_extension_runtime_data(extension); install_or_load_extension(&db, TestMode::Direct, extension)?; assert_repeated_create_extension_error_recovers(&db, TestMode::Direct, extension)?; assert_extension_visible(&db, TestMode::Direct, extension)?; run_extension_functional_smoke(&db, TestMode::Direct, extension, false)?; let archive = block_on(db.backup())?; fs::write(backup_path, &archive).expect("failed to write direct extension backup artifact"); - block_on(db.close()) + block_on(db.close())?; + assert_direct_extension_runtime_data(extension); + Ok(()) } fn run_direct_extension_child_assert_existing(extension: Extension, root: &Path) -> Result<()> { @@ -361,9 +374,28 @@ fn run_direct_extension_child_assert_existing(extension: Extension, root: &Path) .extension(extension) .open(), )?); + assert_direct_extension_runtime_data(extension); assert_extension_visible(&db, TestMode::Direct, extension)?; run_extension_functional_smoke(&db, TestMode::Direct, extension, true)?; - block_on(db.close()) + block_on(db.close())?; + assert_direct_extension_runtime_data(extension); + Ok(()) +} + +fn assert_direct_extension_runtime_data(extension: Extension) { + if extension != Extension::POSTGIS { + return; + } + let proj_data = std::env::var_os("PROJ_DATA").expect("PostGIS requires its staged PROJ data"); + let gdal_data = Path::new(&proj_data) + .parent() + .expect("PROJ data must belong to the runtime share directory") + .join("gdal"); + assert_eq!( + std::env::var_os("GDAL_DATA"), + Some(gdal_data.clone().into_os_string()) + ); + assert!(gdal_data.join("gdalvrt.xsd").is_file()); } fn run_extension_smoke(mode: TestMode, broker: Option<&str>, extension: Extension) -> Result<()> { From 0543bf29be36bfef96bd19010b4ae93f8a7173c9 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 09:00:22 +0000 Subject: [PATCH 19/29] test(extensions): set the Tiger search path for lifecycle verification --- src/test-fixtures/extensions/postgis.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test-fixtures/extensions/postgis.sql b/src/test-fixtures/extensions/postgis.sql index 4d0a92dfa..8f3dea470 100644 --- a/src/test-fixtures/extensions/postgis.sql +++ b/src/test-fixtures/extensions/postgis.sql @@ -43,6 +43,8 @@ BEGIN IF topology_nodes <> 1 OR EXISTS (SELECT 1 FROM topology.ValidateTopology('oliphaunt_postgis_topology')) THEN RAISE EXCEPTION 'postgis topology persistence or validation failed: % nodes', topology_nodes; END IF; + -- Tiger resolves its internal functions and types through the session search path. + SET LOCAL search_path = public, tiger; normalized := tiger.normalize_address('1 Devonshire Place, Boston, MA 02109'); IF normalized.address IS DISTINCT FROM 1 OR normalized.streetname IS DISTINCT FROM 'Devonshire' OR normalized.streettypeabbrev IS DISTINCT FROM 'Pl' OR normalized.location IS DISTINCT FROM 'Boston' From 23ef6c1ad3fbe1975678cfa5f37cffd2aa9ca4e3 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 09:00:56 +0000 Subject: [PATCH 20/29] chore(extensions): regenerate companion lifecycle metadata --- .../native/react-native-expo/src/generated/extension-smoke.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/native/react-native-expo/src/generated/extension-smoke.ts b/src/examples/native/react-native-expo/src/generated/extension-smoke.ts index 92e8ccd8b..906914688 100644 --- a/src/examples/native/react-native-expo/src/generated/extension-smoke.ts +++ b/src/examples/native/react-native-expo/src/generated/extension-smoke.ts @@ -368,7 +368,7 @@ export const GENERATED_MOBILE_EXTENSION_SMOKE = { "CREATE TABLE oliphaunt_postgis_points(id int PRIMARY KEY, geom geometry(Point, 4326));", "INSERT INTO oliphaunt_postgis_points VALUES\n (1, ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326)),\n (2, ST_SetSRID(ST_MakePoint(-71.061, 48.433), 4326));", "CREATE INDEX oliphaunt_postgis_points_gix ON oliphaunt_postgis_points USING GIST (geom);", - "-- oliphaunt-verify\nDO $$\nDECLARE\n distance float8;\n srid int;\n area float8;\n polygons int;\n nearby int;\n gdal_drivers int;\n raster_roundtrip raster;\n raster_value float8;\n topology_nodes int;\n normalized tiger.norm_addy;\nBEGIN\n SELECT count(*) INTO topology_nodes FROM oliphaunt_postgis_topology.node\n WHERE ST_Equals(geom, ST_SetSRID(ST_MakePoint(1, 1), 4326));\n IF topology_nodes <> 1 OR EXISTS (SELECT 1 FROM topology.ValidateTopology('oliphaunt_postgis_topology')) THEN\n RAISE EXCEPTION 'postgis topology persistence or validation failed: % nodes', topology_nodes;\n END IF;\n normalized := tiger.normalize_address('1 Devonshire Place, Boston, MA 02109');\n IF normalized.address IS DISTINCT FROM 1 OR normalized.streetname IS DISTINCT FROM 'Devonshire'\n OR normalized.streettypeabbrev IS DISTINCT FROM 'Pl' OR normalized.location IS DISTINCT FROM 'Boston'\n OR normalized.stateabbrev IS DISTINCT FROM 'MA' OR normalized.zip IS DISTINCT FROM '02109'\n OR normalized.parsed IS DISTINCT FROM true THEN\n RAISE EXCEPTION 'postgis tiger address normalization failed: %', normalized;\n END IF;\n SET LOCAL postgis.gdal_enabled_drivers = 'GTiff';\n SELECT ST_Distance(ST_GeomFromText('POINT(0 0)'), ST_GeomFromText('POINT(3 4)')) INTO distance;\n IF distance <> 5 THEN\n RAISE EXCEPTION 'postgis geometry distance failed: %', distance;\n END IF;\n IF ST_AsText(ST_Buffer(ST_GeomFromText('POINT(0 0)'), 1, 'quad_segs=1')) IS NULL THEN\n RAISE EXCEPTION 'postgis buffer failed';\n END IF;\n IF NOT ST_Within(\n ST_GeomFromText('POINT(0.5 0.5)'),\n ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))')\n ) THEN\n RAISE EXCEPTION 'postgis within failed';\n END IF;\n SELECT ST_SRID(ST_Transform(ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326), 3857)) INTO srid;\n IF srid <> 3857 THEN\n RAISE EXCEPTION 'postgis transform failed: %', srid;\n END IF;\n SELECT ST_Area(ST_Transform(ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-71.1776848522251 42.3902896512902,-71.1776843766797 42.3903701743239,-71.1775844305465 42.3903829478009,-71.1775825927231 42.3902893647987,-71.1776848522251 42.3902896512902)')), 4326), 26986)) INTO area;\n IF area <= 0 THEN\n RAISE EXCEPTION 'postgis projected area failed: %', area;\n END IF;\n SELECT ST_NumGeometries(ST_Polygonize(ARRAY[\n ST_GeomFromText('LINESTRING(0 0, 1 0)'),\n ST_GeomFromText('LINESTRING(1 0, 1 1)'),\n ST_GeomFromText('LINESTRING(1 1, 0 1)'),\n ST_GeomFromText('LINESTRING(0 1, 0 0)')\n ])) INTO polygons;\n IF polygons <> 1 THEN\n RAISE EXCEPTION 'postgis polygonize failed: %', polygons;\n END IF;\n SELECT count(*) INTO nearby\n FROM oliphaunt_postgis_points\n WHERE ST_DWithin(\n geom::geography,\n ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326)::geography,\n 200\n );\n IF nearby <> 2 THEN\n RAISE EXCEPTION 'postgis dwithin failed: %', nearby;\n END IF;\n SELECT count(*) INTO gdal_drivers\n FROM ST_GDALDrivers()\n WHERE short_name = 'GTiff' AND can_read AND can_write;\n IF gdal_drivers <> 1 THEN\n RAISE EXCEPTION 'postgis raster GeoTIFF driver is unavailable';\n END IF;\n SELECT ST_FromGDALRaster(\n ST_AsGDALRaster(\n ST_AddBand(\n ST_MakeEmptyRaster(2, 2, 0, 0, 1, -1, 0, 0, 4326),\n '8BUI'::text,\n 7::double precision,\n 0::double precision\n ),\n 'GTiff'\n )\n ) INTO raster_roundtrip;\n SELECT ST_Value(raster_roundtrip, 1, 1, 1) INTO raster_value;\n IF raster_value IS DISTINCT FROM 7 OR ST_SRID(raster_roundtrip) IS DISTINCT FROM 4326\n OR ST_Width(raster_roundtrip) IS DISTINCT FROM 2 OR ST_Height(raster_roundtrip) IS DISTINCT FROM 2 THEN\n RAISE EXCEPTION 'postgis raster GDAL round-trip failed: value %, srid %',\n raster_value,\n ST_SRID(raster_roundtrip);\n END IF;\nEND $$;" + "-- oliphaunt-verify\nDO $$\nDECLARE\n distance float8;\n srid int;\n area float8;\n polygons int;\n nearby int;\n gdal_drivers int;\n raster_roundtrip raster;\n raster_value float8;\n topology_nodes int;\n normalized tiger.norm_addy;\nBEGIN\n SELECT count(*) INTO topology_nodes FROM oliphaunt_postgis_topology.node\n WHERE ST_Equals(geom, ST_SetSRID(ST_MakePoint(1, 1), 4326));\n IF topology_nodes <> 1 OR EXISTS (SELECT 1 FROM topology.ValidateTopology('oliphaunt_postgis_topology')) THEN\n RAISE EXCEPTION 'postgis topology persistence or validation failed: % nodes', topology_nodes;\n END IF;\n -- Tiger resolves its internal functions and types through the session search path.\n SET LOCAL search_path = public, tiger;\n normalized := tiger.normalize_address('1 Devonshire Place, Boston, MA 02109');\n IF normalized.address IS DISTINCT FROM 1 OR normalized.streetname IS DISTINCT FROM 'Devonshire'\n OR normalized.streettypeabbrev IS DISTINCT FROM 'Pl' OR normalized.location IS DISTINCT FROM 'Boston'\n OR normalized.stateabbrev IS DISTINCT FROM 'MA' OR normalized.zip IS DISTINCT FROM '02109'\n OR normalized.parsed IS DISTINCT FROM true THEN\n RAISE EXCEPTION 'postgis tiger address normalization failed: %', normalized;\n END IF;\n SET LOCAL postgis.gdal_enabled_drivers = 'GTiff';\n SELECT ST_Distance(ST_GeomFromText('POINT(0 0)'), ST_GeomFromText('POINT(3 4)')) INTO distance;\n IF distance <> 5 THEN\n RAISE EXCEPTION 'postgis geometry distance failed: %', distance;\n END IF;\n IF ST_AsText(ST_Buffer(ST_GeomFromText('POINT(0 0)'), 1, 'quad_segs=1')) IS NULL THEN\n RAISE EXCEPTION 'postgis buffer failed';\n END IF;\n IF NOT ST_Within(\n ST_GeomFromText('POINT(0.5 0.5)'),\n ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))')\n ) THEN\n RAISE EXCEPTION 'postgis within failed';\n END IF;\n SELECT ST_SRID(ST_Transform(ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326), 3857)) INTO srid;\n IF srid <> 3857 THEN\n RAISE EXCEPTION 'postgis transform failed: %', srid;\n END IF;\n SELECT ST_Area(ST_Transform(ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-71.1776848522251 42.3902896512902,-71.1776843766797 42.3903701743239,-71.1775844305465 42.3903829478009,-71.1775825927231 42.3902893647987,-71.1776848522251 42.3902896512902)')), 4326), 26986)) INTO area;\n IF area <= 0 THEN\n RAISE EXCEPTION 'postgis projected area failed: %', area;\n END IF;\n SELECT ST_NumGeometries(ST_Polygonize(ARRAY[\n ST_GeomFromText('LINESTRING(0 0, 1 0)'),\n ST_GeomFromText('LINESTRING(1 0, 1 1)'),\n ST_GeomFromText('LINESTRING(1 1, 0 1)'),\n ST_GeomFromText('LINESTRING(0 1, 0 0)')\n ])) INTO polygons;\n IF polygons <> 1 THEN\n RAISE EXCEPTION 'postgis polygonize failed: %', polygons;\n END IF;\n SELECT count(*) INTO nearby\n FROM oliphaunt_postgis_points\n WHERE ST_DWithin(\n geom::geography,\n ST_SetSRID(ST_MakePoint(-71.060316, 48.432044), 4326)::geography,\n 200\n );\n IF nearby <> 2 THEN\n RAISE EXCEPTION 'postgis dwithin failed: %', nearby;\n END IF;\n SELECT count(*) INTO gdal_drivers\n FROM ST_GDALDrivers()\n WHERE short_name = 'GTiff' AND can_read AND can_write;\n IF gdal_drivers <> 1 THEN\n RAISE EXCEPTION 'postgis raster GeoTIFF driver is unavailable';\n END IF;\n SELECT ST_FromGDALRaster(\n ST_AsGDALRaster(\n ST_AddBand(\n ST_MakeEmptyRaster(2, 2, 0, 0, 1, -1, 0, 0, 4326),\n '8BUI'::text,\n 7::double precision,\n 0::double precision\n ),\n 'GTiff'\n )\n ) INTO raster_roundtrip;\n SELECT ST_Value(raster_roundtrip, 1, 1, 1) INTO raster_value;\n IF raster_value IS DISTINCT FROM 7 OR ST_SRID(raster_roundtrip) IS DISTINCT FROM 4326\n OR ST_Width(raster_roundtrip) IS DISTINCT FROM 2 OR ST_Height(raster_roundtrip) IS DISTINCT FROM 2 THEN\n RAISE EXCEPTION 'postgis raster GDAL round-trip failed: value %, srid %',\n raster_value,\n ST_SRID(raster_roundtrip);\n END IF;\nEND $$;" ], "seg": [ "DO $$ BEGIN IF '7(+-)1'::seg::text <> '6 .. 8' THEN RAISE EXCEPTION 'seg cast failed'; END IF; END $$;" From d76a1b4d43e19bc8512f56329e98bbd49a985dd9 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 09:32:40 +0000 Subject: [PATCH 21/29] fix(sources): support long upstream paths on Windows --- src/third-party/tools/fetch-sources.sh | 2 ++ .../tools/source-fetch-core.test.sh | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/third-party/tools/fetch-sources.sh b/src/third-party/tools/fetch-sources.sh index 3e1ac4184..f8842c292 100644 --- a/src/third-party/tools/fetch-sources.sh +++ b/src/third-party/tools/fetch-sources.sh @@ -86,6 +86,8 @@ fetch_source() ( source_git 60 "$checkout_root" init --quiet --template= "$candidate" source_git 60 "$candidate" config --local core.autocrlf false source_git 60 "$candidate" config --local core.eol lf + # GDAL includes paths beyond Win32 MAX_PATH; retain support after promotion. + source_git 60 "$candidate" config --local core.longpaths true source_git 60 "$candidate" remote add origin "$url" local transports=("$url") attempt transport success=false if [[ -n "$mirror" ]]; then transports+=("$mirror"); fi diff --git a/src/third-party/tools/source-fetch-core.test.sh b/src/third-party/tools/source-fetch-core.test.sh index 081dd1fa2..82949da69 100644 --- a/src/third-party/tools/source-fetch-core.test.sh +++ b/src/third-party/tools/source-fetch-core.test.sh @@ -227,6 +227,24 @@ cmp "$scratch/lf" "$checkout/source.txt" printf '%s\n' "$primary" "$primary" > "$scratch/expected" cmp "$scratch/expected" "$root/requests" +root="$scratch/git-longpaths" +init_repo "$root/upstream" trusted upstream +git -C "$root/upstream" config --local core.longpaths true +long_path='' +for part in {1..6}; do + long_path+="source-directory-with-long-upstream-fixture-path-$part/" +done +mkdir -p "$root/upstream/$long_path" +printf 'long path bytes' > "$root/upstream/${long_path}source.txt" +git -C "$root/upstream" add . +git -C "$root/upstream" commit --quiet -m 'long source path' +bun "$test_data" git-pin "$root" "$(git -C "$root/upstream" rev-parse HEAD)" +fetch "$root" "$root/pin.json" +checkout="$root/checkouts/source" +cmp "$root/upstream/${long_path}source.txt" "$checkout/${long_path}source.txt" +[[ -z "$(git -C "$checkout" status --porcelain=v1 --untracked-files=all)" ]] +FETCH_TEST_FAULT=all fetch "$root" "$root/pin.json" verify + case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) echo 'Git symlink creation proof requires a Unix host'; exit 0;; esac root="$scratch/git-links" init_repo "$root/upstream" trusted upstream From 16eea2d510552e9d868255df83a690a1351d667b Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 11:49:00 +0000 Subject: [PATCH 22/29] fix(windows): preserve static PROJ usage requirements in GDAL --- .../artifacts/native/tools/build-windows-extensions.sh | 5 +++-- src/extensions/generated/docs/extension-evidence.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/extensions/artifacts/native/tools/build-windows-extensions.sh b/src/extensions/artifacts/native/tools/build-windows-extensions.sh index 30be66c22..98521f54e 100644 --- a/src/extensions/artifacts/native/tools/build-windows-extensions.sh +++ b/src/extensions/artifacts/native/tools/build-windows-extensions.sh @@ -87,6 +87,7 @@ windows_extension_postgis_dependencies() { cp "$work_root/proj-windows-build/data/proj.db" "$prefix/share/proj/proj.db" fi fi + # PROJ's CMake target carries PROJ_DLL= for static consumers on Windows. prefix="$dependency_root/gdal" if ! (first_file "$prefix" gdal.lib) >/dev/null 2>&1 || [ ! -f "$prefix/share/gdal/gdalvrt.xsd" ]; then # Preserve the release date in the pinned header instead of probing the enclosing checkout. @@ -108,8 +109,8 @@ windows_extension_postgis_dependencies() { "-DJSONC_LIBRARY=$(native_path "$(first_file "$dependency_root/json-c" json-c.lib json-c-static.lib)")" \ -DGDAL_USE_LIBXML2=ON "-DLIBXML2_INCLUDE_DIR=$(native_path "$dependency_root/libxml2/include/libxml2")" \ "-DLIBXML2_LIBRARY=$(native_path "$(first_file "$dependency_root/libxml2" libxml2s.lib libxml2.lib xml2.lib)")" \ - -DGDAL_USE_PROJ=ON "-DPROJ_INCLUDE_DIR=$(native_path "$dependency_root/proj/include")" \ - "-DPROJ_LIBRARY=$(native_path "$(first_file "$dependency_root/proj" proj.lib libproj.lib)")" \ + -DGDAL_FIND_PACKAGE_PROJ_MODE=CONFIG \ + "-DPROJ_DIR=$(native_path "$dependency_root/proj/lib/cmake/proj")" \ -DGDAL_USE_SQLITE3=ON "-DSQLite3_INCLUDE_DIR=$(native_path "$dependency_root/sqlite/include")" \ "-DSQLite3_LIBRARY=$(native_path "$dependency_root/sqlite/lib/sqlite3.lib")" \ -DACCEPT_MISSING_SQLITE3_MUTEX_ALLOC=ON diff --git a/src/extensions/generated/docs/extension-evidence.json b/src/extensions/generated/docs/extension-evidence.json index b5896e7ad..44a0daa88 100644 --- a/src/extensions/generated/docs/extension-evidence.json +++ b/src/extensions/generated/docs/extension-evidence.json @@ -1346,7 +1346,7 @@ "collector": "src/extensions/tools/collect-wasix-evidence.sh", "kind": "exact-sha-ci" }, - "source-digest": "sha256:9bd5bb09681d1ca3f0057fc0e91cddd2b1516d87940544ae17eb9ea4f1ddffa3", + "source-digest": "sha256:49dd933337d1a0c56aacfa974d701900816f96e44e47a2ac0cd3dedc509da1be", "source-digest-inputs": [ "src/third-party/postgres/source.toml", "src/extensions/catalog/extensions.source.json", From 4dafd0325d05930636f0530276a3a40f3284cb68 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Mon, 19 Oct 2026 12:05:45 +0000 Subject: [PATCH 23/29] fix(wasix): retain raster imports after pg_textsearch rebase --- src/wasix/runtime/assets/generated/wasix-dl.exports | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wasix/runtime/assets/generated/wasix-dl.exports b/src/wasix/runtime/assets/generated/wasix-dl.exports index d5ad54bd6..10121323e 100644 --- a/src/wasix/runtime/assets/generated/wasix-dl.exports +++ b/src/wasix/runtime/assets/generated/wasix-dl.exports @@ -1036,6 +1036,7 @@ pg_detoast_datum_packed pg_detoast_datum_slice pg_do_encoding_conversion pg_encoding_max_length +pg_fprintf pg_get_indexdef_columns_extended pg_get_querydef pg_get_shmem_pagesize From ab7726f41dbade315c4f2d527c56b527f83d062b Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Tue, 20 Oct 2026 05:36:20 +0000 Subject: [PATCH 24/29] fix(extensions): repair platform qualification and resource consumers --- .github/workflows/ci.yml | 2 +- .../workflows/extension-artifacts-native.yml | 6 +- src/docs/maintainers/windows-vc-runtime.md | 8 +- .../broker/crates/windows-x64-msvc/build.rs | 8 +- .../bin/build-macos-extension-archives.sh | 1 + .../packaging/src/bin/package_resources.rs | 2 +- src/native/runtime/packaging/src/catalog.rs | 2 + .../packaging/src/extension_artifact.rs | 30 +- src/native/runtime/packaging/src/lib.rs | 288 +++++++++++++++--- src/native/runtime/packaging/src/package.rs | 59 ++-- .../platform-binary-contract.test.mts | 7 +- .../windows-vc-runtime-closure.test.mts | 25 +- .../packaging/windows-vc-runtime-policy.json | 9 +- 13 files changed, 353 insertions(+), 94 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2713eb08..4a0e95fde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1800,7 +1800,7 @@ jobs: if: ${{ contains(fromJson(needs.affected.outputs.job_targets)['native-consumers'], 'oliphaunt-kotlin:test-native-bindings') }} uses: ./.github/actions/setup-android with: - native-tools: "false" + native-tools: "true" gradle-cache-save-if: ${{ env.HEAVY_CACHE_SAVE_IF }} - name: Download Rust SDK package for installed consumer if: ${{ contains(fromJson(needs.affected.outputs.job_targets)['native-consumers'], 'oliphaunt-rust:test-consumer-runtime') }} diff --git a/.github/workflows/extension-artifacts-native.yml b/.github/workflows/extension-artifacts-native.yml index 79ffdc81c..f7f8e8b35 100644 --- a/.github/workflows/extension-artifacts-native.yml +++ b/.github/workflows/extension-artifacts-native.yml @@ -32,13 +32,13 @@ jobs: fail-fast: false matrix: ${{ fromJson(inputs.matrix) }} runs-on: ${{ matrix.runner }} - timeout-minutes: 135 + timeout-minutes: ${{ matrix.target == 'ios-xcframework' && 195 || 135 }} env: CCACHE_DIR: ${{ github.workspace }}/.ci-cache/ccache/native-extension/${{ matrix.target }} CCACHE_BASEDIR: ${{ github.workspace }} CCACHE_COMPILERCHECK: content CCACHE_COMPRESS: "true" - OLIPHAUNT_CCACHE_MAX_SIZE: ${{ matrix.target == 'ios-xcframework' && '512M' || '2G' }} + OLIPHAUNT_CCACHE_MAX_SIZE: 2G OLIPHAUNT_CCACHE_ZERO_STATS: "1" steps: - name: Checkout repository @@ -116,7 +116,7 @@ jobs: tar -xzf target/extension-inputs/host/support.tar.gz - name: Build native exact-extension artifacts - timeout-minutes: 120 + timeout-minutes: ${{ matrix.target == 'ios-xcframework' && 180 || 120 }} env: OLIPHAUNT_EXTENSION_PHASE: ${{ inputs.phase }} OLIPHAUNT_EXTENSION_PRODUCTS: ${{ matrix.extensions_csv }} diff --git a/src/docs/maintainers/windows-vc-runtime.md b/src/docs/maintainers/windows-vc-runtime.md index 2d9c09aee..9ec38a28d 100644 --- a/src/docs/maintainers/windows-vc-runtime.md +++ b/src/docs/maintainers/windows-vc-runtime.md @@ -32,7 +32,7 @@ relicense Microsoft's DLLs. - `liboliphaunt-native` is the one shared provider for native extensions. Its `provider` profile is the exact union required by the base runtime and every - supported Windows extension: `msvcp140.dll`, `vcruntime140.dll`, and + supported Windows extension: `msvcp140.dll`, `msvcp140_2.dll`, `vcruntime140.dll`, and `vcruntime140_1.dll`. Separately versioned extension packages depend on the exact native-runtime product/version and do not duplicate these bytes. - `oliphaunt-tools` and `oliphaunt-broker` are independently installable. Each @@ -52,6 +52,12 @@ PR builds only a subset of extensions. ## Release evidence +The PostGIS 3.6.4 Windows build on commit `4dafd0325d05930636f0530276a3a40f3284cb68` +imports `MSVCP140_2.dll` from `postgis-3.dll`, as recorded in +[the extension build](https://github.com/f0rr0/oliphaunt/actions/runs/39249410590/job/118130472350). +The provider profile includes this production runtime; import-derived staging +still verifies its transitive closure and preserves the original DLL bytes. + The MSVC setup step fails before a long build unless `VCToolsRedistDir` resolves the exact x64 VC145 directory and all audited production files. Windows build and packaging then: diff --git a/src/native/broker/crates/windows-x64-msvc/build.rs b/src/native/broker/crates/windows-x64-msvc/build.rs index e5d1899d9..fc2dbae7c 100644 --- a/src/native/broker/crates/windows-x64-msvc/build.rs +++ b/src/native/broker/crates/windows-x64-msvc/build.rs @@ -10,8 +10,12 @@ const KIND: &str = "broker-helper"; const TARGET: &str = "x86_64-pc-windows-msvc"; const RELATIVE: &str = "bin/oliphaunt-broker.exe"; const VC_RUNTIME_RECEIPT: &str = "bin/windows-vc-runtime.sha256"; -const ALLOWED_VC_RUNTIME_DLLS: [&str; 3] = - ["msvcp140.dll", "vcruntime140.dll", "vcruntime140_1.dll"]; +const ALLOWED_VC_RUNTIME_DLLS: [&str; 4] = [ + "msvcp140.dll", + "msvcp140_2.dll", + "vcruntime140.dll", + "vcruntime140_1.dll", +]; fn main() { emit_manifest(); diff --git a/src/native/runtime/bin/build-macos-extension-archives.sh b/src/native/runtime/bin/build-macos-extension-archives.sh index 912130e42..5c94cfc6a 100755 --- a/src/native/runtime/bin/build-macos-extension-archives.sh +++ b/src/native/runtime/bin/build-macos-extension-archives.sh @@ -398,6 +398,7 @@ dependency_archive() { json-c) printf '%s\n' "$mobile_static_dependency_root/json-c/lib/libjson-c.a" ;; libxml2) printf '%s\n' "$mobile_static_dependency_root/libxml2/lib/libxml2.a" ;; proj) printf '%s\n' "$mobile_static_dependency_root/proj/lib/libproj.a" ;; + gdal) printf '%s\n' "$mobile_static_dependency_root/gdal/lib/libgdal.a" ;; sqlite) printf '%s\n' "$mobile_static_dependency_root/sqlite/lib/libsqlite3.a" ;; *) fail "unsupported macOS extension dependency identity: $1" ;; esac diff --git a/src/native/runtime/packaging/src/bin/package_resources.rs b/src/native/runtime/packaging/src/bin/package_resources.rs index 18e940d6f..991eb4c5c 100644 --- a/src/native/runtime/packaging/src/bin/package_resources.rs +++ b/src/native/runtime/packaging/src/bin/package_resources.rs @@ -453,7 +453,7 @@ mod tests { fs::write( root.join("manifest.properties"), format!( - "packageLayout=oliphaunt-extension-artifact-v1\npgMajor=18\nsqlName=acme_ext\ncreatesExtension=no\nnativeModuleStem=\nnativeModuleFile=\nnativeTarget=\nnativeRuntimeProduct=liboliphaunt-native\nnativeRuntimeVersion={native_runtime_version}\ndependencies=\ndataFiles=\nextensionSqlFileNames=\nextensionSqlFilePrefixes=\nsharedPreloadLibraries=\nmobilePrebuilt=no\nmobileStaticArchives=\nmobileStaticDependencyArchives=\nstaticSymbolPrefix=\nstaticSymbolAliases=\nlicenseFiles=share/licenses/acme_ext/LICENSE\nlicenseProfile=external-native\nfiles=files\n" + "packageLayout=oliphaunt-extension-artifact-v1\npgMajor=18\nsqlName=acme_ext\ncreatesExtension=no\nnativeModuleStem=\nnativeModuleFile=\nnativeTarget=\nnativeRuntimeProduct=liboliphaunt-native\nnativeRuntimeVersion={native_runtime_version}\ndependencies=\ndataFiles=\nextensionSqlFileNames=\nextensionSqlFilePrefixes=\nsharedPreloadLibraries=\nmobilePrebuilt=no\nmobileStaticArchives=\nmobileStaticDependencyArchives=\nstaticSymbolPrefix=\nstaticSymbolAliases=\nlicenseFiles=share/licenses/acme_ext/LICENSE\nlicenseProfile=external-native\nsqlMembers=\nnativeModuleStems=\nfiles=files\n" ), ) .unwrap(); diff --git a/src/native/runtime/packaging/src/catalog.rs b/src/native/runtime/packaging/src/catalog.rs index fe5a5a979..27a7f260e 100644 --- a/src/native/runtime/packaging/src/catalog.rs +++ b/src/native/runtime/packaging/src/catalog.rs @@ -19,6 +19,8 @@ pub(crate) struct CatalogExtension { pub(crate) sql_name: String, pub(crate) creates_extension: bool, pub(crate) native_module_stem: Option, + pub(crate) native_module_stems: Vec, + pub(crate) sql_members: Vec, pub(crate) dependencies: Vec, pub(crate) runtime_share_data_files: Vec, pub(crate) extension_sql_file_names: Vec, diff --git a/src/native/runtime/packaging/src/extension_artifact.rs b/src/native/runtime/packaging/src/extension_artifact.rs index a2440fe81..4b9bb7c3b 100644 --- a/src/native/runtime/packaging/src/extension_artifact.rs +++ b/src/native/runtime/packaging/src/extension_artifact.rs @@ -152,8 +152,8 @@ pub(super) fn validate_prebuilt_extension_leaf_inventory( } let extension_prefix = Path::new("files/share/postgresql/extension"); - let mut has_control = false; - let mut has_install_sql = false; + let mut controls = BTreeSet::new(); + let mut installs = BTreeSet::new(); for relative in &actual { let Ok(file_name) = relative.strip_prefix(extension_prefix) else { continue; @@ -180,20 +180,28 @@ pub(super) fn validate_prebuilt_extension_leaf_inventory( ))); } expected.insert(relative.clone()); - has_control |= file_name == format!("{}.control", extension.sql_name); - has_install_sql |= extension_install_sql_file_belongs(&extension.sql_name, file_name); + for member in &extension.sql_members { + if file_name == format!("{member}.control") { + controls.insert(member); + } + if extension_install_sql_file_belongs(member, file_name) { + installs.insert(member); + } + } } - if extension.creates_extension && (!has_control || !has_install_sql) { - return Err(Error::InvalidConfig(format!( - "prebuilt extension artifact {} for '{}' must include its control file and at least one canonical base install SQL file", - root.display(), - extension.sql_name - ))); + for member in &extension.sql_members { + if !controls.contains(member) || !installs.contains(member) { + return Err(Error::InvalidConfig(format!( + "prebuilt extension artifact {} for '{}' must include its control file and at least one canonical base install SQL file", + root.display(), + member + ))); + } } for relative in &extension.data_files { expected.insert(PathBuf::from("files/share/postgresql").join(relative)); } - if let Some(module) = &extension.native_module_file { + for module in &extension.native_module_files { expected.insert(PathBuf::from("files/lib/postgresql").join(module)); let embedded = PathBuf::from("files/lib/modules").join(module); if extension diff --git a/src/native/runtime/packaging/src/lib.rs b/src/native/runtime/packaging/src/lib.rs index 6e82905ef..992857674 100644 --- a/src/native/runtime/packaging/src/lib.rs +++ b/src/native/runtime/packaging/src/lib.rs @@ -67,7 +67,7 @@ use static_registry::*; const RUNTIME_RESOURCES_SCHEMA: &str = "oliphaunt-runtime-resources-v1"; const EXTENSION_ARTIFACT_LAYOUT: &str = "oliphaunt-extension-artifact-v1"; const EXTENSION_ARTIFACT_NATIVE_RUNTIME_PRODUCT: &str = "liboliphaunt-native"; -const EXTENSION_ARTIFACT_MANIFEST_KEYS: [&str; 22] = [ +const EXTENSION_ARTIFACT_MANIFEST_KEYS: [&str; 24] = [ "packageLayout", "pgMajor", "sqlName", @@ -89,6 +89,8 @@ const EXTENSION_ARTIFACT_MANIFEST_KEYS: [&str; 22] = [ "staticSymbolAliases", "licenseFiles", "licenseProfile", + "sqlMembers", + "nativeModuleStems", "files", ]; const RUNTIME_FILES_LAYOUT: &str = "postgres-runtime-files-v1"; @@ -442,8 +444,9 @@ struct RuntimeResourceExtension { sql_name: String, native_runtime_version: Option, creates_extension: bool, + sql_members: Vec, native_module_stem: Option, - native_module_file: Option, + native_module_files: Vec, native_target: Option, dependencies: Vec, data_files: Vec, @@ -758,11 +761,9 @@ fn built_in_runtime_resource_extension(extension: Extension) -> RuntimeResourceE sql_name: catalog.sql_name.clone(), native_runtime_version: None, creates_extension: catalog.creates_extension, + sql_members: catalog.sql_members.clone(), native_module_stem: catalog.native_module_stem.clone(), - native_module_file: catalog - .native_module_stem - .as_deref() - .map(|stem| format!("{stem}{}", std::env::consts::DLL_SUFFIX)), + native_module_files: extension.native_module_files(), native_target: None, dependencies: catalog.dependencies.clone(), data_files: catalog @@ -896,6 +897,63 @@ fn load_prebuilt_extension_artifact(root: &Path) -> Result>(); + if let Some(catalog) = catalog::by_sql_name(&sql_name) { + let expected_stems = if mobile { + &primary_stems + } else { + &catalog.native_module_stems + }; + if sql_members != catalog.sql_members + || native_module_stem != catalog.native_module_stem + || native_module_stems != *expected_stems + { + return Err(Error::InvalidConfig(format!( + "manifest {} sqlMembers/nativeModuleStems must identify the owned SQL/module closure", + manifest_path.display() + ))); + } + } else if mobile && native_module_stems != primary_stems { + return Err(Error::InvalidConfig(format!( + "manifest {} nativeModuleStems must contain only the merged primary mobile module", + manifest_path.display() + ))); + } + let native_module_files = if let Some(stem) = &native_module_stem { + let suffix = native_module_file + .as_deref() + .and_then(|file| file.strip_prefix(stem)); + let Some(suffix @ (".so" | ".dylib" | ".dll")) = suffix else { + return Err(Error::InvalidConfig(format!( + "manifest {} nativeModuleFile must match nativeModuleStem and a native module suffix", + manifest_path.display() + ))); + }; + native_module_stems + .iter() + .map(|stem| format!("{stem}{suffix}")) + .collect() + } else { + Vec::new() + }; let dependencies = parse_manifest_id_list(&manifest_path, &manifest, "dependencies")?; let data_files = parse_manifest_relative_path_list(&manifest_path, &manifest, "dataFiles")?; let extension_sql_file_names = @@ -968,8 +1026,9 @@ fn load_prebuilt_extension_artifact(root: &Path) -> Result bool { - (extension.creates_extension - && (file_name == format!("{}.control", extension.sql_name) - || file_name == format!("{}.sql", extension.sql_name) - || (file_name.starts_with(&format!("{}--", extension.sql_name)) - && file_name.ends_with(".sql")))) - || extension - .extension_sql_file_names - .iter() - .any(|name| name == file_name) + extension.sql_members.iter().any(|member| { + file_name == format!("{member}.control") + || file_name == format!("{member}.sql") + || (file_name.starts_with(&format!("{member}--")) && file_name.ends_with(".sql")) + }) || extension + .extension_sql_file_names + .iter() + .any(|name| name == file_name) || (file_name.ends_with(".sql") && extension .extension_sql_file_prefixes @@ -1034,6 +1092,153 @@ mod tests { use std::time::{SystemTime, UNIX_EPOCH}; use tar::EntryType; + #[test] + fn postgis_artifact_stages_complete_sql_and_platform_module_closures() { + for mobile in [false, true] { + let temp = tempfile::tempdir().unwrap(); + let artifact = temp.path().join("postgis"); + let target = if mobile { + "ios-xcframework" + } else { + "linux-x64-gnu" + }; + write_prebuilt_extension_artifact( + &artifact, + "postgis", + "postgis-3", + "oliphaunt_static_postgis_3", + "data/postgis.rules", + mobile, + ); + let catalog = catalog::for_extension(Extension::POSTGIS); + let stems = if mobile { + vec!["postgis-3".to_owned()] + } else { + catalog.native_module_stems.clone() + }; + let manifest_path = artifact.join("manifest.properties"); + let manifest = fs::read_to_string(&manifest_path) + .unwrap() + .replace( + "nativeTarget=test-target", + &format!("nativeTarget={target}"), + ) + .replace( + "sqlMembers=postgis\n", + &format!("sqlMembers={}\n", catalog.sql_members.join(",")), + ) + .replace( + "nativeModuleStems=postgis-3\n", + &format!("nativeModuleStems={}\n", stems.join(",")), + ); + fs::write(&manifest_path, &manifest).unwrap(); + for member in &catalog.sql_members { + for suffix in [".control", "--1.0.sql"] { + write_file( + &artifact + .join(format!("files/share/postgresql/extension/{member}{suffix}")), + b"fixture\n", + ); + } + } + for stem in &stems { + for directory in ["postgresql", "modules"] { + write_file( + &artifact.join(format!( + "files/lib/{directory}/{stem}{}", + std::env::consts::DLL_SUFFIX + )), + b"fixture-module\n", + ); + } + } + let extension = load_prebuilt_extension_artifact(&artifact).unwrap(); + let extensions = vec![extension]; + let registered = if mobile { + vec!["postgis-3".to_owned()] + } else { + vec![] + }; + let metadata = mobile_static_registry_metadata(&extensions, ®istered).unwrap(); + let runtime_files = temp.path().join("runtime"); + copy_prebuilt_extension_artifacts(&runtime_files, &extensions, Some(target), &metadata) + .unwrap(); + for member in &catalog.sql_members { + assert!( + runtime_files + .join(format!("share/postgresql/extension/{member}.control")) + .is_file() + ); + assert!( + runtime_files + .join(format!("share/postgresql/extension/{member}--1.0.sql")) + .is_file() + ); + } + for stem in &stems { + assert_eq!( + runtime_files + .join(format!( + "lib/postgresql/{stem}{}", + std::env::consts::DLL_SUFFIX + )) + .is_file(), + !mobile + ); + } + for (field, incomplete) in [ + ("sqlMembers", "postgis"), + ( + "nativeModuleStems", + if mobile { + "postgis-3,postgis_raster-3" + } else { + "postgis-3" + }, + ), + ] { + let current = manifest + .lines() + .find(|line| line.starts_with(&format!("{field}="))) + .unwrap(); + fs::write( + &manifest_path, + manifest.replace(current, &format!("{field}={incomplete}")), + ) + .unwrap(); + assert!(load_prebuilt_extension_artifact(&artifact).is_err()); + fs::write( + &manifest_path, + manifest.replace(&format!("{current}\n"), ""), + ) + .unwrap(); + assert!(load_prebuilt_extension_artifact(&artifact).is_err()); + } + fs::write(&manifest_path, &manifest).unwrap(); + for suffix in [".control", "--1.0.sql"] { + let file = artifact.join(format!( + "files/share/postgresql/extension/postgis_raster{suffix}" + )); + fs::remove_file(&file).unwrap(); + assert!( + load_prebuilt_extension_artifact(&artifact) + .unwrap_err() + .to_string() + .contains("postgis_raster") + ); + write_file(&file, b"fixture\n"); + } + if !mobile { + fs::remove_file(artifact.join(format!( + "files/lib/modules/postgis_raster-3{}", + std::env::consts::DLL_SUFFIX + ))) + .unwrap(); + assert!(load_prebuilt_extension_artifact(&artifact).is_err()); + } + } + } + #[test] fn logical_tree_digest_uses_portable_path_order() { let root = tempfile::tempdir().unwrap(); @@ -2875,6 +3080,8 @@ staticSymbolPrefix={static_symbol_prefix} staticSymbolAliases= licenseFiles=share/licenses/{sql_name}/LICENSE licenseProfile=external-native +sqlMembers={sql_name} +nativeModuleStems={module_stem} files=files ", if mobile_prebuilt { "yes" } else { "no" } @@ -2946,6 +3153,9 @@ files=files ) { let module_file = format!("{module_stem}.so"); let license_value = license_files.join(","); + let catalog = catalog::by_sql_name(sql_name).unwrap(); + let sql_members = catalog.sql_members.join(","); + let native_module_stems = catalog.native_module_stems.join(","); write_file( &root.join("manifest.properties"), format!( @@ -2971,6 +3181,8 @@ staticSymbolPrefix= staticSymbolAliases= licenseFiles={license_value} licenseProfile={} +sqlMembers={sql_members} +nativeModuleStems={native_module_stems} files=files ", profile.as_str() @@ -3004,26 +3216,30 @@ files=files format!("fixture upstream license {license}\n").as_bytes(), ); } - write_file( - &root - .join("files/share/postgresql/extension") - .join(format!("{sql_name}.control")), - b"comment = 'profile fixture'\n", - ); - write_file( - &root - .join("files/share/postgresql/extension") - .join(format!("{sql_name}--1.0.sql")), - b"SELECT 1;\n", - ); - write_file( - &root.join("files/lib/postgresql").join(&module_file), - b"fixture server module\n", - ); - write_file( - &root.join("files/lib/modules").join(module_file), - b"fixture embedded module\n", - ); + for member in &catalog.sql_members { + write_file( + &root + .join("files/share/postgresql/extension") + .join(format!("{member}.control")), + b"comment = 'profile fixture'\n", + ); + write_file( + &root + .join("files/share/postgresql/extension") + .join(format!("{member}--1.0.sql")), + b"SELECT 1;\n", + ); + } + for stem in &catalog.native_module_stems { + write_file( + &root.join("files/lib/postgresql").join(format!("{stem}.so")), + b"fixture server module\n", + ); + write_file( + &root.join("files/lib/modules").join(format!("{stem}.so")), + b"fixture embedded module\n", + ); + } } fn write_tar_archive_from_dir(archive_path: &Path, source: &Path, prefix: &str) { diff --git a/src/native/runtime/packaging/src/package.rs b/src/native/runtime/packaging/src/package.rs index 806580f94..4f0d98a71 100644 --- a/src/native/runtime/packaging/src/package.rs +++ b/src/native/runtime/packaging/src/package.rs @@ -167,27 +167,20 @@ fn prune_built_in_extension_module_files( ) -> Result<()> { let mut selected_modules = BTreeSet::new(); for extension in selected_built_in { - if extension_dynamic_module_required(extension, extension_target, mobile_static_registry)? - && let Some(module) = &extension.native_module_file - { - selected_modules.insert(module.clone()); + if extension_dynamic_module_required(extension, extension_target, mobile_static_registry)? { + selected_modules.extend(extension.native_module_files.iter().cloned()); } } for extension in Extension::ALL { - let Some(stem) = catalog::for_extension(*extension) - .native_module_stem - .as_deref() - else { - continue; - }; - let module = format!("{stem}{}", std::env::consts::DLL_SUFFIX); - if selected_modules.contains(&module) { - continue; + for module in extension.native_module_files() { + if selected_modules.contains(&module) { + continue; + } + remove_runtime_file_if_present( + runtime_files, + &PathBuf::from("lib/postgresql").join(module), + )?; } - remove_runtime_file_if_present( - runtime_files, - &PathBuf::from("lib/postgresql").join(module), - )?; } Ok(()) } @@ -221,7 +214,7 @@ fn prune_prebuilt_extension_base_artifact_paths( &PathBuf::from("share/postgresql").join(relative), )?; } - if let Some(module) = &extension.native_module_file { + for module in &extension.native_module_files { remove_runtime_file_if_present( runtime_files, &PathBuf::from("lib/postgresql").join(module), @@ -259,15 +252,14 @@ pub(super) fn copy_prebuilt_extension_artifacts( extension_target, mobile_static_registry, )? { - let Some(module) = &extension.native_module_file else { - continue; - }; - copy_artifact_runtime_file_to( - files_root, - runtime_files, - &PathBuf::from("lib/modules").join(module), - &PathBuf::from("lib/postgresql").join(module), - )?; + for module in &extension.native_module_files { + copy_artifact_runtime_file_to( + files_root, + runtime_files, + &PathBuf::from("lib/modules").join(module), + &PathBuf::from("lib/postgresql").join(module), + )?; + } } } Ok(()) @@ -471,12 +463,11 @@ fn extension_asset_paths( paths.insert(relative.clone()); } if extension_dynamic_module_required(extension, extension_target, mobile_static_registry)? { - let Some(module) = &extension.native_module_file else { - continue; - }; - let relative = PathBuf::from("lib/postgresql").join(module); - require_report_file(runtime_files, &relative)?; - paths.insert(relative); + for module in &extension.native_module_files { + let relative = PathBuf::from("lib/postgresql").join(module); + require_report_file(runtime_files, &relative)?; + paths.insert(relative); + } } } Ok(paths) @@ -501,7 +492,7 @@ fn extension_dynamic_module_required( if let RuntimeResourceExtensionSource::Prebuilt { .. } = &extension.source { validate_prebuilt_extension_target(extension, extension_target)?; } - Ok(extension.native_module_file.is_some()) + Ok(!extension.native_module_files.is_empty()) } fn prebuilt_extension_dynamic_module_required( diff --git a/tools/packaging/platform-binary-contract.test.mts b/tools/packaging/platform-binary-contract.test.mts index 2c65b2ff8..ca93c4f76 100644 --- a/tools/packaging/platform-binary-contract.test.mts +++ b/tools/packaging/platform-binary-contract.test.mts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, test } from 'bun:test'; -import { mkdtemp, mkdir, rm, symlink, writeFile } from 'node:fs/promises'; +import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; @@ -508,6 +508,7 @@ describe('PE32+ architecture and self-contained runtime imports', () => { [ entry('bin/oliphaunt.dll', pe()), entry('bin/msvcp140.dll', pe()), + entry('bin/msvcp140_2.dll', pe()), entry('bin/vcruntime140.dll', pe()), entry('bin/vcruntime140_1.dll', pe()), ], @@ -519,7 +520,9 @@ describe('PE32+ architecture and self-contained runtime imports', () => { [entry('bin/oliphaunt.dll', pe()), entry('bin/vcruntime140.dll', pe())], { target: 'windows-x64-msvc', windowsVcRuntimeProfile: 'provider' }, ), - ).toThrow(/provider VC runtime profile is missing msvcp140\.dll, vcruntime140_1\.dll/u); + ).toThrow( + /provider VC runtime profile is missing msvcp140\.dll, msvcp140_2\.dll, vcruntime140_1\.dll/u, + ); }); test('rejects x86, PE32, malformed import descriptors, and truncated files', () => { diff --git a/tools/packaging/windows-vc-runtime-closure.test.mts b/tools/packaging/windows-vc-runtime-closure.test.mts index f17a56979..294f1627d 100644 --- a/tools/packaging/windows-vc-runtime-closure.test.mts +++ b/tools/packaging/windows-vc-runtime-closure.test.mts @@ -115,6 +115,29 @@ if (['prepare', 'verify'].includes(process.argv[2])) { } describe('Windows VC runtime dependency closure', () => { + test('stages the PostGIS C++ math runtime and its transitive imports', () => { + const { root, redist, source } = fixture(); + try { + const carrier = path.join(root, 'carrier'); + const bin = path.join(carrier, 'bin'); + mkdirSync(bin, { recursive: true }); + writeFileSync(path.join(carrier, 'postgis-3.dll'), pe({ imports: ['MSVCP140_2.dll'] })); + writeFileSync(path.join(source, 'msvcp140_2.dll'), pe({ imports: ['VCRUNTIME140.dll'] })); + const result = stageWindowsVcRuntime({ + root: carrier, + redistRoot: redist, + destinations: [bin], + }); + assert.deepEqual(result.required, ['msvcp140_2.dll', 'vcruntime140.dll']); + assert.deepEqual( + verifyWindowsVcRuntimeClosure({ root: carrier, searchRoots: [bin] }).required, + result.required, + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + test('parses normal and delay-load imports without an external Windows tool', () => { const parsed = inspectPortableExecutable( pe({ @@ -151,7 +174,7 @@ describe('Windows VC runtime dependency closure', () => { ); assert.throws( () => verifyWindowsVcRuntimeClosure({ root: carrier, searchRoots: [bin] }), - /extra \[msvcp140\.dll, vcruntime140_1\.dll\]/u, + /extra \[msvcp140\.dll, msvcp140_2\.dll, vcruntime140_1\.dll\]/u, ); assert.deepEqual( verifyWindowsVcRuntimeClosure({ root: carrier, searchRoots: [bin], profile: 'provider' }) diff --git a/tools/packaging/windows-vc-runtime-policy.json b/tools/packaging/windows-vc-runtime-policy.json index 07c52c835..c5341535d 100644 --- a/tools/packaging/windows-vc-runtime-policy.json +++ b/tools/packaging/windows-vc-runtime-policy.json @@ -1,7 +1,12 @@ { - "windowsVcRuntimeDlls": ["msvcp140.dll", "vcruntime140.dll", "vcruntime140_1.dll"], + "windowsVcRuntimeDlls": [ + "msvcp140.dll", + "msvcp140_2.dll", + "vcruntime140.dll", + "vcruntime140_1.dll" + ], "windowsVcRuntimeProfiles": { - "provider": ["msvcp140.dll", "vcruntime140.dll", "vcruntime140_1.dll"] + "provider": ["msvcp140.dll", "msvcp140_2.dll", "vcruntime140.dll", "vcruntime140_1.dll"] }, "windowsVcRuntimeReceipt": "windows-vc-runtime.sha256" } From 503e4dce04e5d08b18b32a8baa2975ad1295bfe1 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Tue, 20 Oct 2026 08:05:29 +0000 Subject: [PATCH 25/29] fix(ci): order pgwire tests before rebuilding the Windows CLI --- src/wasix/pgwire-server/moon.yml | 3 ++- tools/ci/ci-plan-node-products.test.mts | 6 +++++- tools/ci/ci-plan-test-inputs.mts | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/wasix/pgwire-server/moon.yml b/src/wasix/pgwire-server/moon.yml index 115dbe1e7..a090be32d 100644 --- a/src/wasix/pgwire-server/moon.yml +++ b/src/wasix/pgwire-server/moon.yml @@ -56,7 +56,8 @@ tasks: test-aot: command: "bash src/wasix/pgwire-server/tools/test-aot.sh" tags: ["runtime", "integration", "requires-rust", "ci-liboliphaunt-wasix-aot"] - deps: [{target: "cargo-sources", cacheStrategy: hash}, "liboliphaunt-wasix:runtime-aot", "extension-artifacts-wasix:build-aot"] + # Windows cannot rebuild the CLI with extension features while integration tests run it. + deps: [{target: "cargo-sources", cacheStrategy: hash}, "liboliphaunt-wasix:runtime-aot", "extension-artifacts-wasix:build-aot", "oliphaunt-pgwire-server:test-integration"] inputs: ["@group(sources)", "tools/test-aot.sh", "/Cargo.toml", "/Cargo.lock"] options: cache: false diff --git a/tools/ci/ci-plan-node-products.test.mts b/tools/ci/ci-plan-node-products.test.mts index 308371e73..5d0049921 100644 --- a/tools/ci/ci-plan-node-products.test.mts +++ b/tools/ci/ci-plan-node-products.test.mts @@ -437,7 +437,7 @@ test('WASIX SDK changes select artifact consumption without invalidating AOT com test('pgwire runtime consumers execute in the AOT job with their required producers', () => { for (const target of [ 'oliphaunt-pgwire-server:test-integration', - 'oliphaunt-pgwire-server:test-aot', + taskRoots.oliphauntPgwireServerTestAot, ]) { const selected = new Set([target]); const tasks = requiredTasksForAffected(selected); @@ -447,6 +447,10 @@ test('pgwire runtime consumers execute in the AOT job with their required produc assert(tasks.has('liboliphaunt-wasix:runtime-aot')); if (target.endsWith(':test-aot')) { assert(tasks.has('extension-artifacts-wasix:build-aot')); + assert( + actionTargets(target).has('oliphaunt-pgwire-server:test-integration'), + 'finish CLI integration before AOT tests rebuild the running Windows executable', + ); } } }); diff --git a/tools/ci/ci-plan-test-inputs.mts b/tools/ci/ci-plan-test-inputs.mts index 10a3e0759..18e97dbd7 100644 --- a/tools/ci/ci-plan-test-inputs.mts +++ b/tools/ci/ci-plan-test-inputs.mts @@ -115,6 +115,7 @@ export const paths = { }; export const taskRoots = { oliphauntWasixRustTestAot: 'oliphaunt-wasix-rust:test-aot', + oliphauntPgwireServerTestAot: 'oliphaunt-pgwire-server:test-aot', liboliphauntWasixRuntimeAot: 'liboliphaunt-wasix:runtime-aot', liboliphauntWasixRuntimePortable: 'liboliphaunt-wasix:runtime-portable', oliphauntSwiftPackageBindings: 'oliphaunt-swift:package-bindings', From 56f0a59706f42e2ec5bcb96871ce32494eb7ca92 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Tue, 20 Oct 2026 08:44:14 +0000 Subject: [PATCH 26/29] fix(mobile): bind PostGIS companions to the static module --- .../tools/extension-artifact-packager.mts | 22 ++++++++++++--- .../extension-artifact-packager.test.mts | 23 ++++++++++++++-- src/native/runtime/packaging/src/lib.rs | 27 +++++++++++++++++++ .../runtime/packaging/src/static_registry.rs | 5 ++-- 4 files changed, 69 insertions(+), 8 deletions(-) diff --git a/src/extensions/artifacts/native/tools/extension-artifact-packager.mts b/src/extensions/artifacts/native/tools/extension-artifact-packager.mts index 1fa077368..80c7d3464 100755 --- a/src/extensions/artifacts/native/tools/extension-artifact-packager.mts +++ b/src/extensions/artifacts/native/tools/extension-artifact-packager.mts @@ -817,8 +817,6 @@ async function extensionSqlMetadata(sqlName, target) { } } const closure = extensionNativeArtifactClosure(sqlName, target); - // Mobile combines companion objects into its primary static module. - if (!DESKTOP_NATIVE_TARGETS.has(target)) closure.modules = []; return { names, prefixes, ...closure }; } @@ -867,6 +865,12 @@ async function copySqlFiles(args, artifactRoot, artifactFiles, extraSql) { let copiedControl = false; let copiedSql = false; const copiedFileNames = []; + const mergedModuleStems = + MOBILE_NATIVE_TARGETS.has(args.nativeTarget) && args.nativeModuleStem + ? extraSql.modules + .map((module) => module.replace(/\.(so|dylib|dll)$/u, '')) + .filter((stem) => stem !== args.nativeModuleStem) + : []; const entries = (await fs.readdir(sourceDir)).sort(); for (const entry of entries) { if (!extensionSqlFileBelongs(args.sqlName, entry, extraSql, args.createsExtension)) { @@ -879,7 +883,17 @@ async function copySqlFiles(args, artifactRoot, artifactFiles, extraSql) { } else if (isCanonicalExtensionInstallSql(entry, args.sqlName)) { copiedSql = true; } - await copyFileChecked(args.runtime, path.join(sourceDir, entry), path.join(targetDir, entry)); + const destination = path.join(targetDir, entry); + await copyFileChecked(args.runtime, path.join(sourceDir, entry), destination); + if (mergedModuleStems.length > 0) { + // Mobile artifacts use host SQL but merge companion code into one static module. + const original = await fs.readFile(destination, 'utf8'); + let sql = original; + for (const stem of mergedModuleStems) { + sql = sql.replaceAll(`'$libdir/${stem}'`, `'$libdir/${args.nativeModuleStem}'`); + } + if (sql !== original) await fs.writeFile(destination, sql); + } } if (args.createsExtension && (!copiedControl || !copiedSql)) { fail( @@ -1005,7 +1019,7 @@ export async function writeArtifactDirectory(artifactRoot, args) { for (const dataFile of metadata.dataFiles) { await copyRuntimeRelativeFile(args.runtime, filesRoot, `share/postgresql/${dataFile}`); } - const modules = new Set(extraSql.modules); + const modules = new Set(DESKTOP_NATIVE_TARGETS.has(args.nativeTarget) ? extraSql.modules : []); if (metadata.nativeModuleFile.length > 0) modules.add(metadata.nativeModuleFile); for (const module of modules) { await copyRuntimeRelativeFile(args.runtime, filesRoot, `lib/postgresql/${module}`); diff --git a/src/extensions/artifacts/native/tools/extension-artifact-packager.test.mts b/src/extensions/artifacts/native/tools/extension-artifact-packager.test.mts index da0925a24..85f3d9a4e 100644 --- a/src/extensions/artifacts/native/tools/extension-artifact-packager.test.mts +++ b/src/extensions/artifacts/native/tools/extension-artifact-packager.test.mts @@ -48,6 +48,7 @@ for (const [target, suffix] of [ ['macos-arm64', '.dylib'], ['windows-x64-msvc', '.dll'], ['android-arm64-v8a', '.so'], + ['android-x86_64', '.so'], ['ios-xcframework', '.dylib'], ]) { test(`PostGIS ${target} freezes and stages its declared companion closure`, async () => { @@ -86,13 +87,17 @@ for (const [target, suffix] of [ }; try { for (const name of controls) { + const module = stems.find((stem) => stem === `${name}-3`); await write( path.join(runtime, `share/postgresql/extension/${name}.control`), - `default_version = '${version}'\n`, + `default_version = '${version}'\n` + + (module ? `module_pathname = '$libdir/${module}'\n` : ''), ); await write( path.join(runtime, `share/postgresql/extension/${name}--${version}.sql`), - `-- ${name} install\n`, + module + ? `CREATE FUNCTION ${name}_fixture() RETURNS text AS '$libdir/${module}', '${name}_fixture' LANGUAGE C;\n` + : `-- ${name} install\n`, ); } for (const stem of stems) { @@ -112,6 +117,20 @@ for (const [target, suffix] of [ const entries = await entriesAt(artifact); const manifest = entries.get('manifest.properties').data.toString(); assert(manifest.includes(`sqlMembers=${[...controls].sort().join(',')}\n`)); + for (const name of controls) { + const module = stems.find((stem) => stem === `${name}-3`); + for (const suffix of ['.control', `--${version}.sql`]) { + const relative = `share/postgresql/extension/${name}${suffix}`; + const original = await fs.readFile(path.join(runtime, relative), 'utf8'); + assert.equal( + entries.get(`files/${relative}`).data.toString(), + mobile && module + ? original.replaceAll(`'$libdir/${module}'`, "'$libdir/postgis-3'") + : original, + ); + if (module) assert(original.includes(`'$libdir/${module}'`)); + } + } if (mobile) { assert(manifest.includes('nativeModuleStems=postgis-3\n')); assertExtensionArtifactClosure( diff --git a/src/native/runtime/packaging/src/lib.rs b/src/native/runtime/packaging/src/lib.rs index 992857674..830ca1a8d 100644 --- a/src/native/runtime/packaging/src/lib.rs +++ b/src/native/runtime/packaging/src/lib.rs @@ -1140,6 +1140,24 @@ mod tests { b"fixture\n", ); } + let symbol = match member.as_str() { + "postgis" => Some("postgis_version"), + "postgis_raster" => Some("RASTER_in"), + "postgis_topology" => Some("Topology_LoadTopoGeometry"), + _ => None, + }; + if let Some(symbol) = symbol { + let stem = if mobile { "postgis" } else { member }; + write_file( + &artifact.join(format!( + "files/share/postgresql/extension/{member}--1.0.sql" + )), + format!( + "CREATE FUNCTION {symbol}() RETURNS text AS '$libdir/{stem}-3', '{symbol}' LANGUAGE C;\n" + ) + .as_bytes(), + ); + } } for stem in &stems { for directory in ["postgresql", "modules"] { @@ -1163,6 +1181,15 @@ mod tests { let runtime_files = temp.path().join("runtime"); copy_prebuilt_extension_artifacts(&runtime_files, &extensions, Some(target), &metadata) .unwrap(); + if mobile { + let modules = + static_registry_modules(&runtime_files, &extensions, &metadata).unwrap(); + assert_eq!(modules.len(), 1); + assert_eq!( + modules[0].sql_symbols, + ["RASTER_in", "Topology_LoadTopoGeometry", "postgis_version"] + ); + } for member in &catalog.sql_members { assert!( runtime_files diff --git a/src/native/runtime/packaging/src/static_registry.rs b/src/native/runtime/packaging/src/static_registry.rs index 9c9a38191..5fea0a7d5 100644 --- a/src/native/runtime/packaging/src/static_registry.rs +++ b/src/native/runtime/packaging/src/static_registry.rs @@ -244,7 +244,6 @@ pub(super) fn collect_extension_sql_symbols( return Ok(Vec::new()); } let extension_dir = runtime_dir.join("share/postgresql/extension"); - let prefix = format!("{}--", extension.sql_name); let module_stem = extension.native_module_stem.as_deref().ok_or_else(|| { Error::InvalidConfig(format!( "selected extension {} has no native module stem for static SQL symbol collection", @@ -261,7 +260,9 @@ pub(super) fn collect_extension_sql_symbols( })?; let file_name = entry.file_name(); let file_name = file_name.to_string_lossy(); - if !file_name.starts_with(&prefix) || !file_name.ends_with(".sql") { + if !file_name.ends_with(".sql") + || !runtime_extension_sql_file_belongs(extension, &file_name) + { continue; } found_sql_file = true; From c345274d47800402f77654e42d5187697176e0d2 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Tue, 20 Oct 2026 09:00:23 +0000 Subject: [PATCH 27/29] fix(test): include companion SQL members in mobile catalog proofs --- .../react-native-expo/src/SmokeDashboard.tsx | 7 +- .../src/generated/extension-smoke.ts | 119 ++++++++++++++++++ .../react-native-expo/src/mobile-smoke.ts | 3 +- .../tools/mobile-extension-proof.test.mts | 35 +++++- .../tools/extension-projections.mts | 3 +- 5 files changed, 162 insertions(+), 5 deletions(-) diff --git a/src/examples/native/react-native-expo/src/SmokeDashboard.tsx b/src/examples/native/react-native-expo/src/SmokeDashboard.tsx index 234588110..04d042790 100644 --- a/src/examples/native/react-native-expo/src/SmokeDashboard.tsx +++ b/src/examples/native/react-native-expo/src/SmokeDashboard.tsx @@ -529,7 +529,7 @@ function requiredPublicEnvironment(name: string, value: string | undefined): str } function mobileReleaseExtensionProofPlan() { - if (packagedExtensions === undefined) { + if (typeof packagedExtensions !== 'string') { throw new Error('installed mobile qualification requires EXPO_PUBLIC_OLIPHAUNT_EXTENSIONS'); } const requested = packagedExtensions @@ -544,7 +544,9 @@ function mobileReleaseExtensionProofPlan() { selected.has(extension.sqlName), ); if (plan.length !== selected.size) { - const known = new Set(GENERATED_MOBILE_EXTENSION_PLAN.map((extension) => extension.sqlName)); + const known = new Set( + GENERATED_MOBILE_EXTENSION_PLAN.map((extension) => extension.sqlName), + ); const unknown = requested.filter((extension) => !known.has(extension)); throw new Error(`unknown packaged mobile extensions: ${unknown.join(',')}`); } @@ -558,6 +560,7 @@ function mobileReleaseExtensionProofPlan() { return { sqlName: extension.sqlName, createsExtension: extension.createsExtension, + sqlMembers: extension.sqlMembers, selectedExtensionDependencies: extension.selectedExtensionDependencies, activationSql: activationSqlForExtension(extension), smokeStatements, diff --git a/src/examples/native/react-native-expo/src/generated/extension-smoke.ts b/src/examples/native/react-native-expo/src/generated/extension-smoke.ts index 906914688..2060f7611 100644 --- a/src/examples/native/react-native-expo/src/generated/extension-smoke.ts +++ b/src/examples/native/react-native-expo/src/generated/extension-smoke.ts @@ -4,6 +4,7 @@ export type GeneratedMobileExtensionProof = { readonly sqlName: string; readonly createsExtension: boolean; + readonly sqlMembers: readonly string[]; readonly selectedExtensionDependencies: readonly string[]; }; @@ -13,51 +14,79 @@ export const GENERATED_MOBILE_EXTENSION_PLAN = [ { "sqlName": "amcheck", "createsExtension": true, + "sqlMembers": [ + "amcheck" + ], "selectedExtensionDependencies": [] }, { "sqlName": "auto_explain", "createsExtension": false, + "sqlMembers": [], "selectedExtensionDependencies": [] }, { "sqlName": "bloom", "createsExtension": true, + "sqlMembers": [ + "bloom" + ], "selectedExtensionDependencies": [] }, { "sqlName": "btree_gin", "createsExtension": true, + "sqlMembers": [ + "btree_gin" + ], "selectedExtensionDependencies": [] }, { "sqlName": "btree_gist", "createsExtension": true, + "sqlMembers": [ + "btree_gist" + ], "selectedExtensionDependencies": [] }, { "sqlName": "citext", "createsExtension": true, + "sqlMembers": [ + "citext" + ], "selectedExtensionDependencies": [] }, { "sqlName": "cube", "createsExtension": true, + "sqlMembers": [ + "cube" + ], "selectedExtensionDependencies": [] }, { "sqlName": "dict_int", "createsExtension": true, + "sqlMembers": [ + "dict_int" + ], "selectedExtensionDependencies": [] }, { "sqlName": "dict_xsyn", "createsExtension": true, + "sqlMembers": [ + "dict_xsyn" + ], "selectedExtensionDependencies": [] }, { "sqlName": "earthdistance", "createsExtension": true, + "sqlMembers": [ + "earthdistance" + ], "selectedExtensionDependencies": [ "cube" ] @@ -65,106 +94,172 @@ export const GENERATED_MOBILE_EXTENSION_PLAN = [ { "sqlName": "file_fdw", "createsExtension": true, + "sqlMembers": [ + "file_fdw" + ], "selectedExtensionDependencies": [] }, { "sqlName": "fuzzystrmatch", "createsExtension": true, + "sqlMembers": [ + "fuzzystrmatch" + ], "selectedExtensionDependencies": [] }, { "sqlName": "hstore", "createsExtension": true, + "sqlMembers": [ + "hstore" + ], "selectedExtensionDependencies": [] }, { "sqlName": "intarray", "createsExtension": true, + "sqlMembers": [ + "intarray" + ], "selectedExtensionDependencies": [] }, { "sqlName": "isn", "createsExtension": true, + "sqlMembers": [ + "isn" + ], "selectedExtensionDependencies": [] }, { "sqlName": "lo", "createsExtension": true, + "sqlMembers": [ + "lo" + ], "selectedExtensionDependencies": [] }, { "sqlName": "ltree", "createsExtension": true, + "sqlMembers": [ + "ltree" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pageinspect", "createsExtension": true, + "sqlMembers": [ + "pageinspect" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pg_buffercache", "createsExtension": true, + "sqlMembers": [ + "pg_buffercache" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pg_freespacemap", "createsExtension": true, + "sqlMembers": [ + "pg_freespacemap" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pg_hashids", "createsExtension": true, + "sqlMembers": [ + "pg_hashids" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pg_ivm", "createsExtension": true, + "sqlMembers": [ + "pg_ivm" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pg_surgery", "createsExtension": true, + "sqlMembers": [ + "pg_surgery" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pg_textsearch", "createsExtension": true, + "sqlMembers": [ + "pg_textsearch" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pg_trgm", "createsExtension": true, + "sqlMembers": [ + "pg_trgm" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pg_uuidv7", "createsExtension": true, + "sqlMembers": [ + "pg_uuidv7" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pg_visibility", "createsExtension": true, + "sqlMembers": [ + "pg_visibility" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pg_walinspect", "createsExtension": true, + "sqlMembers": [ + "pg_walinspect" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pgcrypto", "createsExtension": true, + "sqlMembers": [ + "pgcrypto" + ], "selectedExtensionDependencies": [] }, { "sqlName": "pgtap", "createsExtension": true, + "sqlMembers": [ + "pgtap" + ], "selectedExtensionDependencies": [] }, { "sqlName": "postgis", "createsExtension": true, + "sqlMembers": [ + "postgis", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology" + ], "selectedExtensionDependencies": [ "fuzzystrmatch" ] @@ -172,41 +267,65 @@ export const GENERATED_MOBILE_EXTENSION_PLAN = [ { "sqlName": "seg", "createsExtension": true, + "sqlMembers": [ + "seg" + ], "selectedExtensionDependencies": [] }, { "sqlName": "tablefunc", "createsExtension": true, + "sqlMembers": [ + "tablefunc" + ], "selectedExtensionDependencies": [] }, { "sqlName": "tcn", "createsExtension": true, + "sqlMembers": [ + "tcn" + ], "selectedExtensionDependencies": [] }, { "sqlName": "tsm_system_rows", "createsExtension": true, + "sqlMembers": [ + "tsm_system_rows" + ], "selectedExtensionDependencies": [] }, { "sqlName": "tsm_system_time", "createsExtension": true, + "sqlMembers": [ + "tsm_system_time" + ], "selectedExtensionDependencies": [] }, { "sqlName": "unaccent", "createsExtension": true, + "sqlMembers": [ + "unaccent" + ], "selectedExtensionDependencies": [] }, { "sqlName": "uuid-ossp", "createsExtension": true, + "sqlMembers": [ + "uuid-ossp" + ], "selectedExtensionDependencies": [] }, { "sqlName": "vector", "createsExtension": true, + "sqlMembers": [ + "vector" + ], "selectedExtensionDependencies": [] } ] as const satisfies readonly GeneratedMobileExtensionProof[]; diff --git a/src/examples/native/react-native-expo/src/mobile-smoke.ts b/src/examples/native/react-native-expo/src/mobile-smoke.ts index e8b9e0b4e..1b1f19501 100644 --- a/src/examples/native/react-native-expo/src/mobile-smoke.ts +++ b/src/examples/native/react-native-expo/src/mobile-smoke.ts @@ -3,6 +3,7 @@ import { PostgresError, type OliphauntDatabase, type QueryResult } from '@olipha export type MobileReleaseExtensionProof = { readonly sqlName: string; readonly createsExtension: boolean; + readonly sqlMembers: readonly string[]; readonly selectedExtensionDependencies: readonly string[]; readonly activationSql: readonly string[]; readonly smokeStatements: readonly string[]; @@ -304,7 +305,7 @@ export async function runMobileReleaseExtensionProof( async () => { const expected = plan .filter((extension) => extension.createsExtension) - .map((extension) => extension.sqlName) + .flatMap((extension) => extension.sqlMembers) .sort() .join(','); const actual = await scalar( diff --git a/src/examples/native/react-native-expo/tools/mobile-extension-proof.test.mts b/src/examples/native/react-native-expo/tools/mobile-extension-proof.test.mts index 5a457d9e7..867dabd1c 100644 --- a/src/examples/native/react-native-expo/tools/mobile-extension-proof.test.mts +++ b/src/examples/native/react-native-expo/tools/mobile-extension-proof.test.mts @@ -1,5 +1,6 @@ -import assert from 'node:assert/strict'; import { mock, test } from 'bun:test'; +import assert from 'node:assert/strict'; +import { GENERATED_MOBILE_EXTENSION_PLAN } from '../src/generated/extension-smoke.ts'; class FixturePostgresError extends Error {} @@ -51,6 +52,7 @@ test('the successful pg_textsearch producer supplies every semantic PASS fact', { sqlName: 'pg_textsearch', createsExtension: true, + sqlMembers: ['pg_textsearch'], nativeModuleStem: 'pg_textsearch', selectedExtensionDependencies: [], activationSql: ['CREATE EXTENSION pg_textsearch'], @@ -102,3 +104,34 @@ test('the successful pg_textsearch producer supplies every semantic PASS fact', assert.equal(receipt.extensionCatalogComplete, true); assert.equal(receipt.pgTextsearchEnglishBm25, true); }); + +test('mobile catalog completeness requires all declared PostGIS SQL members', async () => { + const postgis = GENERATED_MOBILE_EXTENSION_PLAN.find((row) => row.sqlName === 'postgis'); + assert(postgis); + const members = ['postgis', 'postgis_raster', 'postgis_tiger_geocoder', 'postgis_topology']; + assert.deepEqual(postgis.sqlMembers, members); + const plan = [{ ...postgis, activationSql: ['CREATE EXTENSION postgis'], smokeStatements: [] }]; + const prove = (installed) => + runMobileReleaseExtensionProof( + { + async execute() {}, + async query(sql) { + if (sql.includes('WHERE extname = $1')) { + return queryResult({ name: 'postgis', version: '3.6.4' }); + } + if (sql.includes('string_agg')) return queryResult({ value: installed }); + throw new Error(`unexpected extension-proof query: ${sql}`); + }, + }, + plan, + ); + assert.equal((await prove(members.join(','))).extensionCatalogComplete, true); + await assert.rejects( + prove(members.filter((member) => member !== 'postgis_raster').join(',')), + /installed mobile extension catalog/, + ); + await assert.rejects( + prove([...members, 'unexpected'].join(',')), + /installed mobile extension catalog/, + ); +}); diff --git a/src/extensions/tools/extension-projections.mts b/src/extensions/tools/extension-projections.mts index fabc28521..4f769ec21 100644 --- a/src/extensions/tools/extension-projections.mts +++ b/src/extensions/tools/extension-projections.mts @@ -315,6 +315,7 @@ ${metadataRows.map((row) => ` public static let ${row.id.replace(/[-_]([a-z]) const smokePlan = metadataRows.map((row) => ({ sqlName: row['sql-name'], createsExtension: row['creates-extension'], + sqlMembers: row['sql-members'], selectedExtensionDependencies: row['selected-extension-dependencies'], })); const smoke = Object.fromEntries( @@ -330,7 +331,7 @@ ${metadataRows.map((row) => ` public static let ${row.id.replace(/[-_]([a-z]) assert(statements.length, `empty smoke recipe for ${name}`); outputs.set( 'src/examples/native/react-native-expo/src/generated/extension-smoke.ts', - `// This file is generated by ${modelPath}.\n// Do not edit by hand. It belongs only to installed mobile qualification.\n\nexport type GeneratedMobileExtensionProof = {\n readonly sqlName: string;\n readonly createsExtension: boolean;\n readonly selectedExtensionDependencies: readonly string[];\n};\n\nexport const GENERATED_MOBILE_EXTENSION_METADATA_SHA256 = ${literal(hash)} as const;\n\nexport const GENERATED_MOBILE_EXTENSION_PLAN = ${JSON.stringify(smokePlan, null, 2)} as const satisfies readonly GeneratedMobileExtensionProof[];\n\nexport const GENERATED_MOBILE_EXTENSION_SMOKE = ${JSON.stringify(sorted(smoke), null, 2).replace(/[\u007f-\uffff]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`)} as const satisfies Readonly>;\n`, + `// This file is generated by ${modelPath}.\n// Do not edit by hand. It belongs only to installed mobile qualification.\n\nexport type GeneratedMobileExtensionProof = {\n readonly sqlName: string;\n readonly createsExtension: boolean;\n readonly sqlMembers: readonly string[];\n readonly selectedExtensionDependencies: readonly string[];\n};\n\nexport const GENERATED_MOBILE_EXTENSION_METADATA_SHA256 = ${literal(hash)} as const;\n\nexport const GENERATED_MOBILE_EXTENSION_PLAN = ${JSON.stringify(smokePlan, null, 2)} as const satisfies readonly GeneratedMobileExtensionProof[];\n\nexport const GENERATED_MOBILE_EXTENSION_SMOKE = ${JSON.stringify(sorted(smoke), null, 2).replace(/[\u007f-\uffff]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`)} as const satisfies Readonly>;\n`, ); outputs.set( 'src/native/sdks/kotlin/oliphaunt/src/commonMain/kotlin/dev/oliphaunt/GeneratedExtensions.kt', From e1e0055cdc48b16410906575fb251104f8e0bd33 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Wed, 21 Oct 2026 12:03:38 +0000 Subject: [PATCH 28/29] fix(extensions): preserve published carriers and validate prepared companions --- .../maintainers/extension-packaging-policy.md | 5 ++- src/native/runtime/packaging/src/lib.rs | 36 +++++++++++++++--- .../ResolveOliphauntAndroidAssetsTask.java | 6 +++ ...OliphauntExtensionCatalogContractTest.java | 2 + .../sdks/react-native/tools/stage-ios-app.mts | 28 +++++++------- .../react-native/tools/stage-ios-app.test.mts | 17 +++++++-- .../pgtap/manifest.properties | 2 - .../Fixtures/swiftpm-extension-selection.json | 6 +-- .../tools/extension-resource-inventory.mts | 31 +++++++++++---- .../extension-resource-inventory.test.mts | 25 +++++------- .../swift/tools/render-extension-products.mts | 3 +- .../swift/tools/swift-carrier-resolver.mts | 3 +- .../tools/swift-carrier-resolver.test.mts | 2 + .../ts/src/__tests__/asset-resolver.test.ts | 30 ++++++++++++++- src/native/sdks/ts/src/native/assets.ts | 16 ++++---- .../sdks/ts/src/native/extension-runtime.ts | 38 ++++++++----------- 16 files changed, 159 insertions(+), 91 deletions(-) diff --git a/src/docs/maintainers/extension-packaging-policy.md b/src/docs/maintainers/extension-packaging-policy.md index dddbeedc9..4c817de66 100644 --- a/src/docs/maintainers/extension-packaging-policy.md +++ b/src/docs/maintainers/extension-packaging-policy.md @@ -103,8 +103,9 @@ Every direct or bundle npm platform package also carries and exports `extension-contract.json` with schema `oliphaunt-npm-extension-contract-v1`. That manifest freezes each member's `createsExtension`, primary and companion native module stems, SQL members, dependencies, data files, additional SQL -names/prefixes, and preload libraries -at the independently versioned extension release. It also freezes the exact, +names/prefixes, and preload libraries at the independently versioned extension +release. Published v1 carriers without companion fields retain only their +primary SQL/module identities. The manifest also freezes the exact, sorted `share/licenses/` file inventory derived from that member's canonical carrier legal metadata. Every row binds the portable path, canonical SHA-256, and archive mode `0644`; contrib members therefore declare an explicit empty diff --git a/src/native/runtime/packaging/src/lib.rs b/src/native/runtime/packaging/src/lib.rs index 830ca1a8d..0d7d8558e 100644 --- a/src/native/runtime/packaging/src/lib.rs +++ b/src/native/runtime/packaging/src/lib.rs @@ -824,11 +824,26 @@ fn load_prebuilt_extension_artifact(root: &Path) -> Result>(); + let mut manifest = parse_canonical_properties_manifest(&manifest_path, &manifest_text, &keys)?; + if legacy { + // Published v1 artifacts encode only the primary SQL and module identities. + let members = if parse_manifest_yes_no(&manifest_path, &manifest, "createsExtension")? { + required_manifest_value(&manifest_path, &manifest, "sqlName")?.to_owned() + } else { + String::new() + }; + let stems = manifest["nativeModuleStem"].clone(); + manifest.insert("sqlMembers".to_owned(), members); + manifest.insert("nativeModuleStems".to_owned(), stems); + } require_property( &manifest_path, &manifest, @@ -1715,6 +1730,17 @@ CREATE FUNCTION sql_only(integer) RETURNS integer "data/acme_ext.rules", true, ); + let manifest = artifact.join("manifest.properties"); + let legacy = fs::read_to_string(&manifest) + .unwrap() + .lines() + .filter(|line| { + !line.starts_with("sqlMembers=") && !line.starts_with("nativeModuleStems=") + }) + .collect::>() + .join("\n") + + "\n"; + fs::write(manifest, legacy).unwrap(); write_file( &artifact.join("files/share/postgresql/extension/hstore.control"), b"comment = 'should not leak'\n", diff --git a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/ResolveOliphauntAndroidAssetsTask.java b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/ResolveOliphauntAndroidAssetsTask.java index 5c5eddd0d..58e03bc35 100644 --- a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/ResolveOliphauntAndroidAssetsTask.java +++ b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/main/java/dev/oliphaunt/android/ResolveOliphauntAndroidAssetsTask.java @@ -1297,6 +1297,12 @@ private static void validateExtensionManifest( Properties manifest, String assetName, String expectedRuntimeVersion) { + // Published v1 artifacts encode only the primary SQL and module identities. + if (!manifest.containsKey("sqlMembers") && !manifest.containsKey("nativeModuleStems")) { + manifest.setProperty("sqlMembers", "yes".equals(manifest.getProperty("createsExtension")) + ? manifest.getProperty("sqlName", "") : ""); + manifest.setProperty("nativeModuleStems", manifest.getProperty("nativeModuleStem", "")); + } TreeSet actualKeys = new TreeSet<>(manifest.stringPropertyNames()); TreeSet expectedKeys = new TreeSet<>(EXTENSION_ARTIFACT_PROPERTY_KEYS); if (!actualKeys.equals(expectedKeys)) { diff --git a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java index 9230c9267..86477bb6d 100644 --- a/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java +++ b/src/native/sdks/kotlin/oliphaunt-android-gradle-plugin/src/test/java/dev/oliphaunt/android/OliphauntExtensionCatalogContractTest.java @@ -2639,6 +2639,8 @@ private static String artifactManifestText(Properties properties) { private static void validatesExactNestedArtifactContract() { Properties manifest = cubeArtifactManifest(); + manifest.remove("sqlMembers"); + manifest.remove("nativeModuleStems"); ResolveOliphauntAndroidAssetsTask.validateExtensionManifestForContractTest( "oliphaunt-extension-contrib-pg18", diff --git a/src/native/sdks/react-native/tools/stage-ios-app.mts b/src/native/sdks/react-native/tools/stage-ios-app.mts index 575d08ce2..b38acfb5d 100755 --- a/src/native/sdks/react-native/tools/stage-ios-app.mts +++ b/src/native/sdks/react-native/tools/stage-ios-app.mts @@ -684,7 +684,12 @@ function validateBase(value, label, allowFileUrls) { } function validateExtension(value, label, carriers) { - const root = object(value, label); + const root = { ...object(value, label) }; + // Published v1 carriers without companion fields contain only their primary identities. + if (!('sqlMembers' in root) && !('nativeModuleStems' in root)) { + root.sqlMembers = root.createsExtension ? [root.sqlName] : []; + root.nativeModuleStems = root.nativeModuleStem === null ? [] : [root.nativeModuleStem]; + } exactKeys( root, [ @@ -1829,9 +1834,6 @@ async function validateExactExtensionArtifactInventory( if (carrier.createsExtension) { const extensionRoot = 'files/share/postgresql/extension'; const sqlMembers = carrier.sqlMembers; - const control = `${extensionRoot}/${carrier.sqlName}.control`; - if (!actualFiles.has(control)) fail(`${source} is missing canonical control file ${control}`); - expectedFiles.add(control); const ownedSqlFiles = [...actualFiles].filter((file) => { if (path.posix.dirname(file) !== extensionRoot) return false; const name = path.posix.basename(file); @@ -1843,16 +1845,6 @@ async function validateExactExtensionArtifactInventory( return /^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(versionPath); }); }); - const installSqlFiles = ownedSqlFiles.filter((file) => { - const name = path.posix.basename(file); - const prefix = `${carrier.sqlName}--`; - if (!name.startsWith(prefix) || !name.endsWith('.sql')) return false; - const version = name.slice(prefix.length, -'.sql'.length); - return !version.includes('--') && /^[0-9][A-Za-z0-9._-]*$/u.test(version); - }); - if (installSqlFiles.length === 0) { - fail(`${source} is missing an install SQL file owned by ${carrier.sqlName}`); - } const ancillarySqlFiles = [...actualFiles].filter((file) => { if (path.posix.dirname(file) !== extensionRoot) return false; const name = path.posix.basename(file); @@ -1869,7 +1861,6 @@ async function validateExactExtensionArtifactInventory( ![...actualFiles].some((file) => { if (path.posix.dirname(file) !== extensionRoot) return false; const name = path.posix.basename(file); - if (name === `${member}.sql`) return true; const prefix = `${member}--`; if (!name.startsWith(prefix) || !name.endsWith('.sql')) return false; const version = name.slice(prefix.length, -'.sql'.length); @@ -1930,6 +1921,13 @@ async function extensionResourceRoot(carrier, base, cacheDir) { const manifestFile = path.join(root, 'manifest.properties'); const manifest = parseProperties(await fs.readFile(manifestFile, 'utf8'), manifestFile); rejectUnsupportedProperties(manifest, EXTENSION_ARTIFACT_PROPERTY_KEYS, manifestFile); + if (!manifest.has('sqlMembers') && !manifest.has('nativeModuleStems')) { + manifest.set( + 'sqlMembers', + manifest.get('createsExtension') === 'yes' ? manifest.get('sqlName') : '', + ); + manifest.set('nativeModuleStems', manifest.get('nativeModuleStem')); + } requireProperty(manifest, 'sqlMembers', carrier.sqlMembers.join(','), manifestFile); requireProperty(manifest, 'nativeModuleStems', carrier.nativeModuleStems.join(','), manifestFile); requireProperty(manifest, 'packageLayout', 'oliphaunt-extension-artifact-v1', manifestFile); diff --git a/src/native/sdks/react-native/tools/stage-ios-app.test.mts b/src/native/sdks/react-native/tools/stage-ios-app.test.mts index da918e62f..b0a0b775a 100755 --- a/src/native/sdks/react-native/tools/stage-ios-app.test.mts +++ b/src/native/sdks/react-native/tools/stage-ios-app.test.mts @@ -532,8 +532,12 @@ async function extensionRow(root, config) { `staticSymbolAliases=${staticSymbolAliases.join(',')}`, `licenseFiles=${licenseFiles.join(',')}`, `licenseProfile=${profile}`, - `sqlMembers=${config.sqlMembers.join(',')}`, - `nativeModuleStems=${config.nativeModuleStem ?? ''}`, + ...(config.sqlName === 'pgtap' + ? [] + : [ + `sqlMembers=${config.sqlMembers.join(',')}`, + `nativeModuleStems=${config.nativeModuleStem ?? ''}`, + ]), 'files=files', '', ].join('\n'), @@ -688,8 +692,13 @@ async function extensionRow(root, config) { extension: { assets: logicalAssets.map(({ locator }) => locator), createsExtension, - sqlMembers: config.sqlMembers, - nativeModuleStems: config.nativeModuleStem === null ? [] : [config.nativeModuleStem], + // Keep one published v1 single-member carrier in the complete staging proof. + ...(config.sqlName === 'pgtap' + ? {} + : { + sqlMembers: config.sqlMembers, + nativeModuleStems: config.nativeModuleStem === null ? [] : [config.nativeModuleStem], + }), dataFiles, dependencies: config.dependencies, extensionSqlFileNames: config.extensionSqlFileNames, diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/pgtap/manifest.properties b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/pgtap/manifest.properties index bb647a9b6..55e744fd8 100644 --- a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/pgtap/manifest.properties +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-resources/pgtap/manifest.properties @@ -19,6 +19,4 @@ staticSymbolPrefix= staticSymbolAliases= licenseFiles=share/licenses/pgtap/README.md licenseProfile=external-native -sqlMembers=pgtap -nativeModuleStems= files=files diff --git a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-selection.json b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-selection.json index 3f453c500..606e42ff0 100644 --- a/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-selection.json +++ b/src/native/sdks/swift/Tests/Fixtures/swiftpm-extension-selection.json @@ -175,11 +175,7 @@ "resourceRoot": "swiftpm-extension-resources/pgtap", "sharedPreloadLibraries": [], "asset": null, - "registration": null, - "sqlMembers": [ - "pgtap" - ], - "nativeModuleStems": [] + "registration": null }, { "product": "oliphaunt-extension-contrib-pg18", diff --git a/src/native/sdks/swift/tools/extension-resource-inventory.mts b/src/native/sdks/swift/tools/extension-resource-inventory.mts index bdea65534..6be2f1e4a 100644 --- a/src/native/sdks/swift/tools/extension-resource-inventory.mts +++ b/src/native/sdks/swift/tools/extension-resource-inventory.mts @@ -167,6 +167,16 @@ function normalizeCanonicalRow(row, label) { }; } +// Published v1 carriers without companion fields contain only their primary identities. +export function normalizeExtensionMemberClosure(row) { + if ('sqlMembers' in row || 'nativeModuleStems' in row) return row; + return { + ...row, + sqlMembers: row.createsExtension ? [row.sqlName] : [], + nativeModuleStems: row.nativeModuleStem === null ? [] : [row.nativeModuleStem], + }; +} + function normalizeFrozenRuntimeContract(row, label) { const sqlName = portable(row?.sqlName, `${label}.sqlName`); const product = portable(row?.product, `${label}.product`); @@ -174,6 +184,7 @@ function normalizeFrozenRuntimeContract(row, label) { if (typeof row?.createsExtension !== 'boolean') fail(label, 'createsExtension must be boolean'); const nativeModuleStem = row?.nativeModuleStem ?? null; if (nativeModuleStem !== null) portable(nativeModuleStem, `${label}.nativeModuleStem`); + row = normalizeExtensionMemberClosure(row); const sqlMembers = canonicalList(row.sqlMembers, `${label}.sqlMembers`, portable); const nativeModuleStems = canonicalList( row.nativeModuleStems, @@ -296,12 +307,23 @@ function parseProperties(text, label) { if (properties.has(key)) fail(label, `repeats property ${key}`); properties.set(key, line.slice(separator + 1)); } - if (JSON.stringify([...properties.keys()]) !== JSON.stringify(PROPERTY_KEYS)) { + const legacy = !properties.has('sqlMembers') && !properties.has('nativeModuleStems'); + const expectedKeys = legacy + ? PROPERTY_KEYS.filter((key) => key !== 'sqlMembers' && key !== 'nativeModuleStems') + : PROPERTY_KEYS; + if (JSON.stringify([...properties.keys()]) !== JSON.stringify(expectedKeys)) { fail( label, `must contain the exact canonical fields in canonical order: ${PROPERTY_KEYS.join(',')}`, ); } + if (legacy) { + properties.set( + 'sqlMembers', + properties.get('createsExtension') === 'yes' ? properties.get('sqlName') : '', + ); + properties.set('nativeModuleStems', properties.get('nativeModuleStem')); + } return properties; } @@ -746,8 +768,6 @@ export async function validateSwiftExtensionResourceArtifact({ allowed.add(mobilePath); } const sqlPrefix = 'files/share/postgresql/extension/'; - let hasControl = false; - let hasSql = false; for (const file of files) { if (!file.relative.startsWith(sqlPrefix)) continue; const fileName = file.relative.slice(sqlPrefix.length); @@ -755,11 +775,6 @@ export async function validateSwiftExtensionResourceArtifact({ fail(label, `contains undeclared extension SQL/control file ${file.relative}`); } allowed.add(file.relative); - if (fileName === `${contract.sqlName}.control`) hasControl = true; - if (isCanonicalInstallSql(fileName, contract.sqlName)) hasSql = true; - } - if (contract.createsExtension && (!hasControl || !hasSql)) { - fail(label, `must contain ${contract.sqlName}.control and canonical base installation SQL`); } for (const member of contract.sqlMembers) { if ( diff --git a/src/native/sdks/swift/tools/extension-resource-inventory.test.mts b/src/native/sdks/swift/tools/extension-resource-inventory.test.mts index 7f5c2f1ea..ec90c7518 100644 --- a/src/native/sdks/swift/tools/extension-resource-inventory.test.mts +++ b/src/native/sdks/swift/tools/extension-resource-inventory.test.mts @@ -166,36 +166,32 @@ async function main() { }), /mobile nativeModuleStems must contain only the merged primary module/u, ); - const expandedPostgis = postgis; - const companionFiles = sqlMembers - .filter((name) => name !== 'postgis') - .map((member) => - path.join(postgis.resourceRoot, 'files/share/postgresql/extension', `${member}.control`), - ); - const expandedResources = await validateSwiftExtensionResourceArtifact({ - extension: expandedPostgis, + const postgisResources = await validateSwiftExtensionResourceArtifact({ + extension: postgis, canonical: catalog.get('postgis'), nativeRuntime: document.nativeRuntime, }); assert.ok( - expandedResources.files.some( + postgisResources.files.some( ({ relative }) => relative === 'extension/postgis_topology.control', ), ); - const missingCompanion = companionFiles.find((file) => - file.endsWith('/postgis_topology.control'), + const missingCompanion = path.join( + postgis.resourceRoot, + 'files/share/postgresql/extension/postgis_topology.control', ); + const companionBytes = await fs.readFile(missingCompanion); await fs.rm(missingCompanion); await assert.rejects( () => validateSwiftExtensionResourceArtifact({ - extension: expandedPostgis, + extension: postgis, canonical: catalog.get('postgis'), nativeRuntime: document.nativeRuntime, }), /postgis_topology\.control and canonical base installation SQL/u, ); - await fs.writeFile(missingCompanion, '-- companion fixture\n'); + await fs.writeFile(missingCompanion, companionBytes); const foreignControl = path.join( postgis.resourceRoot, 'files/share/postgresql/extension/postgis_topology_extra.control', @@ -204,14 +200,13 @@ async function main() { await assert.rejects( () => validateSwiftExtensionResourceArtifact({ - extension: expandedPostgis, + extension: postgis, canonical: catalog.get('postgis'), nativeRuntime: document.nativeRuntime, }), /undeclared extension SQL\/control file/u, ); await fs.rm(foreignControl); - await fs.writeFile(postgisManifest, postgisManifestText); await assert.rejects( () => validateSwiftExtensionResourceArtifact({ diff --git a/src/native/sdks/swift/tools/render-extension-products.mts b/src/native/sdks/swift/tools/render-extension-products.mts index ffc2b9292..3cc0f95a7 100755 --- a/src/native/sdks/swift/tools/render-extension-products.mts +++ b/src/native/sdks/swift/tools/render-extension-products.mts @@ -6,6 +6,7 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { loadSwiftExtensionInventoryCatalog, + normalizeExtensionMemberClosure, readSafeFileSnapshot, snapshotSafeFileTree, validateSwiftExtensionResourceArtifact, @@ -419,7 +420,7 @@ export function validateSelection( const basePackage = validateBasePackage(root.basePackage); const nativeRuntime = validateNativeRuntime(root.nativeRuntime); const extensions = root.extensions.map((raw, index) => { - const row = object(raw, `extensions[${index}]`); + const row = normalizeExtensionMemberClosure(object(raw, `extensions[${index}]`)); exactKeys( row, [ diff --git a/src/native/sdks/swift/tools/swift-carrier-resolver.mts b/src/native/sdks/swift/tools/swift-carrier-resolver.mts index 4b36785dc..88d93321b 100644 --- a/src/native/sdks/swift/tools/swift-carrier-resolver.mts +++ b/src/native/sdks/swift/tools/swift-carrier-resolver.mts @@ -12,6 +12,7 @@ import { } from '../../../../../tools/packaging/portable-archive.mts'; import { loadSwiftExtensionInventoryCatalog, + normalizeExtensionMemberClosure, validateSwiftExtensionResourceArtifact, } from './extension-resource-inventory.mts'; @@ -444,7 +445,7 @@ function validateExtensionReleaseReference(value, label) { return { product, tag: row.tag, version }; } function validateExtension(value, label, carriers) { - const row = object(value, label); + const row = normalizeExtensionMemberClosure(object(value, label)); exactKeys( row, [ diff --git a/src/native/sdks/swift/tools/swift-carrier-resolver.test.mts b/src/native/sdks/swift/tools/swift-carrier-resolver.test.mts index 95dbc99d5..2bdcde988 100755 --- a/src/native/sdks/swift/tools/swift-carrier-resolver.test.mts +++ b/src/native/sdks/swift/tools/swift-carrier-resolver.test.mts @@ -516,6 +516,8 @@ async function main() { frozenOldCarrier.extensions = frozenOldCarrier.extensions.filter( ({ sqlName }) => sqlName === 'pgtap', ); + delete frozenOldCarrier.extensions[0].sqlMembers; + delete frozenOldCarrier.extensions[0].nativeModuleStems; frozenOldCarrier.extensions[0].version = '0.9.0'; frozenOldCarrier.extensions[0].tag = 'oliphaunt-extension-pgtap-v0.9.0'; const frozenCarrierNames = new Set( diff --git a/src/native/sdks/ts/src/__tests__/asset-resolver.test.ts b/src/native/sdks/ts/src/__tests__/asset-resolver.test.ts index 73b376d55..bf7aa9831 100644 --- a/src/native/sdks/ts/src/__tests__/asset-resolver.test.ts +++ b/src/native/sdks/ts/src/__tests__/asset-resolver.test.ts @@ -1203,7 +1203,12 @@ async function nodePostgisCompanionClosureInstall( const incomplete: Partial = { ...complete }; for (const field of omitted) delete incomplete[field]; await writeContract(incomplete); - await assert.rejects(materialize, /fields must be exactly/); + await assert.rejects( + materialize, + omitted.length === 2 + ? /incompatible with the SDK extension contract/ + : /fields must be exactly/, + ); } for (const incomplete of [ { ...complete, dependencies: [] }, @@ -1251,6 +1256,27 @@ async function nodePostgisCompanionClosureInstall( await assert.rejects(materialize, /requires undeclared dependency cube/); await writeFile(tigerControl, controlBytes); const installed = await materialize(); + for (const stem of ['dict_snowball', 'plpgsql']) { + await writeFile( + join(installed.runtimeDirectory!, 'lib/modules', `${stem}${suffix}`), + 'embedded core module', + ); + } + await nodeAssets.validatePreparedNativeRuntimeExtensions(installed, ['postgis']); + for (const relative of [ + 'share/postgresql/extension/postgis_raster.control', + 'share/postgresql/extension/postgis_raster--3.6.4.sql', + `lib/modules/postgis_raster-3${suffix}`, + ]) { + const file = join(installed.runtimeDirectory!, relative); + const original = await readFile(file); + await rm(file); + await assert.rejects( + nodeAssets.validatePreparedNativeRuntimeExtensions(installed, ['postgis']), + /postgis_raster/, + ); + await writeFile(file, original); + } assert.equal( await readFile(join(installed.moduleDirectory!, `postgis_topology-3${suffix}`), 'utf8'), 'complete:postgis_topology-3', @@ -1414,6 +1440,8 @@ async function nodeExtensionMaterializationRejectsIncompletePackagePayloads(): P fixtureExtensionContractManifest(product, extensionVersion, target.id, [ independentlyVersionedContract, ]), + // Published v1 carriers omit both companion-closure fields. + (key, value) => (key === 'sqlMembers' || key === 'nativeModuleStems' ? undefined : value), ), ); const skewData = join(canonicalRuntime, 'share/postgresql/oliphaunt-skew/new-version.dat'); diff --git a/src/native/sdks/ts/src/native/assets.ts b/src/native/sdks/ts/src/native/assets.ts index a2cb5ef2c..2c92e0b78 100644 --- a/src/native/sdks/ts/src/native/assets.ts +++ b/src/native/sdks/ts/src/native/assets.ts @@ -30,6 +30,7 @@ import { parseNpmExtensionLicenseFiles, } from './extension-contract.js'; import { + isCanonicalExtensionInstallSql, nativeModuleSuffixForTarget, type RuntimeFileHost, selectedExtensionClosure, @@ -606,7 +607,12 @@ function parseExtensionMemberContract(value: unknown, label: string): NpmExtensi if (value === null || typeof value !== 'object' || Array.isArray(value)) { throw new Error(`${label} must be a JSON object`); } - const row = value as Record; + const row = { ...value } as Record; + // Published v1 carriers without companion fields contain only their primary identities. + if (!('sqlMembers' in row) && !('nativeModuleStems' in row)) { + row.sqlMembers = row.createsExtension ? [row.sqlName] : []; + row.nativeModuleStems = row.nativeModuleStem === null ? [] : [row.nativeModuleStem]; + } if ( JSON.stringify(Object.keys(row).sort(compareText)) !== JSON.stringify(NPM_EXTENSION_CONTRACT_MEMBER_FIELDS) @@ -1122,14 +1128,6 @@ function extensionRuntimeSqlFileOwned( ); } -function isCanonicalExtensionInstallSql(fileName: string, sqlName: string): boolean { - if (fileName === `${sqlName}.sql`) return true; - const prefix = `${sqlName}--`; - if (!fileName.startsWith(prefix) || !fileName.endsWith('.sql')) return false; - const version = fileName.slice(prefix.length, -'.sql'.length); - return /^[0-9][A-Za-z0-9._-]*$/u.test(version) && !version.includes('--'); -} - async function exactRuntimeLeafPaths(root: string, source: string): Promise { const rows: string[] = []; const collisionPaths = new Map(); diff --git a/src/native/sdks/ts/src/native/extension-runtime.ts b/src/native/sdks/ts/src/native/extension-runtime.ts index 3b1516a54..585ca8085 100644 --- a/src/native/sdks/ts/src/native/extension-runtime.ts +++ b/src/native/sdks/ts/src/native/extension-runtime.ts @@ -72,17 +72,13 @@ export async function requireExtensionRuntimePayload(config: { }): Promise { if (config.extension.createsExtension) { const entries = await extensionSqlDirectoryEntries(config.runtimeDirectories, config.host); - const hasControl = entries.includes(`${config.extension.sqlName}.control`); - if (!hasControl) { - throw new Error(`${config.runtimeSource} is missing ${config.extension.sqlName}.control`); - } - const hasInstallSql = entries.some( - (entry) => entry.endsWith('.sql') && extensionSqlFileBelongs(config.extension, entry), - ); - if (!hasInstallSql) { - throw new Error( - `${config.runtimeSource} is missing SQL install files for ${config.extension.sqlName}`, - ); + for (const member of config.extension.sqlMembers) { + if (!entries.includes(`${member}.control`)) { + throw new Error(`${config.runtimeSource} is missing ${member}.control`); + } + if (!entries.some((entry) => isCanonicalExtensionInstallSql(entry, member))) { + throw new Error(`${config.runtimeSource} is missing SQL install files for ${member}`); + } } } @@ -95,10 +91,8 @@ export async function requireExtensionRuntimePayload(config: { ); } - if (config.extension.nativeModuleStem !== null) { - const moduleFile = `${config.extension.nativeModuleStem}${nativeModuleSuffixForTarget( - config.target, - )}`; + for (const stem of config.extension.nativeModuleStems) { + const moduleFile = `${stem}${nativeModuleSuffixForTarget(config.target)}`; await requireFileInAnyRoot( config.moduleDirectories, moduleFile, @@ -168,14 +162,12 @@ async function extensionSqlDirectoryEntries( return entries; } -function extensionSqlFileBelongs(extension: GeneratedExtensionMetadata, fileName: string): boolean { - return ( - fileName === `${extension.sqlName}.control` || - fileName === `${extension.sqlName}.sql` || - (fileName.startsWith(`${extension.sqlName}--`) && fileName.endsWith('.sql')) || - extension.extensionSqlFileNames.includes(fileName) || - extension.extensionSqlFilePrefixes.some((prefix) => fileName.startsWith(prefix)) - ); +export function isCanonicalExtensionInstallSql(fileName: string, sqlName: string): boolean { + if (fileName === `${sqlName}.sql`) return true; + const prefix = `${sqlName}--`; + if (!fileName.startsWith(prefix) || !fileName.endsWith('.sql')) return false; + const version = fileName.slice(prefix.length, -'.sql'.length); + return /^[0-9][A-Za-z0-9._-]*$/u.test(version) && !version.includes('--'); } async function requireFileInAnyRoot( From 3a8ec96c425cc667d89c7d48e8e87cb974fe8828 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Wed, 21 Oct 2026 12:03:44 +0000 Subject: [PATCH 29/29] refactor(extensions): remove redundant packaging state and review notes --- src/docs/maintainers/windows-vc-runtime.md | 6 ------ .../artifacts/native/tools/windows-extension-sources.mts | 4 ---- .../packages/tools/extension-artifact-inventory.mts | 5 ++--- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/docs/maintainers/windows-vc-runtime.md b/src/docs/maintainers/windows-vc-runtime.md index 9ec38a28d..730ecf5ef 100644 --- a/src/docs/maintainers/windows-vc-runtime.md +++ b/src/docs/maintainers/windows-vc-runtime.md @@ -52,12 +52,6 @@ PR builds only a subset of extensions. ## Release evidence -The PostGIS 3.6.4 Windows build on commit `4dafd0325d05930636f0530276a3a40f3284cb68` -imports `MSVCP140_2.dll` from `postgis-3.dll`, as recorded in -[the extension build](https://github.com/f0rr0/oliphaunt/actions/runs/39249410590/job/118130472350). -The provider profile includes this production runtime; import-derived staging -still verifies its transitive closure and preserves the original DLL bytes. - The MSVC setup step fails before a long build unless `VCToolsRedistDir` resolves the exact x64 VC145 directory and all audited production files. Windows build and packaging then: diff --git a/src/extensions/artifacts/native/tools/windows-extension-sources.mts b/src/extensions/artifacts/native/tools/windows-extension-sources.mts index 5b9919f11..8fcf94520 100644 --- a/src/extensions/artifacts/native/tools/windows-extension-sources.mts +++ b/src/extensions/artifacts/native/tools/windows-extension-sources.mts @@ -135,10 +135,6 @@ export function prepareSimple(config: Config, extension: string) { 'bcrypt.lib', 'ws2_32.lib', 'user32.lib', - 'ole32.lib', - 'oleaut32.lib', - 'uuid.lib', - 'msvcprt.lib', ); } else { copyFileSync( diff --git a/src/extensions/artifacts/packages/tools/extension-artifact-inventory.mts b/src/extensions/artifacts/packages/tools/extension-artifact-inventory.mts index 3854da366..d41d2e281 100644 --- a/src/extensions/artifacts/packages/tools/extension-artifact-inventory.mts +++ b/src/extensions/artifacts/packages/tools/extension-artifact-inventory.mts @@ -147,16 +147,15 @@ export function parseExtensionArtifactProperties(text, label) { } properties.set(key, rawLine.slice(separator + 1)); } - const orderedKeys = EXTENSION_ARTIFACT_PROPERTY_KEYS; const actual = [...properties.keys()].sort(compareText); - const expected = [...orderedKeys].sort(compareText); + const expected = [...EXTENSION_ARTIFACT_PROPERTY_KEYS].sort(compareText); if (JSON.stringify(actual) !== JSON.stringify(expected)) { throw inventoryError( label, `property fields must be exactly ${expected.join(',')}; got ${actual.join(',')}`, ); } - if (JSON.stringify([...properties.keys()]) !== JSON.stringify(orderedKeys)) { + if (JSON.stringify([...properties.keys()]) !== JSON.stringify(EXTENSION_ARTIFACT_PROPERTY_KEYS)) { throw inventoryError(label, 'properties must use the canonical field order'); } return properties;