# Creating a relocatable `mariadb-plugin` CMake package

This guide describes a small CMake package installed by MariaDB's
`Development` component. Its purpose is to let third-party developers build
MariaDB plugins outside the MariaDB source tree.

The package itself can be created with MariaDB's existing CMake 3.12 minimum.
External pure and client plugins can also use CMake 3.12. A dirty plugin built
with MSVC should use CMake 3.15 or newer so that its runtime library can be
selected reliably with CMake's `MSVC_RUNTIME_LIBRARY` target property.

After MariaDB is installed, a plugin project should be able to write:

```cmake
find_package(mariadb-plugin CONFIG REQUIRED)
```

and use these targets:

| Target | What it provides | Binary library added to the link |
|---|---|---|
| `MariaDB::mysqlservices` | Installed plugin API headers and `mysqlservices` | `mysqlservices` on every platform |
| `MariaDB::server` | Plugin API plus internal server headers and the server build's ABI settings | `server.lib` on Windows; nothing on Linux |
| `MariaDB::client_plugin` | MariaDB Connector/C headers needed by a client plugin | Nothing |

## Terminology used in this guide

“Pure” and “dirty” are not established MariaDB or CMake terminology. This
guide uses them as short names for an important dependency boundary. “Dirty”
is not a criticism of the plugin; it only means that the plugin deliberately
depends on server internals.

| Term | Definition | CMake dependency |
|---|---|---|
| **Pure server plugin** | A module loaded by `mariadbd` that includes only the public server-plugin and service headers. It calls server functionality only through `mysqlservices` and has no unresolved server symbols. | `MariaDB::mysqlservices` |
| **Dirty server plugin** | A module loaded by `mariadbd` that includes internal server headers or directly references a server symbol not provided through `mysqlservices`. It must match the installed server's private ABI. | `MariaDB::server` |
| **Storage engine** | A server plugin that uses internal types such as `handler` and `THD`. For this package it is a specialized dirty server plugin, with the conventional `ha_` output name. | `MariaDB::server` |
| **Client plugin** | A module loaded by MariaDB Connector/C, not by `mariadbd`. The example here is a client authentication plugin. It needs Connector/C headers but no MariaDB link library. | `MariaDB::client_plugin` |

On Windows a dirty plugin links `server.lib`. On Linux it does not link a
server library and intentionally leaves direct server references unresolved;
`mariadbd` resolves them when loading the module. A pure plugin must instead
resolve all its symbols at link time on every platform.

The existing in-tree `RECOMPILE_FOR_EMBEDDED` option is not a good public name
for this distinction. It describes a historical build mechanism, while an
external plugin needs to state which API boundary it uses. In particular, a
normal dynamically loaded storage engine may require server-private headers
without being “recompiled for embedded.” The external convenience function
therefore uses `DIRTY` and `STORAGE_ENGINE`; it should not expose
`RECOMPILE_FOR_EMBEDDED` as the way to request `MariaDB::server`.

The important idea is that a CMake target describes both a library and the
requirements for using it. A developer who links to `MariaDB::mysqlservices`
must automatically get the correct include directory and compile definition.
They should not have to find MariaDB headers separately.

`MariaDB::server` is deliberately platform-dependent. On Windows it is an
imported library backed only by `server.lib`. It does not refer to or require
`server.dll`. On Linux it is a header-only CMake target. A Linux server plugin
is allowed to keep references to server symbols unresolved; `mariadbd` supplies
those symbols when it loads the plugin.

## 1. Account for the current header layout

MariaDB already installs the relevant headers in the `Development` component.
Keep doing that. In a normal installation the important directories are
conceptually:

```text
<prefix>/<INSTALL_INCLUDEDIR>/                       Connector/C headers
<prefix>/<INSTALL_INCLUDEDIR>/server/mysql/          plugin and service headers
<prefix>/<INSTALL_INCLUDEDIR>/server/private/        internal server headers
```

The names are misleading. A well-behaved server plugin does not need MariaDB
server internals, but the currently installed public plugin API is nevertheless
located below a directory named `server`. In particular,
`mysql/plugin.h` includes `mysql/services.h`, and `mysql/services.h` includes
the individual `mysql/service_*.h` files. The compiler include root needed for
these headers is therefore:

```text
<prefix>/<INSTALL_INCLUDEDIR>/server
```

For example, Debian installs `mysql/services.h` as
`/usr/include/mariadb/server/mysql/services.h`, while the RPM layout uses
`/usr/include/mysql/server/mysql/services.h`. This public plugin API directory
must not be confused with `server/private`, which contains server guts used by
storage engines and other dirty plugins.

This should not be treated as a permanent public path. Sergei Golubchik
[clarified in the MDEV-40608 review](https://github.com/MariaDB/server/pull/5486#issuecomment-5508008254)
that the additional nesting was unintended: the expected RPM path was
`/usr/include/mysql/plugin.h`, not
`/usr/include/mysql/server/mysql/plugin.h`, and the installation should
eventually be fixed.

The examples in this guide use the layout that MariaDB 11.8 packages actually
contain today. When the installation rule is corrected, update
`MariaDB::plugin_api` to publish the new include root. Do not make third-party
projects test layouts or spell either physical path themselves. Their source
continues to use `#include <mysql/plugin.h>` and their CMake code continues to
link `MariaDB::mysqlservices`; only the exported target changes. This is one of
the main benefits of expressing the header dependency through a CMake target.

If the corrected destination differs between installation layouts, introduce
a layout-specific variable for the plugin API include root. Do not compute it
in the installed config with `..`, and do not make the config guess whether a
particular header layout is present.

The CMake targets below point to those installed directories. CMake does not
install headers merely because an include directory is mentioned on a target;
the existing `install(FILES ...)` and `install(DIRECTORY ...)` rules remain
necessary.

## 2. Create targets for the three sets of headers

Put this in an appropriate MariaDB CMake file after the installation-directory
variables have been defined. The names before `EXPORT_NAME` are private names
used inside the MariaDB build. The installed names are shown in the comments.

```cmake
# Public server-plugin API: MariaDB::plugin_api
add_library(mariadb_plugin_api INTERFACE)
set_target_properties(mariadb_plugin_api PROPERTIES
  EXPORT_NAME plugin_api
)
target_compile_definitions(mariadb_plugin_api INTERFACE
  MYSQL_DYNAMIC_PLUGIN
)
target_include_directories(mariadb_plugin_api INTERFACE
  $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
  $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
  $<INSTALL_INTERFACE:${INSTALL_INCLUDEDIR}/server>
)

# Internal server API: MariaDB::server_headers
add_library(mariadb_server_headers INTERFACE)
set_target_properties(mariadb_server_headers PROPERTIES
  EXPORT_NAME server_headers
)
target_link_libraries(mariadb_server_headers INTERFACE
  mariadb_plugin_api
)
target_include_directories(mariadb_server_headers INTERFACE
  $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/sql>
  $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/sql>
  $<INSTALL_INTERFACE:${INSTALL_INCLUDEDIR}/server/private>
)

# The installed package config adds ABI definitions such as DBUG_OFF,
# WITH_WSREP, and _FILE_OFFSET_BITS from the server build profile described
# in Chapter 7. Do not hard-code a Release profile here: Debug builders differ.

if(WIN32)
  target_compile_definitions(mariadb_server_headers INTERFACE
    _WIN32_WINNT=0x0A00
    NOMINMAX
    NOSERVICE
    WIN32_LEAN_AND_MEAN
  )
endif()

# Connector/C client-plugin API: MariaDB::client_plugin
add_library(mariadb_client_plugin INTERFACE)
set_target_properties(mariadb_client_plugin PROPERTIES
  EXPORT_NAME client_plugin
)
target_include_directories(mariadb_client_plugin INTERFACE
  $<INSTALL_INTERFACE:${INSTALL_INCLUDEDIR}>
)
```

If MariaDB also uses `mariadb_client_plugin` from its build tree, add the
Connector/C source and binary include directories as `BUILD_INTERFACE`
entries. They are not needed merely to create the installed package.

Why use `$<BUILD_INTERFACE:...>` and `$<INSTALL_INTERFACE:...>`? The source-tree
paths are valid while MariaDB itself is being built. They must not appear in an
installed package. The install entries are relative to the installation
prefix, so the complete installation can later be moved.

Do not write this:

```cmake
# Wrong: this stores the installation machine's absolute path in the package.
target_include_directories(mariadb_plugin_api INTERFACE
  ${CMAKE_INSTALL_PREFIX}/${INSTALL_INCLUDEDIR}/server
)
```

## 3. Export `mysqlservices`

Add the public plugin API as a usage requirement of the existing
`mysqlservices` static library:

```cmake
set_target_properties(mysqlservices PROPERTIES
  EXPORT_NAME mysqlservices
)

target_link_libraries(mysqlservices PUBLIC
  mariadb_plugin_api
)

# mysqlservices only defines service-pointer variables and uses no CRT code.
# Do not let its object files select a CRT for the consuming plugin DLL.
if(MSVC)
  target_compile_options(mysqlservices PRIVATE /Zl)
endif()

# A "pure" server plugin must resolve every symbol at link time.
# MSVC already enforces this. GNU/Linux needs --no-undefined.
# Use target_link_libraries rather than target_link_options because MariaDB
# still supports CMake 3.12; target_link_options was added in CMake 3.13.
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
  target_link_libraries(mysqlservices INTERFACE
    "${LINK_FLAG_NO_UNDEFINED}"
  )
endif()
```

`PUBLIC` is intentional. It means that a target using `mysqlservices` also
inherits the plugin API include root and `MYSQL_DYNAMIC_PLUGIN`. It does not
inherit `server/private`, `DBUG_OFF`, or other server-internal settings.

On MSVC, `/Zl` omits the default CRT library name from the object files in
`mysqlservices.lib`. This is appropriate because those objects contain only
service-pointer definitions. It prevents a pure plugin's CRT choice from being
dictated by the static library. Check the result in Windows CI with:

```powershell
dumpbin /directives mysqlservices.lib
```

The output should contain no `/DEFAULTLIB` reference to `MSVCRT`, `MSVCRTD`,
`LIBCMT`, or `LIBCMTD`, and no conflicting `RuntimeLibrary` directive.

Making `--no-undefined` an interface option also makes the rule automatic: a
pure plugin that uses `MariaDB::mysqlservices` cannot accidentally acquire a
dependency on an unexported server symbol.

## 4. Split `server.lib` from `server.dll` on Windows

Do not put the real `server` shared-library target into the export set. An
export of that target describes both `server.lib` and `server.dll`, which would
make the development package require the server runtime package.

Instead, split the two artifacts between the existing components:

```cmake
if(WIN32 AND TARGET server)
  install(TARGETS server
    # The DLL is needed only to run the server.
    RUNTIME DESTINATION ${INSTALL_BINDIR}
            COMPONENT Server

    # The import library is enough to link an external plugin.
    ARCHIVE DESTINATION ${INSTALL_LIBDIR}
            COMPONENT Development
  )
endif()
```

On Windows, CMake classifies a DLL as `RUNTIME` and its import library as
`ARCHIVE`. Because this `install(TARGETS)` call has no `EXPORT` option, it
creates no dependency from `Development` to `Server`.

Replace the existing installation of the `server` target with the split rule;
do not add a second rule that installs the same DLL or import library again.

## 5. Install one export set

Use one export set for `mysqlservices` and its header-only dependencies. The
real Windows `server` target is intentionally absent.

```cmake
set(MARIADB_PLUGIN_CMAKEDIR
  "${INSTALL_LIBDIR}/cmake/mariadb-plugin"
)

install(TARGETS
    mysqlservices
    mariadb_plugin_api
    mariadb_server_headers
    mariadb_client_plugin
  EXPORT mariadb-plugin-targets

  # mysqlservices is a development file.
  ARCHIVE DESTINATION ${INSTALL_LIBDIR}
          COMPONENT Development
)

install(EXPORT mariadb-plugin-targets
  FILE mariadb-plugin-targets.cmake
  NAMESPACE MariaDB::
  DESTINATION "${MARIADB_PLUGIN_CMAKEDIR}"
  COMPONENT Development
)
```

If MariaDB's existing helper around `install(TARGETS)` performs additional
packaging work, extend that helper to accept `EXPORT mariadb-plugin-targets`
instead of bypassing it. The essential result is that `mysqlservices` and the
three header targets above belong to the export set, while `server` does not.

## 6. Create the package config and version files

Create `cmake/mariadb-plugin-config.cmake.in`. The exported targets file creates
the ordinary targets. The small block below creates `MariaDB::server` by hand:

```cmake
@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/mariadb-plugin-targets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/mariadb-plugin-build-profile.cmake")

# Apply the fixed ABI profile of the installed server. These tests do not
# depend on the configuration chosen by the third-party project.
if(MARIADB_PLUGIN_SERVER_DBUG_OFF)
  set_property(TARGET MariaDB::server_headers APPEND PROPERTY
    INTERFACE_COMPILE_DEFINITIONS DBUG_OFF
  )
endif()

if(MARIADB_PLUGIN_SERVER_WITH_WSREP)
  set_property(TARGET MariaDB::server_headers APPEND PROPERTY
    INTERFACE_COMPILE_DEFINITIONS WITH_WSREP
  )
endif()

if(MARIADB_PLUGIN_SERVER_NDEBUG_WORKAROUND)
  set_property(TARGET MariaDB::server_headers APPEND PROPERTY
    INTERFACE_COMPILE_DEFINITIONS NDEBUG
  )
endif()

if(NOT "${MARIADB_PLUGIN_SERVER_FILE_OFFSET_BITS}" STREQUAL "")
  set_property(TARGET MariaDB::server_headers APPEND PROPERTY
    INTERFACE_COMPILE_DEFINITIONS
      "_FILE_OFFSET_BITS=${MARIADB_PLUGIN_SERVER_FILE_OFFSET_BITS}"
  )
endif()

if(WIN32)
  # This target represents the import library only. It deliberately has no
  # IMPORTED_LOCATION for server.dll and therefore does not require the DLL.
  set_and_check(_mariadb_server_import_library
    "@PACKAGE_INSTALL_LIBDIR@/server@CMAKE_IMPORT_LIBRARY_SUFFIX@"
  )
  add_library(MariaDB::server UNKNOWN IMPORTED)
  set_target_properties(MariaDB::server PROPERTIES
    IMPORTED_LOCATION "${_mariadb_server_import_library}"
    INTERFACE_LINK_LIBRARIES MariaDB::server_headers
  )
  unset(_mariadb_server_import_library)
else()
  # No linkable server library on Linux: expose only the internal headers.
  add_library(MariaDB::server INTERFACE IMPORTED)
  set_target_properties(MariaDB::server PROPERTIES
    INTERFACE_LINK_LIBRARIES MariaDB::server_headers
  )
endif()

include("${CMAKE_CURRENT_LIST_DIR}/mariadb-plugin-functions.cmake")
```

Create `cmake/mariadb-plugin-build-profile.cmake.in`. It is deliberately small
and contains only facts captured from the server configuration that produced
this Development package:

```cmake
set(MARIADB_PLUGIN_SERVER_DBUG_OFF
  @MARIADB_PLUGIN_SERVER_DBUG_OFF@)
set(MARIADB_PLUGIN_SERVER_WITH_WSREP
  @MARIADB_PLUGIN_SERVER_WITH_WSREP@)
set(MARIADB_PLUGIN_SERVER_NDEBUG_WORKAROUND
  @MARIADB_PLUGIN_SERVER_NDEBUG_WORKAROUND@)
set(MARIADB_PLUGIN_SERVER_FILE_OFFSET_BITS
  "@MARIADB_PLUGIN_SERVER_FILE_OFFSET_BITS@")

# Empty on non-MSVC platforms. Use CMake's property spelling, not /MD or /MT.
set(MARIADB_PLUGIN_SERVER_MSVC_RUNTIME_LIBRARY
  "@MARIADB_PLUGIN_SERVER_MSVC_RUNTIME_LIBRARY@")
```

These are ordinary package variables as well as inputs used to construct the
imported targets. They let a developer inspect the server profile, but normal
plugin code should consume `MariaDB::server` and `mariadb_add_plugin()` rather
than reimplementing the tests.

`UNKNOWN IMPORTED` is appropriate for `server.lib`: CMake passes that file to
the linker without pretending that it is the server DLL. `set_and_check()`
also gives a clear error if the `Development` component is incomplete and the
import library is missing.

Do not add `--no-undefined` to `MariaDB::server`. A dirty Linux plugin is
supposed to contain references that are resolved only when the server loads it.
The normal Linux linker behaviour for a `MODULE` library permits this.

Then generate and install it:

```cmake
include(CMakePackageConfigHelpers)

# MARIADB_PLUGIN_SERVER_DBUG_OFF must already have been recorded by the same
# configuration-specific logic that controls -DDBUG_OFF. Set the NDEBUG
# workaround according to whether the affected Json_writer header is shipped.
if(NOT DEFINED MARIADB_PLUGIN_SERVER_DBUG_OFF OR
   NOT DEFINED MARIADB_PLUGIN_SERVER_NDEBUG_WORKAROUND)
  message(FATAL_ERROR "MariaDB server ABI profile was not initialized")
endif()
set(MARIADB_PLUGIN_SERVER_WITH_WSREP "${WITH_WSREP}")
set(MARIADB_PLUGIN_SERVER_FILE_OFFSET_BITS "${_FILE_OFFSET_BITS}")
if(MSVC)
  set(MARIADB_PLUGIN_SERVER_MSVC_RUNTIME_LIBRARY
    "${CMAKE_MSVC_RUNTIME_LIBRARY}"
  )
else()
  set(MARIADB_PLUGIN_SERVER_MSVC_RUNTIME_LIBRARY "")
endif()

# This direct configure_file form is suitable for a single-config package.
# For a multi-config generator, generate one profile per configuration as
# explained below and install the selected profile under this filename.
configure_file(
  "${PROJECT_SOURCE_DIR}/cmake/mariadb-plugin-build-profile.cmake.in"
  "${PROJECT_BINARY_DIR}/mariadb-plugin-build-profile.cmake"
  @ONLY
)

configure_package_config_file(
  "${PROJECT_SOURCE_DIR}/cmake/mariadb-plugin-config.cmake.in"
  "${PROJECT_BINARY_DIR}/mariadb-plugin-config.cmake"
  INSTALL_DESTINATION "${MARIADB_PLUGIN_CMAKEDIR}"
  PATH_VARS INSTALL_LIBDIR
)

write_basic_package_version_file(
  "${PROJECT_BINARY_DIR}/mariadb-plugin-config-version.cmake"
  VERSION "${SERVER_VERSION}"
  COMPATIBILITY SameMinorVersion
)

install(FILES
    "${PROJECT_BINARY_DIR}/mariadb-plugin-config.cmake"
    "${PROJECT_BINARY_DIR}/mariadb-plugin-config-version.cmake"
    "${PROJECT_BINARY_DIR}/mariadb-plugin-build-profile.cmake"
    "${PROJECT_SOURCE_DIR}/cmake/mariadb-plugin-functions.cmake"
  DESTINATION "${MARIADB_PLUGIN_CMAKEDIR}"
  COMPONENT Development
)
```

The config file contains no absolute installation path. The generated targets
file computes its prefix from its own location, and
`@PACKAGE_INSTALL_LIBDIR@` is generated relative to the same prefix. This is
what makes both `mysqlservices` and `server.lib` relocatable.

Use a separate export filename such as `mariadb-plugin-targets.cmake`, rather
than naming the export file like the package config. CMake may generate
per-configuration companion files and loads them by filename pattern.

The build-profile file must describe the configuration that is actually
installed. For a normal MariaDB build the `DBUG_OFF` values are:

| Installed server configuration | `MARIADB_PLUGIN_SERVER_DBUG_OFF` |
|---|---|
| `Debug` | `FALSE` |
| `Release` | `TRUE` |
| `RelWithDebInfo` | `TRUE` |
| `MinSizeRel` | `TRUE` |

Record this value in the same MariaDB CMake logic that adds or omits
`-DDBUG_OFF`; do not rediscover it later from the consumer's build type. If a
custom builder forces `DBUG_ON`, the recorded value must be `FALSE` even when
the configuration is named `Release`.

With a single-config generator, configure the profile from
`CMAKE_BUILD_TYPE`. With Visual Studio or another multi-config generator,
generate one profile for each configuration and install the one selected by
`cmake --install --config <configuration>`. A package containing several
server configurations needs corresponding imported configurations and ABI
profiles; otherwise publish separate Development packages for Debug and
Release servers.

## 7. Preserve the server ABI for dirty plugins

A pure plugin sees only the plugin and service API. A dirty plugin includes
internal server headers and may exchange internal objects with `mariadbd`.
Consequently, a dirty plugin must be compiled for the ABI of the installed
server, not merely for the platform and MariaDB version.

The generated build profile must capture at least these settings:

- `DBUG_OFF`. Many internal declarations and inline implementations depend on
  it. MariaDB normally defines it for `Release`, `RelWithDebInfo`, and
  `MinSizeRel`, but not for `Debug`. A Debug builder must therefore export
  `MARIADB_PLUGIN_SERVER_DBUG_OFF=FALSE`.
- `WITH_WSREP`, when the installed server was built with WSREP. It adds members
  to internal structures including `system_variables` and `THD`; a mismatch
  changes member offsets. Do not define it as zero because the headers use
  `#ifdef WITH_WSREP`.
- `_FILE_OFFSET_BITS`, when MariaDB defines it. A mismatch can change `off_t`
  and any structure containing it.
- On Windows, the MSVC runtime and STL ABI. MariaDB currently defaults
  `CMAKE_MSVC_RUNTIME_LIBRARY` to `MultiThreadedDLL`, which means `/MD` even
  for a Debug server configuration unless the builder overrides it.

`NDEBUG` should normally remain the third-party project's choice. It controls
the standard C and C++ `assert()` facility; it should not be part of MariaDB's
public compile profile.

There is a likely bug in the current `sql/my_json_writer.h` and
`sql/my_json_writer.cc`: several conditionals use `NDEBUG` to add or remove
debug-only state, including STL members of `Json_writer`. MariaDB's own switch
for this purpose is `DBUG_OFF`, as the same header already demonstrates in
`Json_writer_nesting_guard`. These conditionals should use `DBUG_OFF` instead,
in both the header and implementation. Otherwise a release server and a dirty
plugin built without `NDEBUG` disagree about the layout of `Json_writer`.

Fixing the header is preferable to exporting `NDEBUG`. If a package is made
for an existing server version in which the header has not been fixed, adding
`NDEBUG` to that version's `MariaDB::server_headers` is a compatibility
workaround required to reproduce the release server's existing ABI. It should
be clearly marked as such, rather than presented as a general plugin API
requirement.

The settings belong to `MariaDB::server` through
`MariaDB::server_headers`. They must not be attached to
`MariaDB::mysqlservices`: a pure plugin does not use internal server classes,
and its private implementation need not inherit the server's CRT choice.

Do not derive these settings from the third-party project's configuration. In
particular, this is wrong for a release MariaDB installation:

```cmake
# Wrong: this describes the consumer configuration, not the installed server.
$<$<NOT:$<CONFIG:Debug>>:DBUG_OFF>
```

A developer may compile a dirty plugin with `/Od` and `/Zi`, but it must still
use the installed server's `DBUG_OFF`, CRT, and STL ABI. Conversely, a plugin
for a Debug server must not acquire `DBUG_OFF` merely because the third-party
project selected a non-Debug configuration. `NDEBUG` is needed only as the
compatibility workaround described above when using an affected installed
header.

Compile definitions are transitive CMake usage requirements, so the package
config can attach them to `MariaDB::server_headers`. The
`MSVC_RUNTIME_LIBRARY` target property is not transitive. The package therefore
exports its value as `MARIADB_PLUGIN_SERVER_MSVC_RUNTIME_LIBRARY`, and
`mariadb_add_plugin()` applies it directly to each dirty plugin target. A
developer who creates a dirty target manually must set the same property.

Existing MariaDB 11.8 builds record `WITH_WSREP` in generated `my_config.h`.
However, the current
[MDEV-40608 pull request](https://github.com/MariaDB/server/pull/5486)
removes it from `my_config.h` and instead adds `-DWITH_WSREP` only while the
server source tree is built. Its installed plugin config does not currently
reproduce that definition. That is safe for pure plugins, but not for dirty
plugins built against a WSREP-enabled server. The exported
`MariaDB::server_headers` target must therefore add `WITH_WSREP` when the
installed server used it, as shown earlier. It must not add the definition to
`MariaDB::mysqlservices`.

Most other feature and platform definitions do not need to be copied into the
CMake package individually. MariaDB's installed generated `my_config.h`
records storage-engine feature macros, `HAVE_*`, and type sizes, and
`my_global.h` includes it. The exported target must ensure that the matching
installed `my_config.h` is found.

Debug-server development packages are a separate problem. MariaDB Debug builds
can additionally use definitions such as `ENABLED_DEBUG_SYNC`,
`PROTECT_STATEMENT_MEMROOT`, `SAFE_MUTEX`, `_GLIBCXX_DEBUG`, and
`_GLIBCXX_ASSERTIONS`. Several of these change `THD`, lock structures, internal
classes, or the libstdc++ ABI. If MariaDB chooses to distribute development
files for a Debug or sanitizer build, generate a separate package containing
that build's exact compile profile. Do not represent it by merely removing
`DBUG_OFF` from the ordinary release package.

`MYSQL_SERVER` should not be added automatically. It exposes large additional
parts of internal headers, and in-tree storage engines do not uniformly define
it. A specialized plugin may request it explicitly, but `DIRTY` alone should
not imply it.

## 8. Third-party plugin example

A third-party project can build all three plugin kinds with one small
`CMakeLists.txt`:

```cmake
cmake_minimum_required(VERSION 3.15)
project(my_mariadb_plugins LANGUAGES C CXX)

find_package(mariadb-plugin CONFIG REQUIRED)

# A. Pure server plugin
# Uses only the public plugin API and mysqlservices.
# On Linux, --no-undefined is inherited from MariaDB::mysqlservices.
add_library(my_pure_plugin MODULE pure_plugin.c)
set_target_properties(my_pure_plugin PROPERTIES PREFIX "")
target_link_libraries(my_pure_plugin PRIVATE
  MariaDB::mysqlservices
)

# B. Dirty server plugin or storage engine
# May include internal headers and refer directly to server symbols.
# Windows links server.lib. Linux adds no library and permits unresolved
# server symbols.
add_library(my_storage_engine MODULE storage_engine.cc)
set_target_properties(my_storage_engine PROPERTIES PREFIX "")
target_link_libraries(my_storage_engine PRIVATE
  MariaDB::server
)
if(MSVC)
  # MSVC_RUNTIME_LIBRARY is not inherited through target_link_libraries().
  set_property(TARGET my_storage_engine PROPERTY
    MSVC_RUNTIME_LIBRARY
      "${MARIADB_PLUGIN_SERVER_MSVC_RUNTIME_LIBRARY}"
  )
endif()

# C. Client authentication plugin
# MariaDB::client_plugin is an INTERFACE target. It supplies Connector/C
# headers but adds no binary library to the linker command.
add_library(my_client_auth MODULE client_auth.c)
set_target_properties(my_client_auth PROPERTIES PREFIX "")
target_link_libraries(my_client_auth PRIVATE
  MariaDB::client_plugin
)
```

Although `target_link_libraries()` is used for the client plugin,
`MariaDB::client_plugin` is header-only. No MariaDB library appears on the
actual linker command line. In modern CMake, `target_link_libraries()` is also
the normal way to inherit usage requirements from an interface target.

Typical source-file includes are:

```c
/* pure_plugin.c: public server plugin API */
#include <mysql/plugin.h>
#include <mysql/service_my_snprintf.h>
```

```cpp
/* storage_engine.cc: internal server API */
#include <mysql/plugin.h>
#include <handler.h>
#include <sql_class.h>
```

```c
/* client_auth.c: Connector/C client plugin API */
#include <mysql.h>
#include <mysql/client_plugin.h>
```

The plugin source still needs the appropriate
`maria_declare_plugin(...)` or `mysql_declare_client_plugin(...)` declaration.
The package changes only how it is compiled and linked; it does not change the
plugin ABI.

Build the third-party project by pointing CMake at the MariaDB installation:

```sh
cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/mariadb
cmake --build build --config Release
```

Alternatively, point directly at the config directory:

```sh
cmake -S . -B build \
  -Dmariadb-plugin_DIR=/path/to/mariadb/lib/cmake/mariadb-plugin
```

`CMAKE_PREFIX_PATH` is usually preferable because it names the installation as
a whole and does not depend on whether a platform uses `lib` or `lib64`.

The example uses CMake 3.15 because it builds a dirty plugin with MSVC. A
project containing only pure or client plugins may retain
`cmake_minimum_required(VERSION 3.12)`.

## 9. Relocatability test

Test the installed package, not only the MariaDB build tree:

1. Install only the `Development` component into a temporary prefix.
2. Move or copy that complete prefix to a different absolute directory.
3. Configure the third-party example with only the new directory in
   `CMAKE_PREFIX_PATH`.
4. Build all three modules.
5. Inspect the link commands:
   - the pure plugin contains `mysqlservices` and rejects unresolved symbols;
   - the dirty plugin contains `server.lib` on Windows and no server library on
     Linux;
   - the client plugin contains no MariaDB library.
6. Search the installed `mariadb-plugin-*.cmake` files for the original source,
   build, and installation paths. None should occur.

This catches the common mistake of producing a config file that works on the
build machine only.

## Short checklist

- Present libraries to consumers through CMake targets, not path variables.
- Put header include directories and compile definitions on those targets.
- Use `BUILD_INTERFACE` for source/build-tree paths.
- Use relative `INSTALL_INTERFACE` paths for installed headers.
- Export every interface target referenced by an exported library.
- Install `server.lib` and `mysqlservices` in `Development`.
- Keep `server.dll` exclusively in `Server`; `Development` must not depend on
  it.
- Represent `server.lib` with a relocatable imported target, without exporting
  the real server DLL target.
- Make `MariaDB::server` interface-only on Linux.
- Keep `mysqlservices.lib` CRT-neutral on MSVC with `/Zl`.
- Generate an ABI profile for the server configuration that is actually
  installed; a Debug builder must record that `DBUG_OFF` is absent.
- Put server ABI definitions such as `DBUG_OFF`, `WITH_WSREP`, and
  `_FILE_OFFSET_BITS` on `MariaDB::server`, not on `MariaDB::mysqlservices`.
- Export `NDEBUG` only as a compatibility workaround for affected headers.
- Make dirty MSVC plugins use the exact exported
  `MSVC_RUNTIME_LIBRARY` value; do not assume it from the plugin's build type.
- Treat Debug and sanitizer server development packages as distinct ABI
  profiles.
- Do not apply `--no-undefined` to dirty server plugins.
- Test after moving the complete installation prefix.

## 10. Add the `mariadb_add_plugin()` convenience function

Put this function in `cmake/mariadb-plugin-functions.cmake`. It intentionally
resembles the existing in-tree `MYSQL_ADD_PLUGIN` call style: the plugin name
and sources are positional, while special behaviour is selected by keywords.
It only creates and configures the module target; installation and packaging
are deliberately outside its scope.

```cmake
function(mariadb_add_plugin plugin_name)
  cmake_parse_arguments(ARG
    "CLIENT;DIRTY;STORAGE_ENGINE"
    "MODULE_OUTPUT_NAME"
    "LINK_LIBRARIES"
    ${ARGN}
  )

  if(ARG_CLIENT AND (ARG_DIRTY OR ARG_STORAGE_ENGINE))
    message(FATAL_ERROR
      "mariadb_add_plugin(${plugin_name}): CLIENT cannot be DIRTY or a STORAGE_ENGINE"
    )
  endif()

  if(NOT ARG_UNPARSED_ARGUMENTS)
    message(FATAL_ERROR
      "mariadb_add_plugin(${plugin_name}): no source files were specified"
    )
  endif()

  string(TOLOWER "${plugin_name}" target)
  if(TARGET "${target}")
    message(FATAL_ERROR
      "mariadb_add_plugin(${plugin_name}): target ${target} already exists"
    )
  endif()

  add_library("${target}" MODULE ${ARG_UNPARSED_ARGUMENTS})

  if(ARG_MODULE_OUTPUT_NAME)
    set(output_name "${ARG_MODULE_OUTPUT_NAME}")
  elseif(ARG_STORAGE_ENGINE)
    set(output_name "ha_${target}")
  else()
    set(output_name "${target}")
  endif()

  set_target_properties("${target}" PROPERTIES
    PREFIX ""
    OUTPUT_NAME "${output_name}"
  )

  if(MSVC AND (ARG_DIRTY OR ARG_STORAGE_ENGINE))
    if(CMAKE_VERSION VERSION_LESS 3.15)
      message(FATAL_ERROR
        "Dirty MariaDB plugins built with MSVC require CMake 3.15 or newer"
      )
    endif()
    cmake_policy(GET CMP0091 cmp0091_state)
    if(NOT cmp0091_state STREQUAL "NEW")
      message(FATAL_ERROR
        "Set cmake_minimum_required(VERSION 3.15) before project() so CMP0091 is NEW"
      )
    endif()

    if("${MARIADB_PLUGIN_SERVER_MSVC_RUNTIME_LIBRARY}" STREQUAL "")
      message(FATAL_ERROR
        "The mariadb-plugin package does not describe the server's MSVC runtime"
      )
    endif()

    # MSVC_RUNTIME_LIBRARY is not a transitive usage requirement. Apply the
    # exact value captured from the packaged server build.
    set_property(TARGET "${target}" PROPERTY
      MSVC_RUNTIME_LIBRARY
        "${MARIADB_PLUGIN_SERVER_MSVC_RUNTIME_LIBRARY}"
    )
  endif()

  if(ARG_CLIENT)
    # Header-only target: no MariaDB binary library is linked.
    target_link_libraries("${target}" PRIVATE MariaDB::client_plugin)
  elseif(ARG_DIRTY OR ARG_STORAGE_ENGINE)
    # server.lib on Windows; headers only on Linux.
    target_link_libraries("${target}" PRIVATE MariaDB::server)
  else()
    # Pure server plugin; also enforces --no-undefined on Linux.
    target_link_libraries("${target}" PRIVATE MariaDB::mysqlservices)
  endif()

  if(ARG_LINK_LIBRARIES)
    target_link_libraries("${target}" PRIVATE ${ARG_LINK_LIBRARIES})
  endif()
endfunction()
```

Typical third-party calls are:

```cmake
find_package(mariadb-plugin CONFIG REQUIRED)

# Pure server plugin: links mysqlservices and forbids unresolved symbols.
mariadb_add_plugin(example example.c)

# Dirty server plugin: server.lib on Windows, unresolved server symbols on Linux.
mariadb_add_plugin(type_example type_example.cc DIRTY)

# Storage engines are dirty and use the conventional ha_ filename by default.
mariadb_add_plugin(example_engine ha_example.cc STORAGE_ENGINE)

# Client authentication plugin: Connector/C headers, no MariaDB library.
mariadb_add_plugin(example_auth example_auth.c CLIENT)

# Optional output-name override and non-MariaDB dependencies are also supported.
mariadb_add_plugin(foo foo.cc DIRTY
  MODULE_OUTPUT_NAME mariadb_foo
  LINK_LIBRARIES third_party_library
)
```
