[SCM] PostGIS branch master updated. 3.7.0rc1-71-ged8527bca

git at osgeo.org git at osgeo.org
Mon Sep 28 08:46:39 PDT 2026


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "PostGIS".

The branch, master has been updated
       via  ed8527bca7653bb86644b05235f8b72380743db4 (commit)
       via  33d2b44e857c9e73c30005e82365dca5347e22af (commit)
      from  dfdaa24812e35bc88a64aa57c5a686408e38f4e9 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit ed8527bca7653bb86644b05235f8b72380743db4
Merge: dfdaa2481 33d2b44e8
Author: Regina Obe <lr at pcorp.us>
Date:   Mon Sep 28 08:46:16 2026 -0700

    Merge pull request '- Don't try to build visual images for sfcgal if sfcfal building is disabled' (!802) from robe/postgis:doc-conditional-build-sfcgal into master
    
    Closes https://trac.osgeo.org/postgis/ticket/6134
    
    Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/802


commit 33d2b44e857c9e73c30005e82365dca5347e22af
Author: Regina Obe <lr at pcorp.us>
Date:   Mon Sep 28 08:57:20 2026 -0400

    - Don't try to build visual images for sfcgal if sfcgal building is disabled
    Closes https://trac.osgeo.org/postgis/ticket/6134

diff --git a/doc/README b/doc/README
index dc0e308c1..04bc08b69 100644
--- a/doc/README
+++ b/doc/README
@@ -61,6 +61,11 @@ manual at https://gitea.osgeo.org/postgis/postgis/src/branch/master/doc/developm
 for layout roles, labels, textual-output policy, native geometry/EWKT output
 handling, and the visual review procedure.
 
+A build configured ``--without-sfcgal`` still documents the SFCGAL reference
+chapter, but the renderer skips the SFCGAL examples that would have to execute,
+and no figures are emitted for them. The examples that render from their
+recorded documented output are unaffected and keep their figures.
+
 To run garden tests against functions, see the Garden Checks section in
 https://gitea.osgeo.org/postgis/postgis/src/branch/master/doc/development/testing/.
 
diff --git a/doc/development/manual.md b/doc/development/manual.md
index 5937159d4..341e85276 100644
--- a/doc/development/manual.md
+++ b/doc/development/manual.md
@@ -78,6 +78,32 @@ Raster-returning SQL examples are also executed at build time; their exact
 pixels are embedded in the generated SVG container rather than checked in as a
 manual screenshot.
 
+### Optional backends and figures
+
+An example is rendered only when the backend it needs is present. Two mechanisms
+cooperate:
+
+* A `requires-<backend>-<version>` role on the `programlisting` or `screen`
+  gates a single example, and `requires-cgal-<version>` gates on the CGAL
+  version. A version the server does not report is treated as unavailable, so
+  the example is skipped rather than failed.
+* A whole chapter can be gated instead. `CHAPTER_CAPABILITY_REQUIREMENTS` in
+  [`utils/docs/postgis_exampletest.py`](../../utils/docs/postgis_exampletest.py)
+  maps a chapter `xml:id` to the backend every runnable example in it needs.
+  The SFCGAL reference chapter is gated this way because most of its examples
+  carry no per-example role. Documented-output examples are exempt, because they
+  are drawn from their recorded `<screen>` output and never execute.
+
+A figure is emitted only when the manifest holds an entry for that block, so a
+skipped example degrades to its recorded text output instead of a broken image.
+Do not add a figure for an example that a backend gate will skip.
+
+A build configured `--without-sfcgal` passes `--disable-sfcgal`, which tells the
+renderer to treat SFCGAL and CGAL as unavailable without probing the server.
+The SFCGAL reference chapter is still documented and its SFCGAL cheatsheet,
+comment script, and garden script are still built, but the examples in that
+chapter that would have to execute are omitted along with their figures.
+
 Garden checks combine documentation examples with behavior validation. Keep the
 command and review guidance in [Testing and debugging](testing/_index.md#garden-checks)
 and link back here when manual examples need attention.
diff --git a/doc/xsl/postgis-dblatex-block-labels.xsl b/doc/xsl/postgis-dblatex-block-labels.xsl
index 29670a408..b306959a2 100644
--- a/doc/xsl/postgis-dblatex-block-labels.xsl
+++ b/doc/xsl/postgis-dblatex-block-labels.xsl
@@ -61,18 +61,24 @@
   </xsl:variable>
   <xsl:variable name="manifest.visual"
                 select="document($postgis.visual.manifest)/visual-examples/visual[@refentry = $refentry.id and @screen = string($screen.ordinal)]"/>
+	<!-- A figure is only emitted when the manifest actually holds an entry for
+	     this block.  Examples skipped at build time (unavailable backend, or
+	     the visual-skip role) never reach the manifest, and referencing their
+	     SVG would break the PDF include.  The same test guards
+	     visual.preferred so the text output is not dropped in favour of a
+	     figure that was never built. -->
 	<xsl:variable name="visual.id">
     <xsl:choose>
-      <xsl:when test="contains($role.tokens, ' visual-primary ') and (@xml:id or @id)">
+      <xsl:when test="$manifest.visual and contains($role.tokens, ' visual-primary ') and (@xml:id or @id)">
         <xsl:value-of select="(@xml:id | @id)[1]"/>
       </xsl:when>
       <xsl:otherwise><xsl:value-of select="$manifest.visual/@id"/></xsl:otherwise>
     </xsl:choose>
   </xsl:variable>
 	<xsl:variable name="visual.preferred"
-	              select="not(contains($role.tokens, ' text-primary '))
-	                      and ($manifest.visual/@preferred = 'true'
-	                           or (not($manifest.visual) and contains($role.tokens, ' visual-primary ')))"/>
+	              select="string($visual.id) != ''
+	                      and not(contains($role.tokens, ' text-primary '))
+	                      and $manifest.visual/@preferred = 'true'"/>
 	<!--
 	  HTML can keep a large WKT output collapsed next to its interactive figure.
 	  In the non-interactive PDF the preferred figure carries the same geometry,
diff --git a/doc/xsl/postgis-html-block-labels.xsl b/doc/xsl/postgis-html-block-labels.xsl
index a92a9cb85..8cc37f4f5 100644
--- a/doc/xsl/postgis-html-block-labels.xsl
+++ b/doc/xsl/postgis-html-block-labels.xsl
@@ -160,7 +160,7 @@
                 select="document($postgis.visual.manifest)/visual-examples/visual[@refentry = $next.refentry.id and @screen = string($next.screen.ordinal)]"/>
   <xsl:variable name="visual.id">
     <xsl:choose>
-      <xsl:when test="$next.screen and contains($next.screen.role.tokens, ' visual-primary ') and $next.screen/@xml:id">
+      <xsl:when test="$next.screen and $next.manifest.visual and contains($next.screen.role.tokens, ' visual-primary ') and $next.screen/@xml:id">
         <xsl:value-of select="$next.screen/@xml:id"/>
       </xsl:when>
       <xsl:when test="$next.screen"><xsl:value-of select="$next.manifest.visual/@id"/></xsl:when>
@@ -281,18 +281,23 @@
       <xsl:with-param name="kind" select="'show-readable-ewkt'"/>
     </xsl:call-template>
   </xsl:variable>
+  <!-- A figure is only emitted when the manifest actually holds an entry for
+       this block.  Examples skipped at build time (unavailable backend, or the
+       visual-skip role) never reach the manifest, and referencing their SVG
+       would emit a broken image.  The same test guards visual.preferred so the
+       text output is not hidden in favour of a figure that was never built. -->
   <xsl:variable name="visual.id">
     <xsl:choose>
-      <xsl:when test="contains($role.tokens, ' visual-primary ') and @xml:id">
+      <xsl:when test="$manifest.visual and contains($role.tokens, ' visual-primary ') and @xml:id">
         <xsl:value-of select="@xml:id"/>
       </xsl:when>
       <xsl:otherwise><xsl:value-of select="$manifest.visual/@id"/></xsl:otherwise>
     </xsl:choose>
   </xsl:variable>
   <xsl:variable name="visual.preferred"
-                select="not(contains($role.tokens, ' text-primary '))
-                        and ($manifest.visual/@preferred = 'true'
-                             or (not($manifest.visual) and contains($role.tokens, ' visual-primary ')))"/>
+                select="string($visual.id) != ''
+                        and not(contains($role.tokens, ' text-primary '))
+                        and $manifest.visual/@preferred = 'true'"/>
 
   <div role="group" data-postgis-block="output">
     <xsl:attribute name="class">
diff --git a/regress/Makefile.in b/regress/Makefile.in
index 13870797b..3ebd86103 100644
--- a/regress/Makefile.in
+++ b/regress/Makefile.in
@@ -29,9 +29,19 @@ POSTGIS_MICRO_VERSION=@POSTGIS_MICRO_VERSION@
 #
 GARDENDB=postgis_garden
 EXAMPLETESTDB=postgis_exampletest
-EXAMPLETEST_RUN_FLAGS = --keep-going
 EXAMPLETEST_VISUAL_JOBS ?= 2
 
+# A build without the SFCGAL backend cannot run the SFCGAL chapter examples or
+# produce their figures.  Tell the tester so it skips them instead of probing
+# for functions that were never installed.  Each target spells out its own
+# EXAMPLETEST_RUN_FLAGS below, so this has to be repeated there too.
+ifeq ($(HAVE_SFCGAL),yes)
+EXAMPLETEST_SFCGAL_FLAGS =
+else
+EXAMPLETEST_SFCGAL_FLAGS = --disable-sfcgal
+endif
+EXAMPLETEST_RUN_FLAGS = --keep-going $(EXAMPLETEST_SFCGAL_FLAGS)
+
 # Where we put our regression installation
 # See https://trac.osgeo.org/postgis/ticket/5066
 ifeq ($(MINGWBUILD),1)
@@ -152,7 +162,7 @@ exampletest: staged-install exampletest-run
 
 visual-examples: staged-install visual-examples-run
 
-visual-examples-run: EXAMPLETEST_RUN_FLAGS = --keep-going --visual-only --jobs $(EXAMPLETEST_VISUAL_JOBS)
+visual-examples-run: EXAMPLETEST_RUN_FLAGS = --keep-going --visual-only --jobs $(EXAMPLETEST_VISUAL_JOBS) $(EXAMPLETEST_SFCGAL_FLAGS)
 visual-examples-run: EXAMPLETEST_CHECK_ENVIRONMENT = no
 visual-examples-run: exampletest-run
 
diff --git a/utils/docs/postgis_exampletest.py b/utils/docs/postgis_exampletest.py
index af3611c47..ca7a50031 100755
--- a/utils/docs/postgis_exampletest.py
+++ b/utils/docs/postgis_exampletest.py
@@ -73,6 +73,17 @@ FUNCTION_CAPABILITY_REQUIREMENTS = (
     )),
     ("cgal", (6, 0, 0), re.compile(r"\bCG_PolygonRepair\s*\(", re.I)),
 )
+# Chapters that document an optional backend wholesale.  Every runnable example
+# in them needs that backend, and most carry no per-example role, so the chapter
+# itself supplies the requirement.  The minimum is the oldest SFCGAL PostGIS can
+# be configured against at all (configure.ac rejects anything below 1.3.1), so
+# this only ever tests for presence: an absent backend reports an empty version
+# and fails the comparison.  Documented-output examples are exempt at example
+# level, because they are rendered from their recorded output and never execute.
+CHAPTER_CAPABILITY_REQUIREMENTS = {
+    "reference_sfcgal": ("sfcgal", (1, 0, 0)),
+    "reference_sfcgal_legacy": ("sfcgal", (1, 0, 0)),
+}
 ENVIRONMENT_CHECKS = (
     {
         "label": "PROJ grid au_icsm_GDA94_GDA2020_conformal_and_distortion.tif",
@@ -195,9 +206,14 @@ CATALOG_QUERY_RE = re.compile(r"\bpg_available_extensions\b", re.I)
 
 
 class ExampleTester:
-    def __init__(self, xml_file):
+    # Class-level default so helpers keep working on instances built with
+    # __new__ (the parser-only callers in the unit tests).
+    disabled_capabilities = frozenset()
+
+    def __init__(self, xml_file, disabled_capabilities=()):
         self.index = parse_xml(xml_file)
         self.doc = self.index.tree
+        self.disabled_capabilities = frozenset(disabled_capabilities)
 
     def node_text(self, node):
         return "".join(node.itertext()).replace("\r\n", "\n").replace("\r", "\n")
@@ -239,6 +255,24 @@ class ExampleTester:
                 })
         return requirements
 
+    def chapter_capability_requirements(self, node):
+        requirements = []
+        for ancestor in self.index.ancestors(node):
+            if ancestor.tag != f"{{{DOCBOOK_NS}}}chapter":
+                continue
+            requirement = CHAPTER_CAPABILITY_REQUIREMENTS.get(
+                ancestor.get(f"{{{XML_NS}}}id")
+            )
+            if requirement is None:
+                continue
+            name, minimum = requirement
+            requirements.append({
+                "name": name,
+                "minimum": minimum,
+                "role": f"requires-{name}-" + ".".join(str(part) for part in minimum),
+            })
+        return requirements
+
     def merge_capability_requirements(self, requirements):
         merged = {}
         for requirement in requirements:
@@ -251,6 +285,9 @@ class ExampleTester:
     def runtime_capabilities(self, database, names):
         capabilities = {}
         for name in sorted(names):
+            if name in self.disabled_capabilities:
+                capabilities[name] = ()
+                continue
             query = CAPABILITY_VERSION_QUERIES[name]
             try:
                 value = self.run_psql_scalar(database, query)
@@ -1380,6 +1417,7 @@ class ExampleTester:
             "requirements": self.merge_capability_requirements(
                 self.capability_requirements(node)
                 + self.inferred_capability_requirements(query)
+                + ([] if documented_only else self.chapter_capability_requirements(node))
             ),
             "documented_only": documented_only,
             "volatile": self.query_is_version_example(query) or self.query_is_catalog_example(query),
@@ -1794,6 +1832,8 @@ class ExampleTester:
             example for example in all_examples
             if not self.requirements_satisfied(example.get("requirements", []), capabilities)
         ]
+        # Documented-output figures are drawn from their recorded screen and never
+        # touch a backend, so they are deliberately not capability filtered.
         documented_visuals = [
             example for example in all_examples
             if example.get("documented_only") and example["visual_id"]
@@ -4639,6 +4679,11 @@ def parse_args():
     parser.add_argument("--render-dir", help="write selected build-time SVG assets after a successful --run")
     parser.add_argument("--visual-only", action="store_true", help="run only selected visual examples")
     parser.add_argument("--jobs", type=int, default=1, help="parallel workers for --visual-only (default: 1)")
+    parser.add_argument(
+        "--disable-sfcgal",
+        action="store_true",
+        help="treat the SFCGAL backend as unavailable without probing the server",
+    )
     parser.add_argument("xml_file")
     return parser.parse_args()
 
@@ -4647,7 +4692,8 @@ def main():
     args = parse_args()
 
     try:
-        tester = ExampleTester(args.xml_file)
+        disabled_capabilities = {"sfcgal", "cgal"} if args.disable_sfcgal else ()
+        tester = ExampleTester(args.xml_file, disabled_capabilities=disabled_capabilities)
 
         if args.report:
             tester.print_report()
diff --git a/utils/docs/tests/test_postgis_exampletest.py b/utils/docs/tests/test_postgis_exampletest.py
index 3d120d150..abf114c58 100644
--- a/utils/docs/tests/test_postgis_exampletest.py
+++ b/utils/docs/tests/test_postgis_exampletest.py
@@ -491,6 +491,96 @@ SELECT 'POINT(1 2)', $$LINESTRING(0 0,1 1)$$,
             "role": "auto-requires-sfcgal-2.0.0",
         }], requirements)
 
+    def _example_in_chapter(self, chapter_id, roles=""):
+        xml = f"""<book xmlns="http://docbook.org/ns/docbook">
+  <chapter xml:id="{chapter_id}">
+    <refentry xml:id="CG_Extrude"><refsection>
+      <programlisting{roles}>SELECT ST_AsText(
+        CG_Extrude('POLYGON((0 0,1 0,1 1,0 0))'::geometry, 0, 0, 10)
+      );</programlisting>
+      <screen role="visual-primary text-primary">POLYGON Z ((0 0 0,1 0 0,1 1 0,0 0 0))</screen>
+    </refsection></refentry>
+  </chapter>
+</book>"""
+        with tempfile.TemporaryDirectory() as directory:
+            path = Path(directory) / "chapter.xml"
+            path.write_text(xml, encoding="utf-8")
+            examples = ExampleTester(path).examples()
+
+        self.assertEqual(1, len(examples))
+        return examples[0]
+
+    def test_sfcgal_chapter_example_requires_sfcgal_backend(self):
+        example = self._example_in_chapter("reference_sfcgal")
+
+        self.assertEqual(
+            [{"name": "sfcgal", "minimum": (1, 0, 0), "role": "requires-sfcgal-1.0.0"}],
+            example["requirements"],
+        )
+        self.assertFalse(example["documented_only"])
+        self.assertEqual("visual-cg-extrude-01", example["visual_id"])
+
+    def test_sfcgal_legacy_chapter_example_requires_sfcgal_backend(self):
+        example = self._example_in_chapter("reference_sfcgal_legacy")
+
+        self.assertEqual(
+            [{"name": "sfcgal", "minimum": (1, 0, 0), "role": "requires-sfcgal-1.0.0"}],
+            example["requirements"],
+        )
+
+    def test_example_outside_sfcgal_chapter_is_not_gated(self):
+        example = self._example_in_chapter("reference_processing")
+
+        self.assertEqual([], example["requirements"])
+
+    def test_documented_output_in_sfcgal_chapter_keeps_its_figure(self):
+        # Documented-output examples are drawn from their recorded screen and
+        # never execute, so they must not be gated on the backend.
+        example = self._example_in_chapter("reference_sfcgal", ' role="documented-output"')
+
+        self.assertTrue(example["documented_only"])
+        self.assertEqual([], example["requirements"])
+        self.assertEqual("visual-cg-extrude-01", example["visual_id"])
+
+    def test_disabled_sfcgal_capability_skips_chapter_examples(self):
+        tester = ExampleTester.__new__(ExampleTester)
+        requirement = [
+            {"name": "sfcgal", "minimum": (1, 0, 0), "role": "requires-sfcgal-1.0.0"}
+        ]
+
+        # A backend that is absent, disabled at configure time, or whose version
+        # function is missing all report an empty version and must not satisfy
+        # the chapter requirement.
+        self.assertFalse(tester.requirements_satisfied(requirement, {"sfcgal": ()}))
+        self.assertFalse(tester.requirements_satisfied(requirement, {}))
+        self.assertTrue(tester.requirements_satisfied(requirement, {"sfcgal": (1, 5, 0)}))
+        self.assertTrue(tester.requirements_satisfied(requirement, {"sfcgal": (2, 3, 0)}))
+
+    def test_disable_sfcgal_short_circuits_runtime_capabilities(self):
+        calls = []
+        tester = ExampleTester.__new__(ExampleTester)
+
+        def record(database, query):
+            calls.append(query)
+            return "2.3.0"
+
+        tester.run_psql_scalar = record
+
+        tester.disabled_capabilities = frozenset({"sfcgal", "cgal"})
+        self.assertEqual(
+            {"cgal": (), "sfcgal": ()},
+            tester.runtime_capabilities("db", {"sfcgal", "cgal"}),
+        )
+        self.assertEqual([], calls)
+
+        calls.clear()
+        tester.disabled_capabilities = frozenset()
+        self.assertEqual(
+            {"sfcgal": (2, 3, 0)},
+            tester.runtime_capabilities("db", {"sfcgal"}),
+        )
+        self.assertEqual(["SELECT postgis_sfcgal_version()"], calls)
+
     def test_legacy_2d_wkt_output_compares_against_2d_projection(self):
         tester = ExampleTester.__new__(ExampleTester)
         query = tester.geometry_comparison_query(

-----------------------------------------------------------------------

Summary of changes:
 doc/README                                   |  5 ++
 doc/development/manual.md                    | 26 ++++++++
 doc/xsl/postgis-dblatex-block-labels.xsl     | 14 +++--
 doc/xsl/postgis-html-block-labels.xsl        | 15 +++--
 regress/Makefile.in                          | 14 ++++-
 utils/docs/postgis_exampletest.py            | 50 +++++++++++++++-
 utils/docs/tests/test_postgis_exampletest.py | 90 ++++++++++++++++++++++++++++
 7 files changed, 201 insertions(+), 13 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list