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/extension-packaging-policy.md b/src/docs/maintainers/extension-packaging-policy.md index e08f30b94..4c817de66 100644 --- a/src/docs/maintainers/extension-packaging-policy.md +++ b/src/docs/maintainers/extension-packaging-policy.md @@ -101,9 +101,11 @@ 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 -at the independently versioned extension release. It also freezes the exact, +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. 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 @@ -117,8 +119,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 @@ -458,10 +460,19 @@ 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`, `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 diff --git a/src/docs/maintainers/windows-vc-runtime.md b/src/docs/maintainers/windows-vc-runtime.md index 2d9c09aee..730ecf5ef 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 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 08d274371..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,60 +4,89 @@ export type GeneratedMobileExtensionProof = { readonly sqlName: string; readonly createsExtension: boolean; + readonly sqlMembers: readonly string[]; readonly selectedExtensionDependencies: readonly string[]; }; -export const GENERATED_MOBILE_EXTENSION_METADATA_SHA256 = "c1d2e09905d7ecc0172173b34b9e4104dad58987503dd3dab7af4ae78890d9f3" as const; +export const GENERATED_MOBILE_EXTENSION_METADATA_SHA256 = "dbe1ab284693ef923cb12e35e1c75bcc7fc1ed42af1fecf9fe9fee0aaf0ef8d0" as const; 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,146 +94,238 @@ 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, - "selectedExtensionDependencies": [] + "sqlMembers": [ + "postgis", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology" + ], + "selectedExtensionDependencies": [ + "fuzzystrmatch" + ] }, { "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[]; @@ -357,11 +478,16 @@ export const GENERATED_MOBILE_EXTENSION_SMOKE = { "SELECT * FROM finish();" ], "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;\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;\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 $$;" 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/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( diff --git a/src/examples/wasix/browser/vite.config.ts b/src/examples/wasix/browser/vite.config.ts index 5c5a48669..c6ad6e620 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,21 @@ 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')); + } + carriers.push(carrier); return { schema: 'oliphaunt-wasix-extension-v1', runtime: 'wasix', @@ -352,7 +368,7 @@ async function developmentDescriptor(packageName: string): Promise/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 \ + -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_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 + fi } windows_extension_generate() { 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..80c7d3464 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,8 @@ async function extensionSqlMetadata(sqlName) { fail(`prebuilt extension '${sqlName}' SQL file prefix '${prefix}' must not contain '.'`); } } - return { names, prefixes }; + const closure = extensionNativeArtifactClosure(sqlName, target); + return { names, prefixes, ...closure }; } async function ensureParent(file) { @@ -858,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)) { @@ -870,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( @@ -898,6 +921,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 +1007,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(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}`); 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 +1071,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..85f3d9a4e --- /dev/null +++ b/src/extensions/artifacts/native/tools/extension-artifact-packager.test.mts @@ -0,0 +1,189 @@ +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'], + ['android-x86_64', '.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) { + const module = stems.find((stem) => stem === `${name}-3`); + await write( + path.join(runtime, `share/postgresql/extension/${name}.control`), + `default_version = '${version}'\n` + + (module ? `module_pathname = '$libdir/${module}'\n` : ''), + ); + await write( + path.join(runtime, `share/postgresql/extension/${name}--${version}.sql`), + module + ? `CREATE FUNCTION ${name}_fixture() RETURNS text AS '$libdir/${module}', '${name}_fixture' LANGUAGE C;\n` + : `-- ${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`)); + 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( + { ...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 3a2ef1259..8fcf94520 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, @@ -189,6 +207,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); @@ -244,6 +280,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 +318,41 @@ 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 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)', + '# 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 +361,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']) { + 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); @@ -300,6 +379,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, @@ -349,10 +435,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 +452,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 +464,20 @@ 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'), + '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', @@ -388,6 +489,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'), @@ -405,6 +509,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 +518,39 @@ 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'); - mkdirSync(sql, { recursive: true }); + const rasterSql = path.join(directory, 'extensions/postgis_raster/sql'); + 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, @@ -469,11 +600,58 @@ 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, + ); + 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( @@ -493,20 +671,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'), @@ -516,6 +697,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'), @@ -554,9 +749,91 @@ 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'); + 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') { - 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`)))); + } + 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}`); } @@ -610,6 +887,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 8b409d260..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, 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-')); @@ -42,3 +48,191 @@ test('Windows textsearch compiles upstream layout checks and installs the upgrad rmSync(root, { recursive: true, force: true }); } }); + +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 }); + 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', + '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( + 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( + '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, + 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;')); + 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', + ), + ); + 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..d41d2e281 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', ]); @@ -439,6 +441,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 +462,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 +493,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 +818,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 +916,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/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..8ab503f98 100644 --- a/src/extensions/external/postgis/recipe.toml +++ b/src/extensions/external/postgis/recipe.toml @@ -21,17 +21,60 @@ 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", + "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/liboliphaunt_postgis_deps.so"] +native_dependency_modules = [ + "lib/postgresql/postgis_raster-3.so", + "lib/postgresql/postgis_topology-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 +87,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 +106,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..1fb49d877 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,17 +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", "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 d50eba130..aa411bcca 100644 --- a/src/extensions/external/postgis/targets/native.toml +++ b/src/extensions/external/postgis/targets/native.toml @@ -3,9 +3,36 @@ 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", + "lib/postgresql/postgis_topology-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,17 +44,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", "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 7e17550ba..f04c26d8e 100644 --- a/src/extensions/external/postgis/targets/wasix.toml +++ b/src/extensions/external/postgis/targets/wasix.toml @@ -4,9 +4,37 @@ 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/postgis_topology-3.so", "lib/postgresql/liboliphaunt_postgis_deps.so", ] data_files = [ + "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", @@ -15,31 +43,34 @@ 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-raster", - "--without-topology", "--without-sfcgal", "--without-address-standardizer", - "--without-tiger", "--without-protobuf", "--disable-nls", ] 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_raster", - "postgis_topology", "postgis_sfcgal", - "postgis_tiger_geocoder", "address_standardizer", "address_standardizer_data_us", ] @@ -50,6 +81,18 @@ 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" + +[[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" @@ -58,3 +101,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/gdal" + +[[staging.data_dirs]] +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 ef7e073c0..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-plus-selected-support-side-module-v2" +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 && @@ -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" @@ -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 exit 1 @@ -199,6 +227,7 @@ export CPPFLAGS="$("$ROOT/pg_config_wasix.sh" --cppflags) -I$LIBICONV_PREFIX/inc --with-pgconfig="$pg_config" \ --with-libiconv="$LIBICONV_PREFIX" \ --with-geosconfig="$geos_config" \ + --with-gdalconfig="$gdal_config" \ --with-xml2config="$LIBXML2_PREFIX/bin/xml2-config" \ "${postgis_configure_flags[@]}" @@ -241,6 +270,7 @@ EOF --no-gc-sections \ "$POSTGIS_BUILD_DIR/postgis/oliphaunt_postgis_deps_stubs.o" \ --whole-archive \ + "$GDAL_PREFIX/lib/libgdal.a" \ "$GEOS_PREFIX/lib/libgeos_c.a" \ "$GEOS_PREFIX/lib/libgeos.a" \ "$PROJ_PREFIX/lib/libproj.a" \ @@ -257,7 +287,7 @@ EOF "$wasix_sysroot_lib/libclang_rt.builtins-wasm32.a" \ -o "$postgis_deps_module" - export OLIPHAUNT_POSTGIS_STATIC_ARCHIVES="$GEOS_PREFIX/lib/libgeos_c.a $GEOS_PREFIX/lib/libgeos.a $PROJ_PREFIX/lib/libproj.a $SQLITE_PREFIX/lib/libsqlite3.a $JSONC_PREFIX/lib/libjson-c.a $LIBXML2_PREFIX/lib/libxml2.a $LIBICONV_PREFIX/lib/libiconv.a $LIBICONV_PREFIX/lib/libcharset.a $wasix_sysroot_lib/libc++.a $wasix_sysroot_lib/libc++abi.a $wasix_sysroot_lib/libunwind.a $wasix_sysroot_lib/libm.a $wasix_sysroot_lib/libc.a $wasix_sysroot_lib/libclang_rt.builtins-wasm32.a" + export OLIPHAUNT_POSTGIS_STATIC_ARCHIVES="$GDAL_PREFIX/lib/libgdal.a $GEOS_PREFIX/lib/libgeos_c.a $GEOS_PREFIX/lib/libgeos.a $PROJ_PREFIX/lib/libproj.a $SQLITE_PREFIX/lib/libsqlite3.a $JSONC_PREFIX/lib/libjson-c.a $LIBXML2_PREFIX/lib/libxml2.a $LIBICONV_PREFIX/lib/libiconv.a $wasix_sysroot_lib/libc++.a $wasix_sysroot_lib/libc++abi.a $wasix_sysroot_lib/libunwind.a $wasix_sysroot_lib/libm.a $wasix_sysroot_lib/libc.a $wasix_sysroot_lib/libclang_rt.builtins-wasm32.a" perl -0pi -e ' s|^OBJS=\$\(PG_OBJS\)$|OBJS=\$(PG_OBJS) oliphaunt_postgis_deps_stubs.o $ENV{OLIPHAUNT_POSTGIS_STATIC_ARCHIVES}|m; s|^FLATGEOBUF_LIB = .*$|FLATGEOBUF_LIB = ../deps/flatgeobuf/flatgeobuf_c.o ../deps/flatgeobuf/geometrywriter.o ../deps/flatgeobuf/geometryreader.o ../deps/flatgeobuf/packedrtree.o -lc++|m; @@ -268,6 +298,10 @@ EOF s|^(CXXFLAGS =.*)$|$1 -fvisibility=hidden -fvisibility-inlines-hidden|m; ' deps/flatgeobuf/Makefile + perl -0pi -e ' + s|^LIBGDAL_LDFLAGS=.*$|LIBGDAL_LDFLAGS=-L$ENV{POSTGIS_BUILD_DIR}/postgis -loliphaunt_postgis_deps|m; + ' raster/rt_pg/Makefile + oliphaunt_wasix_apply_wasix_profile build make -s -j"$JOBS" -C liblwgeom liblwgeom.la make -s -j"$JOBS" -C libpgcommon libpgcommon.a @@ -298,21 +332,76 @@ EOF -loliphaunt_postgis_deps \ -rpath '$ORIGIN' \ -o "$POSTGIS_BUILD_DIR/postgis/postgis-3.so" + + make -s -j"$JOBS" -C raster/rt_core all + make -s -j"$JOBS" -C raster/rt_pg all + raster_objects=() + while IFS= read -r object; do + [ -n "$object" ] && raster_objects+=("$object") + done < <(find "$POSTGIS_BUILD_DIR/raster/rt_pg" -maxdepth 1 -name '*.o' | sort) + "$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 \ + "${raster_objects[@]}" \ + "$POSTGIS_BUILD_DIR/raster/rt_core/librtcore.a" \ + "$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/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" - # PostGIS core upgrade SQL still includes raster-unpackage stubs even when - # raster support is disabled. Generate those SQL inputs as a best-effort - # prerequisite before packaging PostGIS. Keep this serial: the - # PostGIS SQL Makefiles use generated .tmp files and have incomplete - # parallel dependency edges. - make -s -j1 raster-sql || true - make -s -j1 -C raster/rt_pg sql_objs + 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/postgis postgis.control all + make -s -j1 -C extensions all mkdir -p "$POSTGIS_BUILD_DIR/share" rm -rf "$POSTGIS_BUILD_DIR/share/proj" cp -R "$PROJ_PREFIX/share/proj" "$POSTGIS_BUILD_DIR/share/proj" -} >&2 + 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-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" printf '%s\n' "$stamp" > "$POSTGIS_BUILD_DIR/.oliphaunt-wasix-postgis-build" 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..5f6a57091 100644 --- a/src/extensions/external/postgis/tools/windows/build-sql.sh +++ b/src/extensions/external/postgis/tools/windows/build-sql.sh @@ -4,6 +4,9 @@ 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)" @@ -14,9 +17,15 @@ 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" + 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 f4ca28225..5e01ba6d8 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,134 @@ 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 + +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, @@ -179,3 +228,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..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", @@ -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..df0b8c6ec 100644 --- a/src/extensions/generated/extensions.catalog.json +++ b/src/extensions/generated/extensions.catalog.json @@ -1172,12 +1172,14 @@ "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" }, - "dependencies": [], + "dependencies": [ + "fuzzystrmatch" + ], "load-order": [ "lib/postgresql/postgis-3.so" ], 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..09f565cca 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": "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" }, { @@ -910,9 +1086,37 @@ "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": [], + "dependencies": [ + "fuzzystrmatch" + ], "display-name": "PostGIS", "extension-sql-file-names": [ "uninstall_postgis.sql" @@ -920,6 +1124,9 @@ "extension-sql-file-prefixes": [ "postgis_comments", "postgis_proc_set_search_path", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology", "rtpostgis" ], "id": "postgis", @@ -932,11 +1139,13 @@ "sqlite", "proj", "libxml2", - "json-c" + "json-c", + "gdal" ], "family": "native", "kind": "native-dynamic", "link-units": [ + "gdal", "geos-c", "geos", "proj", @@ -945,6 +1154,7 @@ "json-c" ], "runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "target": "linux-arm64-gnu" @@ -955,11 +1165,13 @@ "sqlite", "proj", "libxml2", - "json-c" + "json-c", + "gdal" ], "family": "native", "kind": "native-dynamic", "link-units": [ + "gdal", "geos-c", "geos", "proj", @@ -968,6 +1180,7 @@ "json-c" ], "runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "target": "linux-x64-gnu" @@ -978,11 +1191,13 @@ "sqlite", "proj", "libxml2", - "json-c" + "json-c", + "gdal" ], "family": "native", "kind": "native-dynamic", "link-units": [ + "gdal", "geos-c", "geos", "proj", @@ -991,6 +1206,7 @@ "json-c" ], "runtime-files": [ + "gdal/gdalvrt.xsd", "proj/proj.db" ], "target": "macos-arm64" @@ -1001,11 +1217,13 @@ "sqlite", "proj", "libxml2", - "json-c" + "json-c", + "gdal" ], "family": "native", "kind": "native-dynamic", "link-units": [ + "gdal", "geos-c", "geos", "proj", @@ -1014,58 +1232,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 +1301,13 @@ "sqlite", "proj", "libxml2", - "json-c" + "json-c", + "gdal" ], "family": "native", "kind": "native-static-registry", "link-units": [ + "gdal", "geos-c", "geos", "proj", @@ -1089,38 +1316,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", @@ -1129,11 +1361,21 @@ "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", "runtime-bound": false, "runtime-environment": [ + { + "name": "GDAL_DATA", + "path": "share/postgresql/gdal", + "required_file": "gdalvrt.xsd" + }, { "name": "PROJ_DATA", "path": "share/postgresql/proj", @@ -1149,11 +1391,45 @@ "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": [], + "selected-extension-dependencies": [ + "fuzzystrmatch" + ], "shared-preload-libraries": [], "source-kind": "postgis", + "sql-members": [ + "postgis", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology" + ], "sql-name": "postgis" }, { @@ -1171,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", @@ -1180,6 +1459,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "seg" + ], "sql-name": "seg" }, { @@ -1197,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", @@ -1206,6 +1491,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "tablefunc" + ], "sql-name": "tablefunc" }, { @@ -1223,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", @@ -1232,6 +1523,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "tcn" + ], "sql-name": "tcn" }, { @@ -1249,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", @@ -1258,6 +1555,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "tsm_system_rows" + ], "sql-name": "tsm_system_rows" }, { @@ -1275,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", @@ -1284,6 +1587,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "tsm_system_time" + ], "sql-name": "tsm_system_time" }, { @@ -1303,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", @@ -1314,6 +1623,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "unaccent" + ], "sql-name": "unaccent" }, { @@ -1430,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", @@ -1439,6 +1754,9 @@ "selected-extension-dependencies": [], "shared-preload-libraries": [], "source-kind": "postgres-contrib", + "sql-members": [ + "uuid-ossp" + ], "sql-name": "uuid-ossp" }, { @@ -1456,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", @@ -1465,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/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..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, @@ -690,25 +692,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 +722,18 @@ "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" + }, + { + "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..4f769ec21 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]) 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( @@ -310,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', @@ -331,6 +352,7 @@ ${metadataRows.map((row) => ` 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 +543,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/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..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 { @@ -15,9 +16,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 +34,16 @@ 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']); + 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/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/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 daf31cd91..f76df3146 100755 --- a/src/native/runtime/bin/build-postgres18-linux.sh +++ b/src/native/runtime/bin/build-postgres18-linux.sh @@ -154,6 +154,9 @@ required_extension_controls=( pg_ivm pgcrypto postgis + postgis_raster + postgis_topology + postgis_tiger_geocoder pg_surgery pg_textsearch pg_trgm @@ -196,6 +199,8 @@ required_extension_modules=( pg_ivm pgcrypto postgis-3 + postgis_raster-3 + postgis_topology-3 pg_surgery pg_textsearch pg_trgm @@ -260,6 +265,8 @@ filter_native_extension_selection() { postgis) selected_external+=(postgis) module_file="postgis-3.so" + 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)" @@ -448,9 +455,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 +1180,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 +1188,10 @@ 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/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" @@ -1366,6 +1386,66 @@ 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" + # 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= \ + -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 +1464,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 +1483,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 patch_postgis_generated_makefiles "$postgis_build_dir" make postgis_revision.h >> "$make_log" 2>&1 @@ -1555,13 +1661,22 @@ build_postgis_extension() { make postgis_revision.h >> "$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 -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" + 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" } @@ -1713,10 +1828,12 @@ 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 + build_native_extension_artifacts echo "$lib_out" exit 0 fi @@ -1745,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/build-postgres18-macos.sh b/src/native/runtime/bin/build-postgres18-macos.sh index f067404ba..388c06d7c 100755 --- a/src/native/runtime/bin/build-postgres18-macos.sh +++ b/src/native/runtime/bin/build-postgres18-macos.sh @@ -138,6 +138,9 @@ required_extension_controls=( pg_ivm pgcrypto postgis + postgis_raster + postgis_topology + postgis_tiger_geocoder pg_surgery pg_textsearch pg_trgm @@ -180,6 +183,8 @@ required_extension_modules=( pg_ivm pgcrypto postgis-3 + postgis_raster-3 + postgis_topology-3 pg_surgery pg_textsearch pg_trgm @@ -244,6 +249,8 @@ filter_native_extension_selection() { postgis) selected_external+=(postgis) module_file="postgis-3.so" + 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)" @@ -846,9 +853,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 +1420,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 +1428,10 @@ 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/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" @@ -1625,6 +1645,70 @@ 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" + # 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= \ + -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 +1727,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 +1751,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 +1761,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 +2158,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() { @@ -2083,6 +2194,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" @@ -2114,11 +2227,8 @@ build_postgis_extension() { --with-pgconfig="$install_dir/bin/pg_config" \ "${postgis_configure_args[@]}" \ --without-protobuf \ - --without-raster \ - --without-topology \ --without-sfcgal \ --without-address-standardizer \ - --without-tiger \ --disable-nls patch_postgis_generated_makefiles "$postgis_build_dir" @@ -2130,17 +2240,28 @@ 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 + 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 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 -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 - copy_embedded_postgis_module "$postgis_build_dir/postgis" + 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/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' diff --git a/src/native/runtime/bin/mobile-postgis-extensions.sh b/src/native/runtime/bin/mobile-postgis-extensions.sh index e0ef77666..11faa241f 100644 --- a/src/native/runtime/bin/mobile-postgis-extensions.sh +++ b/src/native/runtime/bin/mobile-postgis-extensions.sh @@ -43,23 +43,29 @@ 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 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 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" "$topology_control_file" "$tiger_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" "$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")" + 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 +306,88 @@ 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" ] && + [ -f "$dependency_dir/share/gdal/gdalvrt.xsd" ] && [ -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" + # 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" + [ -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 +442,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 +463,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 +478,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 +511,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 @@ -418,7 +519,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 @@ -487,7 +588,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 +598,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 +653,27 @@ 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" - 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/" + 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" + 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/" } oliphaunt_postgis_patch_extension_makefile() { @@ -549,13 +682,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 +698,69 @@ 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_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" + 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 +789,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 +822,9 @@ 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")" && @@ -632,19 +845,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,13 +872,11 @@ 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 - --without-tiger --disable-nls ) case "$oliphaunt_mobile_target" in @@ -692,6 +905,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 @@ -699,14 +914,17 @@ 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" + 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 -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" + oliphaunt_postgis_build_topology_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 +934,11 @@ 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) + 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" \ @@ -727,6 +950,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..8f695f040 --- /dev/null +++ b/src/native/runtime/bin/mobile-postgis-extensions.test.sh @@ -0,0 +1,196 @@ +#!/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" +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() { + return 0 +} + +cmake_args_file="$test_root/cmake-args.txt" +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" + : > "$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" +} + +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 + 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 + +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/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/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..0d7d8558e 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 @@ -823,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, @@ -896,6 +912,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 +1041,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 +1107,180 @@ 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", + ); + } + 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"] { + 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(); + 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 + .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(); @@ -1483,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", @@ -2875,6 +3133,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 +3206,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 +3234,8 @@ staticSymbolPrefix= staticSymbolAliases= licenseFiles={license_value} licenseProfile={} +sqlMembers={sql_members} +nativeModuleStems={native_module_stems} files=files ", profile.as_str() @@ -3004,26 +3269,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/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; 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/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/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 711cff99a..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 { @@ -645,6 +740,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 +815,9 @@ pub(super) const fn extension_sql_file_prefixes(extension: Extension) -> &'stati ExtensionId::Postgis => &[ "postgis_comments", "postgis_proc_set_search_path", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology", "rtpostgis", ], ExtensionId::Seg => &[], @@ -787,11 +911,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..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, @@ -233,6 +215,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/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..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 @@ -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; @@ -1293,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)) { @@ -1440,6 +1450,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 +1779,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 +1791,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 +1815,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 +1962,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 +1991,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 +2221,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 +2303,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 +2804,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 +2927,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 +2951,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 +2980,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 +2995,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 +3830,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 9bd0307d2..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": "c1d2e09905d7ecc0172173b34b9e4104dad58987503dd3dab7af4ae78890d9f3", + "sourceCatalogSha256": "dbe1ab284693ef923cb12e35e1c75bcc7fc1ed42af1fecf9fe9fee0aaf0ef8d0", "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..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=c1d2e09905d7ecc0172173b34b9e4104dad58987503dd3dab7af4ae78890d9f3 +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 c906a044e..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 @@ -576,8 +576,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()); } @@ -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,19 +856,54 @@ 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(); } - equal(16L, upstreamFiles, "PostGIS exact upstream legal file count"); + equal(17L, upstreamFiles, "PostGIS exact upstream legal file count"); } finally { deleteRecursively(root); } @@ -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", @@ -2590,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", @@ -2777,8 +2828,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 +2971,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 +3354,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 a510a3679..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 = "c1d2e09905d7ecc0172173b34b9e4104dad58987503dd3dab7af4ae78890d9f3" 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" }, { @@ -781,9 +959,37 @@ 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": [], + "dependencies": [ + "fuzzystrmatch" + ], "displayName": "PostGIS", "extensionSqlFileNames": [ "uninstall_postgis.sql" @@ -791,10 +997,14 @@ export const GENERATED_EXTENSION_METADATA = [ "extensionSqlFilePrefixes": [ "postgis_comments", "postgis_proc_set_search_path", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology", "rtpostgis" ], "id": "postgis", "iosStaticDependencies": [ + "gdal", "geos", "geos-c", "json-c", @@ -805,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", @@ -818,11 +1033,45 @@ 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": [], + "selectedExtensionDependencies": [ + "fuzzystrmatch" + ], "sharedPreloadLibraries": [], "sourceKind": "postgis", + "sqlMembers": [ + "postgis", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology" + ], "sqlName": "postgis" }, { @@ -839,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", @@ -847,6 +1099,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "seg" + ], "sqlName": "seg" }, { @@ -863,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", @@ -871,6 +1129,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tablefunc" + ], "sqlName": "tablefunc" }, { @@ -887,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", @@ -895,6 +1159,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tcn" + ], "sqlName": "tcn" }, { @@ -911,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", @@ -919,6 +1189,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tsm_system_rows" + ], "sqlName": "tsm_system_rows" }, { @@ -935,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", @@ -943,6 +1219,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tsm_system_time" + ], "sqlName": "tsm_system_time" }, { @@ -961,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", @@ -971,6 +1253,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "unaccent" + ], "sqlName": "unaccent" }, { @@ -989,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", @@ -997,6 +1285,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "uuid-ossp" + ], "sqlName": "uuid-ossp" }, { @@ -1013,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", @@ -1021,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..b38acfb5d 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; @@ -677,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, [ @@ -696,6 +708,8 @@ function validateExtension(value, label, carriers) { 'sqlName', 'tag', 'version', + 'sqlMembers', + 'nativeModuleStems', ], label, ); @@ -740,6 +754,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 +840,8 @@ function validateExtension(value, label, carriers) { return { assets: { dependencyFrameworks, extension, runtime }, createsExtension: root.createsExtension, + sqlMembers, + nativeModuleStems, dataFiles, dependencies, extensionSqlFileNames, @@ -1797,28 +1833,18 @@ async function validateExactExtensionArtifactInventory( for (const legalFile of carrier.legal.files) expectedFiles.add(legalFile.member); if (carrier.createsExtension) { const extensionRoot = 'files/share/postgresql/extension'; - const control = `${extensionRoot}/${carrier.sqlName}.control`; - if (!actualFiles.has(control)) fail(`${source} is missing canonical control file ${control}`); - expectedFiles.add(control); + const sqlMembers = carrier.sqlMembers; 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); - }); - 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); + 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); + }); }); - 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); @@ -1828,6 +1854,21 @@ 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); + 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 +1921,15 @@ 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); requireProperty(manifest, 'pgMajor', '18', manifestFile); requireProperty(manifest, 'sqlName', carrier.sqlName, manifestFile); @@ -1971,7 +2021,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 +2371,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 +2479,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 ba2e04670..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,11 +532,27 @@ async function extensionRow(root, config) { `staticSymbolAliases=${staticSymbolAliases.join(',')}`, `licenseFiles=${licenseFiles.join(',')}`, `licenseProfile=${profile}`, + ...(config.sqlName === 'pgtap' + ? [] + : [ + `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 +692,13 @@ async function extensionRow(root, config) { extension: { assets: logicalAssets.map(({ locator }) => locator), createsExtension, + // 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, @@ -769,6 +792,7 @@ async function createFixture(root) { { sqlName: 'auto_explain' }, { sqlName: 'cube' }, { sqlName: 'earthdistance' }, + { sqlName: 'fuzzystrmatch' }, { sqlName: 'pgcrypto' }, { sqlName: 'pgtap', includeUpdateSql: true }, { @@ -789,11 +813,20 @@ 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, 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'], @@ -897,11 +930,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'), @@ -976,6 +1018,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', @@ -986,6 +1029,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', ]); @@ -1054,12 +1098,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,pgcrypto,pgtap,postgis$/mu); assert.match( runtimeManifest, - /^mobileStaticRegistryRegistered=auto_explain,cube,earthdistance,pgcrypto,postgis$/mu, + /^extensions=cube,earthdistance,fuzzystrmatch,pgcrypto,pgtap,postgis$/mu, + ); + assert.match( + runtimeManifest, + /^mobileStaticRegistryRegistered=auto_explain,cube,earthdistance,fuzzystrmatch,pgcrypto,postgis$/mu, ); const packageSize = await fs.readFile( path.join( @@ -1074,7 +1121,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'), @@ -1173,8 +1220,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( @@ -1195,8 +1242,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( @@ -1535,6 +1582,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; @@ -1794,6 +1878,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/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<()> { 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/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..606e42ff0 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,49 @@ "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 + }, + { + "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..6be2f1e4a 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, @@ -164,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`); @@ -171,6 +184,23 @@ 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, + `${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 +220,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, @@ -275,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; } @@ -596,11 +639,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 +680,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 +724,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)}`); @@ -715,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); @@ -724,11 +775,17 @@ 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 ( + !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) { 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..ec90c7518 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,94 @@ 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 postgisResources = await validateSwiftExtensionResourceArtifact({ + extension: postgis, + canonical: catalog.get('postgis'), + nativeRuntime: document.nativeRuntime, + }); + assert.ok( + postgisResources.files.some( + ({ relative }) => relative === 'extension/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: postgis, + canonical: catalog.get('postgis'), + nativeRuntime: document.nativeRuntime, + }), + /postgis_topology\.control and canonical base installation SQL/u, + ); + await fs.writeFile(missingCompanion, companionBytes); + 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: postgis, + canonical: catalog.get('postgis'), + nativeRuntime: document.nativeRuntime, + }), + /undeclared extension SQL\/control file/u, + ); + await fs.rm(foreignControl); 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..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, [ @@ -438,6 +439,8 @@ export function validateSelection( 'sharedPreloadLibraries', 'sqlName', 'version', + 'sqlMembers', + 'nativeModuleStems', ], `extensions[${index}]`, ); @@ -498,6 +501,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..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, [ @@ -463,6 +464,8 @@ function validateExtension(value, label, carriers) { 'sqlName', 'tag', 'version', + 'sqlMembers', + 'nativeModuleStems', ], label, ); @@ -483,6 +486,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 +1034,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..2bdcde988 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( @@ -510,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( @@ -532,6 +540,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 +618,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 +645,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 +1332,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..bf7aa9831 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, + omitted.length === 2 + ? /incompatible with the SDK extension contract/ + : /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(); + 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', + ); + 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'), { @@ -1256,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/generated/extensions.ts b/src/native/sdks/ts/src/generated/extensions.ts index c729ea666..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 = "c1d2e09905d7ecc0172173b34b9e4104dad58987503dd3dab7af4ae78890d9f3" 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" }, { @@ -748,9 +926,37 @@ 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": [], + "dependencies": [ + "fuzzystrmatch" + ], "displayName": "PostGIS", "extensionSqlFileNames": [ "uninstall_postgis.sql" @@ -758,12 +964,20 @@ export const GENERATED_EXTENSION_METADATA = [ "extensionSqlFilePrefixes": [ "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", @@ -777,11 +991,45 @@ 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": [], + "selectedExtensionDependencies": [ + "fuzzystrmatch" + ], "sharedPreloadLibraries": [], "sourceKind": "postgis", + "sqlMembers": [ + "postgis", + "postgis_raster", + "postgis_tiger_geocoder", + "postgis_topology" + ], "sqlName": "postgis" }, { @@ -797,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", @@ -805,6 +1056,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "seg" + ], "sqlName": "seg" }, { @@ -820,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", @@ -828,6 +1085,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tablefunc" + ], "sqlName": "tablefunc" }, { @@ -843,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", @@ -851,6 +1114,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tcn" + ], "sqlName": "tcn" }, { @@ -866,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", @@ -874,6 +1143,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tsm_system_rows" + ], "sqlName": "tsm_system_rows" }, { @@ -889,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", @@ -897,6 +1172,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "tsm_system_time" + ], "sqlName": "tsm_system_time" }, { @@ -914,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", @@ -924,6 +1205,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "unaccent" + ], "sqlName": "unaccent" }, { @@ -939,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", @@ -947,6 +1234,9 @@ export const GENERATED_EXTENSION_METADATA = [ "selectedExtensionDependencies": [], "sharedPreloadLibraries": [], "sourceKind": "postgres-contrib", + "sqlMembers": [ + "uuid-ossp" + ], "sqlName": "uuid-ossp" }, { @@ -962,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", @@ -970,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..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, @@ -100,6 +101,8 @@ type NpmExtensionMemberContract = { sqlName: string; createsExtension: boolean; nativeModuleStem: string | null; + nativeModuleStems: string[]; + sqlMembers: string[]; dependencies: string[]; dataFiles: string[]; extensionSqlFileNames: string[]; @@ -142,7 +145,9 @@ const NPM_EXTENSION_CONTRACT_MEMBER_FIELDS = [ 'extensionSqlFilePrefixes', 'licenseFiles', 'nativeModuleStem', + 'nativeModuleStems', 'sharedPreloadLibraries', + 'sqlMembers', 'sqlName', ] as const; @@ -236,7 +241,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 +261,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 +280,7 @@ export async function materializeExtensionInstall( version: entry.version, sqlName: entry.sqlName, contract: entry.contract, + payloadDigest: entry.payloadDigest, })), }, null, @@ -341,6 +347,7 @@ type ResolvedExtensionPackage = { version: string; sqlName: string; contract: NpmExtensionMemberContract; + payloadDigest: string; runtimeDirectories: string[]; moduleDirectories: string[]; }; @@ -474,6 +481,7 @@ async function resolveExtensionPackage( version: packageJson.version, sqlName, contract: selectedContract, + payloadDigest: await extensionPayloadDigest(runtimeDirectories), runtimeDirectories, moduleDirectories, }; @@ -599,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) @@ -621,6 +634,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 +700,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 +781,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,25 +1116,18 @@ 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))) ); } -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(); @@ -1144,6 +1181,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 +1202,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 +1217,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 +1292,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/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( diff --git a/src/test-fixtures/extensions/postgis.sql b/src/test-fixtures/extensions/postgis.sql index dc5dafeac..8f3dea470 100644 --- a/src/test-fixtures/extensions/postgis.sql +++ b/src/test-fixtures/extensions/postgis.sql @@ -1,3 +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)); @@ -16,7 +32,27 @@ DECLARE area float8; polygons int; nearby int; + 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; + -- 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' + 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 RAISE EXCEPTION 'postgis geometry distance failed: %', distance; @@ -57,4 +93,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/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 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/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..e0f11ca43 --- /dev/null +++ b/src/wasix/runtime/assets/build/build_wasix_gdal.sh @@ -0,0 +1,128 @@ +#!/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" ] && + [ -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" + exit 0 +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 \ + "$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 -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" 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..10121323e 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 @@ -313,6 +315,7 @@ RelationGetIndexScan RelationGetNumberOfBlocksInFork RelationIdGetRelation RelationIsVisible +RelationNameGetTupleDesc RelationTruncate ReleaseAllPlanCacheRefsInOwner ReleaseBuffer @@ -341,6 +344,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 +365,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 +374,7 @@ SPI_prepare_extended SPI_processed SPI_register_relation SPI_register_trigger_data +SPI_repalloc SPI_result SPI_result_code_string SPI_returntuple @@ -425,6 +431,7 @@ UnlockReleaseBuffer UnlockTuple UnpinPortal UnregisterSnapshot +UnregisterXactCallback UpdateActiveSnapshotCommandId WaitForParallelWorkersToAttach WaitForParallelWorkersToFinish @@ -496,11 +503,11 @@ array_free_iterator array_iterate arraycontjoinsel arraycontsel +asc_tolower asin assign_expr_collations atan2l atexit -atof atoi be_lo_unlink before_shmem_exit @@ -736,6 +743,7 @@ free_object_addresses fseek fstat ftell +func_volatile function_parse_error_transpose fwrite gen_random_uuid @@ -768,6 +776,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,12 +954,13 @@ max_parallel_maintenance_workers memchr memcmp memset +mmap mul_size +munmap namein nanosleep network_cmp new_object_addresses -nextafterf nocache_index_getattr nocachegetattr nodeToString @@ -1026,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 @@ -1139,7 +1150,6 @@ repalloc_huge reservoir_get_next_S reservoir_init_selection_state resetStringInfo -roundf s_lock sampler_random_fract sampler_random_init_state @@ -1166,7 +1176,6 @@ smgrnblocks smgropen smgrpin smgrtruncate -sprintf sqrtl sscanf standard_ExecutorEnd 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/src/wasix/runtime/tools/xtask/src/asset_checks.rs b/src/wasix/runtime/tools/xtask/src/asset_checks.rs index def8e444f..311d3f6a0 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/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/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..daea34161 100644 --- a/src/wasix/sdks/rust/src/oliphaunt/generated_extensions.rs +++ b/src/wasix/sdks/rust/src/oliphaunt/generated_extensions.rs @@ -304,13 +304,23 @@ 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"), + }, + 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/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs b/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs index bc7123e27..47f9b80ab 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/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..cc19b2c0f 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/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/gdal']), + }, + postgis, + ); + expect(layout.mounts['/lib']?.files['postgresql/postgis_raster-3.so']).toEqual( + Uint8Array.of(1), + ); + expect(layout.mounts['/share']?.files['gdal/gdalvrt.xsd']).toEqual(Uint8Array.of(2)); + expect(() => + overlayExtensionArchive( + runtimeLayout(), + { + files: new Map([...files, ['share/gdal/undeclared.csv', Uint8Array.of(3)]]), + directories: new Set(), + }, + postgis, + ), + ).toThrow('unexpected share/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..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,6 +183,7 @@ export function wasixPostgresEnvironment( PGTZ: 'UTC', PG_COLOR: 'never', PROJ_DATA: '/share/proj', + 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 ca13a1050..fee269d32 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/gdal/', 'share/postgresql/extension/', 'share/postgresql/tsearch_data/', ]; @@ -900,6 +901,7 @@ function extensionMountTarget( 'lib/postgresql', 'share/proj', 'share/postgresql', + 'share/gdal', 'share/postgresql/extension', 'share/postgresql/tsearch_data', ]); diff --git a/tools/ci/ci-plan-node-products.test.mts b/tools/ci/ci-plan-node-products.test.mts index a13598c82..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', + ); } } }); @@ -1152,6 +1156,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..18e97dbd7 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', @@ -114,7 +115,9 @@ 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', oliphauntSwiftPackage: 'oliphaunt-swift:package', oliphauntJsPackage: 'oliphaunt-js:package', 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" } 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,