From e057b0b439d69b17fd6bc6cea266ef263213f450 Mon Sep 17 00:00:00 2001
From: Nazir Bilal Yavuz <byavuz81@gmail.com>
Date: Thu, 27 Feb 2025 17:45:31 +0300
Subject: [PATCH v12 2/7] meson: Test building extensions by using
 postgresql-extension.pc

The 'test_meson_extensions' pyton wrapper is added to run these tests.
It compiles and builds extensions at
${build}/testrun/meson_extensions/${extension_name} path.

The tests for building amcheck, auth_delay and postgres_fdw extensions
are added. These are also examples of how to build extensions by using
postgresql-extension.pc.

Author: Andres Freund <andres@anarazel.de>
Author: Nazir Bilal Yavuz <byavuz81@gmail.com>
Reviewed-by: Peter Eisentraut <peter@eisentraut.org>
Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
Discussion: https://postgr.es/m/206b001d-1884-4081-bd02-bed5c92f02ba%40eisentraut.org
---
 contrib/amcheck/meson-test.build      | 31 +++++++++++
 contrib/amcheck/meson.build           |  7 +++
 contrib/auth_delay/meson-test.build   | 20 +++++++
 contrib/auth_delay/meson.build        |  7 +++
 contrib/postgres_fdw/meson-test.build | 33 +++++++++++
 contrib/postgres_fdw/meson.build      |  7 +++
 meson.build                           | 36 ++++++++++++
 src/tools/test_meson_extensions       | 80 +++++++++++++++++++++++++++
 8 files changed, 221 insertions(+)
 create mode 100644 contrib/amcheck/meson-test.build
 create mode 100644 contrib/auth_delay/meson-test.build
 create mode 100644 contrib/postgres_fdw/meson-test.build
 create mode 100644 src/tools/test_meson_extensions

diff --git a/contrib/amcheck/meson-test.build b/contrib/amcheck/meson-test.build
new file mode 100644
index 00000000000..d3aed6b4bc1
--- /dev/null
+++ b/contrib/amcheck/meson-test.build
@@ -0,0 +1,31 @@
+# Copyright (c) 2022-2026, PostgreSQL Global Development Group
+
+project('amcheck', 'c')
+
+# This file will be moved to another directory during testing. By using
+# 'meson_source_dir', we ensure that the correct Meson source directory is
+# used.
+amcheck_path = get_option('meson_source_dir')
+
+amcheck_sources = files(
+  amcheck_path / 'verify_heapam.c',
+  amcheck_path / 'verify_nbtree.c',
+)
+
+pg_ext = dependency('postgresql-extension-warnings')
+
+amcheck = shared_module('amcheck',
+  amcheck_sources,
+  dependencies: pg_ext,
+  install_dir: pg_ext.get_variable(pkgconfig: 'dir_mod'),
+)
+
+install_data(
+  amcheck_path / 'amcheck.control',
+  amcheck_path / 'amcheck--1.0.sql',
+  amcheck_path / 'amcheck--1.0--1.1.sql',
+  amcheck_path / 'amcheck--1.1--1.2.sql',
+  amcheck_path / 'amcheck--1.2--1.3.sql',
+  amcheck_path / 'amcheck--1.3--1.4.sql',
+  install_dir: pg_ext.get_variable(pkgconfig: 'dir_data'),
+)
diff --git a/contrib/amcheck/meson.build b/contrib/amcheck/meson.build
index d5137ef691d..4952feebb8c 100644
--- a/contrib/amcheck/meson.build
+++ b/contrib/amcheck/meson.build
@@ -53,3 +53,10 @@ tests += {
     ],
   },
 }
+
+meson_extension_tests += {
+  'name': 'amcheck',
+  'kind': 'pkg_config',
+  'sd': meson.current_source_dir(),
+  'input': files('meson-test.build'),
+}
diff --git a/contrib/auth_delay/meson-test.build b/contrib/auth_delay/meson-test.build
new file mode 100644
index 00000000000..5d5bc4d740e
--- /dev/null
+++ b/contrib/auth_delay/meson-test.build
@@ -0,0 +1,20 @@
+# Copyright (c) 2022-2026, PostgreSQL Global Development Group
+
+project('auth_delay', 'c')
+
+# This file will be moved to another directory during testing. By using
+# 'meson_source_dir', we ensure that the correct Meson source directory is
+# used.
+auth_delay_path = get_option('meson_source_dir')
+
+auth_delay_sources = files(
+  auth_delay_path / 'auth_delay.c',
+)
+
+pg_ext = dependency('postgresql-extension-warnings')
+
+auth_delay = shared_module('auth_delay',
+  auth_delay_sources,
+  dependencies: pg_ext,
+  install_dir: pg_ext.get_variable(pkgconfig: 'dir_mod'),
+)
diff --git a/contrib/auth_delay/meson.build b/contrib/auth_delay/meson.build
index 21192992a84..a3286bcf3ff 100644
--- a/contrib/auth_delay/meson.build
+++ b/contrib/auth_delay/meson.build
@@ -15,3 +15,10 @@ auth_delay = shared_module('auth_delay',
   kwargs: contrib_mod_args,
 )
 contrib_targets += auth_delay
+
+meson_extension_tests += {
+  'name': 'auth_delay',
+  'kind': 'pkg_config',
+  'sd': meson.current_source_dir(),
+  'input': files('meson-test.build'),
+}
\ No newline at end of file
diff --git a/contrib/postgres_fdw/meson-test.build b/contrib/postgres_fdw/meson-test.build
new file mode 100644
index 00000000000..230c4ec4f50
--- /dev/null
+++ b/contrib/postgres_fdw/meson-test.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2026, PostgreSQL Global Development Group
+
+project('postgres_fdw', 'c')
+
+# This file will be moved to another directory during testing. By using
+# 'meson_source_dir', we ensure that the correct Meson source directory is
+# used.
+postgres_fdw_path = get_option('meson_source_dir')
+
+postgres_fdw_sources = files(
+  postgres_fdw_path / 'connection.c',
+  postgres_fdw_path / 'deparse.c',
+  postgres_fdw_path / 'option.c',
+  postgres_fdw_path / 'postgres_fdw.c',
+  postgres_fdw_path / 'shippable.c',
+)
+
+pg_ext = dependency('postgresql-extension-warnings')
+libpq = dependency('libpq')
+
+postgres_fdw = shared_module('postgres_fdw',
+  postgres_fdw_sources,
+  dependencies: [pg_ext, libpq],
+  install_dir: pg_ext.get_variable(pkgconfig: 'dir_mod'),
+)
+
+install_data(
+  postgres_fdw_path / 'postgres_fdw.control',
+  postgres_fdw_path / 'postgres_fdw--1.0.sql',
+  postgres_fdw_path / 'postgres_fdw--1.0--1.1.sql',
+  postgres_fdw_path / 'postgres_fdw--1.1--1.2.sql',
+  install_dir: pg_ext.get_variable(pkgconfig: 'dir_data'),
+)
diff --git a/contrib/postgres_fdw/meson.build b/contrib/postgres_fdw/meson.build
index 3e2ed06b766..9a50aaba07a 100644
--- a/contrib/postgres_fdw/meson.build
+++ b/contrib/postgres_fdw/meson.build
@@ -55,3 +55,10 @@ tests += {
     ],
   },
 }
+
+meson_extension_tests += {
+  'name': 'postgres_fdw',
+  'kind': 'pkg_config',
+  'sd': meson.current_source_dir(),
+  'input': files('meson-test.build'),
+}
diff --git a/meson.build b/meson.build
index 7a140a394c8..f7b59eda65c 100644
--- a/meson.build
+++ b/meson.build
@@ -3273,6 +3273,7 @@ update_unicode_targets = []
 # Define the tests to distribute them to the correct test styles later
 test_deps = []
 tests = []
+meson_extension_tests = []
 
 
 # Default options for targets
@@ -3829,6 +3830,41 @@ sys.exit(sp.returncode)
      suite: ['setup'])
 
 
+# pkgconfig is not available on Windows, so skip it.
+if host_machine.system() != 'windows'
+  # it seems freebsd doesn't use libdir for pkgconfig path
+  if host_system == 'freebsd'
+    pkgconf_installdir =  dir_prefix / 'libdata' / 'pkgconfig'
+  else
+    pkgconf_installdir = dir_prefix / dir_lib / 'pkgconfig'
+  endif
+  test_pkg_conf_file = files('src/tools/test_meson_extensions')
+
+  foreach test : meson_extension_tests
+    if test['kind'] not in ['pkg_config']
+      error('unknown kind @0@ of test in @1@'.format(test['kind'], test['sd']))
+    endif
+
+    test_group = 'meson_@0@_extensions'.format(test['kind'])
+
+    test(test_group / test['name'],
+        test_pkg_conf_file,
+        args: [
+          '--meson', meson_bin.full_path(),
+          '--meson_args', meson_args,
+          '--input', test['input'],
+          '--test_source_dir', test['sd'],
+          '--test_dir', test_result_dir / 'meson_extensions' / test['name'],
+          '--builddir', meson.project_build_root(),
+          '--pkg_conf_path', get_option('pkg_config_path'),
+          '--',
+            cc.cmd_array(),
+          ],
+          suite: test_group,
+        )
+
+  endforeach
+endif
 
 ###############################################################
 # Test Generation
diff --git a/src/tools/test_meson_extensions b/src/tools/test_meson_extensions
new file mode 100644
index 00000000000..d0306b04b5a
--- /dev/null
+++ b/src/tools/test_meson_extensions
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import shutil
+import subprocess
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('--meson', help='path to meson binary',
+                    type=str, required=True)
+parser.add_argument('--meson_args', help='args of meson binary',
+                    type=str, nargs='*', required=False)
+parser.add_argument('--input', help='input meson-test.build file',
+                    type=str, required=True)
+parser.add_argument('--test_dir', help='test directory',
+                    type=str, required=True)
+parser.add_argument('--test_source_dir', help='test source directory',
+                    type=str, required=True)
+parser.add_argument('--builddir', help='meson build directory',
+                    type=str, required=True)
+parser.add_argument('--pkg_conf_path',
+                    help='PKG_CONF_PATH from surrounding meson build',
+                    type=str, nargs='?', const='', required=False)
+parser.add_argument('c_args', help='c_args from surrounding meson build',
+                     nargs='*')
+
+args = parser.parse_args()
+
+meson_bin = args.meson
+meson_args = args.meson_args or []
+input = args.input
+test_dir = args.test_dir
+test_source_dir = args.test_source_dir
+build_dir = args.builddir
+pkg_conf_path = args.pkg_conf_path
+c_args = ' '.join(args.c_args)
+
+def remove_duplicates(duplicate_str):
+    # Remove duplicates based on basename as there could be a mix of both full
+    # paths and bare binary names.
+    words = [os.path.basename(word) for word in duplicate_str.split()]
+    return ' '.join(sorted(set(words), key=words.index))
+
+
+def run_tests(pkg_conf_path_local, message):
+    print('\n{}\n{}\n'.format('#' * 60, message), flush=True)
+
+    test_out_dir = 'build'
+    test_args = f'-Dmeson_source_dir={test_source_dir}'
+
+    env = {**os.environ, }
+    env['PKG_CONFIG_PATH'] = '{}:{}:{}'.format(
+      pkg_conf_path_local, pkg_conf_path, env.get('PKG_CONFIG_PATH', ''),
+    ).strip(': ')
+    env['CC'] = '{} {}'.format(
+      c_args, env.get('CC', ''),
+    )
+    env['CC'] = remove_duplicates(env['CC'])
+
+    # Copy input file to test directory and rename it as a meson.build
+    if os.path.exists(test_dir):
+        shutil.rmtree(test_dir)
+    os.makedirs(test_dir)
+    shutil.copyfile(input, os.path.join(test_dir, 'meson.build'))
+
+    # Put meson_options.txt to the test_dir so that we can pass
+    # meson_source_dir argument
+    with open(os.path.join(test_dir, 'meson_options.txt'), 'w') as f:
+        f.write("option('meson_source_dir', type: 'string', value: '',\n"
+                "  description: 'Actual source directory of the meson-test.build file')\n")
+
+    meson_setup_command = [meson_bin, *meson_args, 'setup', test_args, test_out_dir]
+    meson_compile_command = [meson_bin, 'compile', '-C', test_out_dir, '-v']
+
+    subprocess.run(meson_setup_command, env=env, cwd=test_dir, check=True)
+    subprocess.run(meson_compile_command, cwd=test_dir, check=True)
+
+run_tests(os.path.join(build_dir, 'meson-uninstalled'),
+          message='Testing postgresql-extension-warnings-uninstalled')
-- 
2.47.3

