From d856b1a9fbcadd04524196afa0618eea47f161ae Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@cancellar.hu>
Date: Wed, 18 Feb 2026 20:36:48 +0100
Subject: [PATCH v2] Integrate headerscheck into the meson build

This patch properly integrates the headerscheck utility used by the make
build into the meson/ninja process by:

* Extracting the exclusion list from the bash script into a
  configuration file
* Generating two static library targets (C and C++) for meson which
  contain minimal C/C++ sources, each referencing a single header,
  excluded from the default build
* Adds two test targets (headercheck and headercheck-c++) depending on
  these static libraries

This process ensures that headercheck isn't included in the default
build, only when requested explicitly as part of the test suite, while
also making sure that we don't recompile anything unecessarily, only
when a dependency changes.
---
 doc/src/sgml/installation.sgml              |  13 ++
 meson_options.txt                           |   3 +
 src/meson.build                             |   2 +
 src/tools/pginclude/headercheck_discover.py | 103 ++++++++++++
 src/tools/pginclude/headercheck_generate.py |  55 +++++++
 src/tools/pginclude/headerscheck            | 169 +++++++-------------
 src/tools/pginclude/headerscheck_skip       | 118 ++++++++++++++
 src/tools/pginclude/meson.build             | 111 +++++++++++++
 8 files changed, 459 insertions(+), 115 deletions(-)
 create mode 100644 src/tools/pginclude/headercheck_discover.py
 create mode 100644 src/tools/pginclude/headercheck_generate.py
 create mode 100644 src/tools/pginclude/headerscheck_skip
 create mode 100644 src/tools/pginclude/meson.build

diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index c903ccff988..f2467300ef2 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -3231,6 +3231,19 @@ ninja install
       </listitem>
      </varlistentry>
 
+     <varlistentry id="configure-headerscheck-meson">
+      <term><option>-Dheaderscheck={ true | false }</option></term>
+      <listitem>
+       <para>
+        Builds additional targets that compile every source-tree header file
+        standalone, verifying that each header has no missing includes or other
+        portability issues.  This is enabled by default.  When disabled, the
+        headerscheck targets are skipped entirely, which can speed up builds
+        when header verification is not needed.
+       </para>
+      </listitem>
+     </varlistentry>
+
       <varlistentry id="configure-segsize-blocks-meson">
        <term><option>-Dsegsize_blocks=SEGSIZE_BLOCKS</option></term>
        <listitem>
diff --git a/meson_options.txt b/meson_options.txt
index 6a793f3e479..30193dcd82b 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -49,6 +49,9 @@ option('injection_points', type: 'boolean', value: false,
 option('PG_TEST_EXTRA', type: 'string', value: '',
   description: 'Enable selected extra tests. Overridden by PG_TEST_EXTRA environment variable.')
 
+option('headerscheck', type: 'boolean', value: true,
+  description: 'Build headerscheck targets to verify headers compile standalone')
+
 option('PG_GIT_REVISION', type: 'string', value: 'HEAD',
   description: 'git revision to be packaged by pgdist target')
 
diff --git a/src/meson.build b/src/meson.build
index b57d4a5c259..8663ffba7b3 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -15,6 +15,8 @@ subdir('interfaces')
 
 subdir('tools/pg_bsd_indent')
 
+subdir('tools/pginclude')
+
 
 ### Generate a Makefile.global that's complete enough for PGXS to work.
 #
diff --git a/src/tools/pginclude/headercheck_discover.py b/src/tools/pginclude/headercheck_discover.py
new file mode 100644
index 00000000000..5ce737d0f87
--- /dev/null
+++ b/src/tools/pginclude/headercheck_discover.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+#
+# headercheck_discover.py -- discover headers for headerscheck at configure time
+#
+# Scans the source tree for .h files, filters out skipped headers,
+# and outputs one line per header: "prelude:relative_path"
+#
+# Arguments: source_root skip_file mode
+#   mode is "c" or "c++"
+#
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+import os
+import sys
+
+
+def load_skip_list(skip_file, mode):
+    """Load the skip list, returning a set of paths to skip."""
+    skipped = set()
+    with open(skip_file) as f:
+        for line in f:
+            line = line.strip()
+            if not line or line.startswith('#'):
+                continue
+            if line.startswith('c_only:'):
+                if mode == 'c':
+                    skipped.add(line[len('c_only:'):].strip())
+            elif line.startswith('cxx_only:'):
+                if mode == 'c++':
+                    skipped.add(line[len('cxx_only:'):].strip())
+            else:
+                skipped.add(line)
+    return skipped
+
+
+def get_prelude(header_path):
+    """Determine the prelude (#include) needed for a header.
+
+    Uses the same logic as the headerscheck bash script.
+    """
+    if header_path in ('src/include/postgres.h',
+                       'src/include/postgres_fe.h',
+                       'src/include/c.h'):
+        return 'bare'
+
+    if header_path in ('src/interfaces/libpq/libpq-fe.h',
+                       'src/interfaces/libpq/libpq-events.h'):
+        return 'bare'
+
+    if header_path == 'src/interfaces/ecpg/ecpglib/ecpglib_extern.h':
+        return 'postgres_fe'
+
+    if header_path.startswith('src/interfaces/ecpg/ecpglib/'):
+        return 'bare'
+
+    if header_path.startswith('src/interfaces/'):
+        return 'postgres_fe'
+
+    if header_path.startswith('src/bin/'):
+        return 'postgres_fe'
+
+    if header_path.startswith('src/fe_utils/'):
+        return 'postgres_fe'
+
+    if header_path.startswith('src/port/'):
+        return 'bare'
+
+    if header_path.startswith('src/common/'):
+        return 'c'
+
+    return 'postgres'
+
+
+def main():
+    source_root = sys.argv[1]
+    skip_file = sys.argv[2]
+    mode = sys.argv[3]
+
+    skipped = load_skip_list(skip_file, mode)
+
+    headers = []
+    for search_dir in ['src', 'contrib']:
+        search_path = os.path.join(source_root, search_dir)
+        if not os.path.isdir(search_path):
+            continue
+        for dirpath, dirnames, filenames in os.walk(search_path):
+            dirnames.sort()
+            for filename in sorted(filenames):
+                if not filename.endswith('.h'):
+                    continue
+                full_path = os.path.join(dirpath, filename)
+                rel_path = os.path.relpath(full_path, source_root)
+                if rel_path in skipped:
+                    continue
+                prelude = get_prelude(rel_path)
+                headers.append('{}:{}'.format(prelude, rel_path))
+
+    for entry in headers:
+        print(entry)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/tools/pginclude/headercheck_generate.py b/src/tools/pginclude/headercheck_generate.py
new file mode 100644
index 00000000000..3ef0b5a3708
--- /dev/null
+++ b/src/tools/pginclude/headercheck_generate.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+#
+# headercheck_generate.py -- generate source files for headerscheck at build time
+#
+# For each header entry, generates a .c (or .cpp) file that includes
+# the header with the appropriate prelude.
+#
+# Arguments: output_dir is_cpp entry [entry ...]
+#   is_cpp is "true" or "false"
+#   Each entry is "prelude:relative_path" as produced by headercheck_discover.py
+#
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+import os
+import sys
+
+
+def underscorify(s):
+    """Replace non-alphanumeric characters with underscores (like meson's underscorify)."""
+    return ''.join(c if c.isalnum() else '_' for c in s)
+
+
+def main():
+    output_dir = sys.argv[1]
+    is_cpp = sys.argv[2] == 'true'
+    entries = sys.argv[3:]
+
+    ext = 'cpp' if is_cpp else 'c'
+
+    for entry in entries:
+        prelude, rel_path = entry.split(':', 1)
+        out_name = 'hdrchk_' + underscorify(rel_path) + '.' + ext
+        out_path = os.path.join(output_dir, out_name)
+
+        lines = []
+        if is_cpp:
+            lines.append('extern "C" {')
+        if prelude == 'postgres':
+            lines.append('#include "postgres.h"')
+        elif prelude == 'postgres_fe':
+            lines.append('#include "postgres_fe.h"')
+        elif prelude == 'c':
+            lines.append('#include "c.h"')
+        # bare: no prelude
+        lines.append('#include "{}"'.format(rel_path))
+        if is_cpp:
+            lines.append('}')
+        lines.append('')
+
+        with open(out_path, 'w') as f:
+            f.write('\n'.join(lines))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/tools/pginclude/headerscheck b/src/tools/pginclude/headerscheck
index 7a6755991bb..cb1f7836245 100755
--- a/src/tools/pginclude/headerscheck
+++ b/src/tools/pginclude/headerscheck
@@ -37,6 +37,57 @@ fi
 
 me=`basename $0`
 
+mydir=`dirname $0`
+skipfile="$mydir/headerscheck_skip"
+
+# Load the skip list into shell variables.
+# We build a newline-separated list of paths to skip.
+skip_all=""
+skip_c_only=""
+skip_cxx_only=""
+while IFS= read -r line; do
+    line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+    case "$line" in
+        ''|'#'*) continue ;;
+    esac
+    case "$line" in
+        c_only:*)
+            path=$(echo "$line" | sed 's/^c_only:[[:space:]]*//')
+            skip_c_only="$skip_c_only
+$path"
+            ;;
+        cxx_only:*)
+            path=$(echo "$line" | sed 's/^cxx_only:[[:space:]]*//')
+            skip_cxx_only="$skip_cxx_only
+$path"
+            ;;
+        *)
+            skip_all="$skip_all
+$line"
+            ;;
+    esac
+done < "$skipfile"
+
+should_skip() {
+    _path="$1"
+
+    case "$skip_all" in
+        *"$_path"*) return 0 ;;
+    esac
+
+    if $cplusplus; then
+        case "$skip_cxx_only" in
+            *"$_path"*) return 0 ;;
+        esac
+    else
+        case "$skip_c_only" in
+            *"$_path"*) return 0 ;;
+        esac
+    fi
+
+    return 1
+}
+
 # These switches are g++ specific, you may override if necessary.
 CXXFLAGS=${CXXFLAGS:- -fsyntax-only -Wall}
 
@@ -85,121 +136,9 @@ exit_status=0
 # Scan all of src/ and contrib/ for header files.
 for f in `cd "$srcdir" && find src contrib -name '*.h' -print`
 do
-	# Ignore files that are unportable or intentionally not standalone.
-
-	# These files are platform-specific, and c.h will include the
-	# one that's relevant for our current platform anyway.
-	test "$f" = src/include/port/cygwin.h && continue
-	test "$f" = src/include/port/darwin.h && continue
-	test "$f" = src/include/port/freebsd.h && continue
-	test "$f" = src/include/port/linux.h && continue
-	test "$f" = src/include/port/netbsd.h && continue
-	test "$f" = src/include/port/openbsd.h && continue
-	test "$f" = src/include/port/solaris.h && continue
-	test "$f" = src/include/port/win32.h && continue
-
-	# Additional Windows-specific headers.
-	test "$f" = src/include/port/win32_port.h && continue
-	test "$f" = src/include/port/win32/netdb.h && continue
-	test "$f" = src/include/port/win32/sys/resource.h && continue
-	test "$f" = src/include/port/win32/sys/socket.h && continue
-	test "$f" = src/include/port/win32_msvc/dirent.h && continue
-	test "$f" = src/include/port/win32_msvc/utime.h && continue
-	test "$f" = src/include/port/win32ntdll.h && continue
-	test "$f" = src/port/pthread-win32.h && continue
-
-	# Likewise, these files are platform-specific, and the one
-	# relevant to our platform will be included by atomics.h.
-	test "$f" = src/include/port/atomics/arch-arm.h && continue
-	test "$f" = src/include/port/atomics/arch-ppc.h && continue
-	test "$f" = src/include/port/atomics/arch-x86.h && continue
-	test "$f" = src/include/port/atomics/fallback.h && continue
-	test "$f" = src/include/port/atomics/generic.h && continue
-	test "$f" = src/include/port/atomics/generic-gcc.h && continue
-	test "$f" = src/include/port/atomics/generic-msvc.h && continue
-
-	# sepgsql.h depends on headers that aren't there on most platforms.
-	test "$f" = contrib/sepgsql/sepgsql.h && continue
-
-	# nodetags.h cannot be included standalone: it's just a code fragment.
-	test "$f" = src/include/nodes/nodetags.h && continue
-	test "$f" = src/backend/nodes/nodetags.h && continue
-
-	# These files are not meant to be included standalone, because
-	# they contain lists that might have multiple use-cases.
-	test "$f" = src/include/access/rmgrlist.h && continue
-	test "$f" = src/include/parser/kwlist.h && continue
-	test "$f" = src/include/postmaster/proctypelist.h && continue
-	test "$f" = src/include/regex/regerrs.h && continue
-	test "$f" = src/include/storage/lwlocklist.h && continue
-	test "$f" = src/include/tcop/cmdtaglist.h && continue
-	test "$f" = src/interfaces/ecpg/preproc/c_kwlist.h && continue
-	test "$f" = src/interfaces/ecpg/preproc/ecpg_kwlist.h && continue
-	test "$f" = src/pl/plpgsql/src/pl_reserved_kwlist.h && continue
-	test "$f" = src/pl/plpgsql/src/pl_unreserved_kwlist.h && continue
-	test "$f" = src/pl/plpgsql/src/plerrcodes.h && continue
-	test "$f" = src/pl/plpython/spiexceptions.h && continue
-	test "$f" = src/pl/tcl/pltclerrcodes.h && continue
-
-	# Also not meant to be included standalone.
-	test "$f" = contrib/isn/EAN13.h && continue
-	test "$f" = contrib/isn/ISBN.h && continue
-	test "$f" = contrib/isn/ISMN.h && continue
-	test "$f" = contrib/isn/ISSN.h && continue
-	test "$f" = contrib/isn/UPC.h && continue
-
-	test "$f" = src/include/common/unicode_nonspacing_table.h && continue
-	test "$f" = src/include/common/unicode_east_asian_fw_table.h && continue
-
-	test "$f" = src/include/catalog/syscache_ids.h && continue
-	test "$f" = src/include/catalog/syscache_info.h && continue
-
-	# We can't make these Bison output files compilable standalone
-	# without using "%code require", which old Bison versions lack.
-	# parser/gram.h will be included by parser/gramparse.h anyway.
-	test "$f" = contrib/cube/cubeparse.h && continue
-	test "$f" = contrib/seg/segparse.h && continue
-	test "$f" = src/backend/bootstrap/bootparse.h && continue
-	test "$f" = src/backend/parser/gram.h && continue
-	test "$f" = src/backend/replication/repl_gram.h && continue
-	test "$f" = src/backend/replication/syncrep_gram.h && continue
-	test "$f" = src/backend/utils/adt/jsonpath_gram.h && continue
-	test "$f" = src/bin/pgbench/exprparse.h && continue
-	test "$f" = src/pl/plpgsql/src/pl_gram.h && continue
-	test "$f" = src/interfaces/ecpg/preproc/preproc.h && continue
-	test "$f" = src/test/isolation/specparse.h && continue
-
-	# This produces a "no previous prototype" warning.
-	! $cplusplus && test "$f" = src/include/storage/checksum_impl.h && continue
-
-	# SectionMemoryManager.h is C++
-	test "$f" = src/include/jit/SectionMemoryManager.h && continue
-
-	# ppport.h is not under our control, so we can't make it standalone.
-	test "$f" = src/pl/plperl/ppport.h && continue
-
-	# regression.h is not actually C, but ECPG code.
-	test "$f" = src/interfaces/ecpg/test/regression.h && continue
-	# printf_hack.h produces "unused function" warnings.
-	test "$f" = src/interfaces/ecpg/test/printf_hack.h && continue
-
-	if $cplusplus; then
-		# pg_trace.h and utils/probes.h can include sys/sdt.h from SystemTap,
-		# which itself contains C++ code and so won't compile with a C++
-		# compiler under extern "C" linkage.
-		test "$f" = src/include/pg_trace.h && continue
-		test "$f" = src/include/utils/probes.h && continue
-
-		# pg_dump is not C++-clean because it uses "public" and "namespace"
-		# as field names, which is unfortunate but we won't change it now.
-		test "$f" = src/bin/pg_dump/compress_gzip.h && continue
-		test "$f" = src/bin/pg_dump/compress_io.h && continue
-		test "$f" = src/bin/pg_dump/compress_lz4.h && continue
-		test "$f" = src/bin/pg_dump/compress_none.h && continue
-		test "$f" = src/bin/pg_dump/compress_zstd.h && continue
-		test "$f" = src/bin/pg_dump/parallel.h && continue
-		test "$f" = src/bin/pg_dump/pg_backup_archiver.h && continue
-		test "$f" = src/bin/pg_dump/pg_dump.h && continue
+	# Check the shared skip list
+	if should_skip "$f"; then
+		continue
 	fi
 
 	# Help ccache by using a stable name.  Remove slashes and dots.
diff --git a/src/tools/pginclude/headerscheck_skip b/src/tools/pginclude/headerscheck_skip
new file mode 100644
index 00000000000..a8251da5d76
--- /dev/null
+++ b/src/tools/pginclude/headerscheck_skip
@@ -0,0 +1,118 @@
+# Headers to skip during headerscheck/cpluspluscheck.
+#
+# Plain entries are skipped in both C and C++ modes.
+# Entries prefixed with "c_only:" are skipped only in C mode.
+# Entries prefixed with "cxx_only:" are skipped only in C++ mode.
+#
+# src/tools/pginclude/headerscheck_skip
+
+# Platform-specific headers (c.h includes the right one)
+src/include/port/cygwin.h
+src/include/port/darwin.h
+src/include/port/freebsd.h
+src/include/port/linux.h
+src/include/port/netbsd.h
+src/include/port/openbsd.h
+src/include/port/solaris.h
+src/include/port/win32.h
+
+# Additional Windows-specific headers
+src/include/port/win32_port.h
+src/include/port/win32/netdb.h
+src/include/port/win32/sys/resource.h
+src/include/port/win32/sys/socket.h
+src/include/port/win32_msvc/dirent.h
+src/include/port/win32_msvc/utime.h
+src/include/port/win32ntdll.h
+src/port/pthread-win32.h
+
+# Platform-specific atomics headers (atomics.h includes the right one)
+src/include/port/atomics/arch-arm.h
+src/include/port/atomics/arch-ppc.h
+src/include/port/atomics/arch-x86.h
+src/include/port/atomics/fallback.h
+src/include/port/atomics/generic.h
+src/include/port/atomics/generic-gcc.h
+src/include/port/atomics/generic-msvc.h
+
+# sepgsql.h depends on headers that aren't there on most platforms
+contrib/sepgsql/sepgsql.h
+
+# nodetags.h cannot be included standalone: it's just a code fragment
+src/include/nodes/nodetags.h
+src/backend/nodes/nodetags.h
+
+# These files are not meant to be included standalone, because
+# they contain lists that might have multiple use-cases.
+src/include/access/rmgrlist.h
+src/include/parser/kwlist.h
+src/include/postmaster/proctypelist.h
+src/include/regex/regerrs.h
+src/include/storage/lwlocklist.h
+src/include/tcop/cmdtaglist.h
+src/interfaces/ecpg/preproc/c_kwlist.h
+src/interfaces/ecpg/preproc/ecpg_kwlist.h
+src/pl/plpgsql/src/pl_reserved_kwlist.h
+src/pl/plpgsql/src/pl_unreserved_kwlist.h
+src/pl/plpgsql/src/plerrcodes.h
+src/pl/plpython/spiexceptions.h
+src/pl/tcl/pltclerrcodes.h
+
+# Also not meant to be included standalone
+contrib/isn/EAN13.h
+contrib/isn/ISBN.h
+contrib/isn/ISMN.h
+contrib/isn/ISSN.h
+contrib/isn/UPC.h
+
+src/include/common/unicode_nonspacing_table.h
+src/include/common/unicode_east_asian_fw_table.h
+
+src/include/catalog/syscache_ids.h
+src/include/catalog/syscache_info.h
+
+# We can't make these Bison output files compilable standalone
+# without using "%code require", which old Bison versions lack.
+# parser/gram.h will be included by parser/gramparse.h anyway.
+contrib/cube/cubeparse.h
+contrib/seg/segparse.h
+src/backend/bootstrap/bootparse.h
+src/backend/parser/gram.h
+src/backend/replication/repl_gram.h
+src/backend/replication/syncrep_gram.h
+src/backend/utils/adt/jsonpath_gram.h
+src/bin/pgbench/exprparse.h
+src/pl/plpgsql/src/pl_gram.h
+src/interfaces/ecpg/preproc/preproc.h
+src/test/isolation/specparse.h
+
+# This produces a "no previous prototype" warning
+c_only: src/include/storage/checksum_impl.h
+
+# SectionMemoryManager.h is C++
+src/include/jit/SectionMemoryManager.h
+
+# ppport.h is not under our control, so we can't make it standalone
+src/pl/plperl/ppport.h
+
+# regression.h is not actually C, but ECPG code
+src/interfaces/ecpg/test/regression.h
+# printf_hack.h produces "unused function" warnings
+src/interfaces/ecpg/test/printf_hack.h
+
+# pg_trace.h and utils/probes.h can include sys/sdt.h from SystemTap,
+# which itself contains C++ code and so won't compile with a C++
+# compiler under extern "C" linkage.
+cxx_only: src/include/pg_trace.h
+cxx_only: src/include/utils/probes.h
+
+# pg_dump is not C++-clean because it uses "public" and "namespace"
+# as field names, which is unfortunate but we won't change it now.
+cxx_only: src/bin/pg_dump/compress_gzip.h
+cxx_only: src/bin/pg_dump/compress_io.h
+cxx_only: src/bin/pg_dump/compress_lz4.h
+cxx_only: src/bin/pg_dump/compress_none.h
+cxx_only: src/bin/pg_dump/compress_zstd.h
+cxx_only: src/bin/pg_dump/parallel.h
+cxx_only: src/bin/pg_dump/pg_backup_archiver.h
+cxx_only: src/bin/pg_dump/pg_dump.h
diff --git a/src/tools/pginclude/meson.build b/src/tools/pginclude/meson.build
new file mode 100644
index 00000000000..4de87d655e0
--- /dev/null
+++ b/src/tools/pginclude/meson.build
@@ -0,0 +1,111 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Native headerscheck: compile every source-tree header standalone to
+# verify it doesn't have missing includes or other portability issues.
+#
+# At configure time, headercheck_discover.py scans the source tree and
+# emits one "prelude:path" line per header.  At build time,
+# headercheck_generate.py produces a tiny .c/.cpp file for each header
+# that #includes the appropriate prelude (postgres.h, postgres_fe.h,
+# c.h, or nothing) followed by the header under test.  These files are
+# compiled in a static_library as part of the default build.
+#
+# Controlled by -Dheaderscheck=true|false (default: true).
+#
+# The skip list (headerscheck_skip) is shared with the shell script
+# (src/tools/pginclude/headerscheck) used by the make build.
+
+if not get_option('headerscheck')
+  subdir_done()
+endif
+
+headercheck_discover = files('headercheck_discover.py')
+headercheck_skip_file = files('headerscheck_skip')
+
+# --- C mode ---
+
+c_header_entries = run_command(python, headercheck_discover,
+  meson.project_source_root(), headercheck_skip_file, 'c',
+  check: true).stdout().strip().split('\n')
+
+c_gen_names = []
+foreach entry : c_header_entries
+  parts = entry.split(':')
+  path = parts[1]
+  c_gen_names += 'hdrchk_' + path.underscorify() + '.c'
+endforeach
+
+headercheck_c_gen = custom_target('headercheck_c_gen',
+  output: c_gen_names,
+  command: [python, files('headercheck_generate.py'),
+            '@OUTDIR@', 'false'] + c_header_entries,
+  depends: [generated_backend_headers_stamp],
+)
+
+# Include paths matching what the shell script uses.  Adding all extra
+# paths (perl, python, ecpg, etc.) is harmless for headers that don't
+# need them, and avoids needing per-file compiler flags.
+headercheck_args = [
+  '-I' + meson.project_source_root(),
+  '-I' + meson.project_build_root(),
+  '-I' + meson.project_source_root() / 'src' / 'include',
+  '-I' + meson.project_build_root() / 'src' / 'include',
+  '-I' + meson.project_source_root() / 'src' / 'interfaces' / 'libpq',
+  '-I' + meson.project_build_root() / 'src' / 'interfaces' / 'libpq',
+  '-I' + meson.project_build_root() / 'src' / 'interfaces' / 'ecpg' / 'include',
+  '-I' + meson.project_source_root() / 'src' / 'interfaces' / 'ecpg' / 'include',
+  '-I' + meson.project_build_root() / 'src' / 'backend' / 'parser',
+  '-I' + meson.project_build_root() / 'src' / 'backend' / 'utils' / 'adt',
+]
+if perl_dep.found()
+  headercheck_args += perl_ccflags
+endif
+
+headercheck_deps = []
+if python3_dep.found()
+  headercheck_deps += python3_dep
+endif
+if icu.found()
+  headercheck_deps += icu
+endif
+if llvm.found()
+  headercheck_deps += llvm
+endif
+
+headercheck_c_lib = static_library('headercheck_c',
+  headercheck_c_gen,
+  c_args: headercheck_args,
+  dependencies: headercheck_deps,
+  implicit_include_directories: false,
+  install: false,
+)
+
+# --- C++ mode ---
+
+if have_cxx
+  cxx_header_entries = run_command(python, headercheck_discover,
+    meson.project_source_root(), headercheck_skip_file, 'c++',
+    check: true).stdout().strip().split('\n')
+
+  cxx_gen_names = []
+  foreach entry : cxx_header_entries
+    parts = entry.split(':')
+    path = parts[1]
+    cxx_gen_names += 'hdrchk_' + path.underscorify() + '.cpp'
+  endforeach
+
+  headercheck_cxx_gen = custom_target('headercheck_cxx_gen',
+    output: cxx_gen_names,
+    command: [python, files('headercheck_generate.py'),
+              '@OUTDIR@', 'true'] + cxx_header_entries,
+    depends: [generated_backend_headers_stamp],
+  )
+
+  headercheck_cxx_lib = static_library('headercheck_cxx',
+    headercheck_cxx_gen,
+    cpp_args: headercheck_args,
+    dependencies: headercheck_deps,
+    implicit_include_directories: false,
+    install: false,
+  )
+endif
-- 
2.43.0

