# 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.

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` | Public server-plugin headers and `mysqlservices` | `mysqlservices` on every platform |
| `MariaDB::server` | Public and private server headers | `server.lib` on Windows; nothing on Linux |
| `MariaDB::client_plugin` | MariaDB Connector/C headers needed by a client plugin | Nothing |

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. Install the headers as before

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/          public server-plugin headers
<prefix>/<INSTALL_INCLUDEDIR>/server/private/  internal server headers
```

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>
)

# 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
)

# A "pure" server plugin must resolve every symbol at link time.
# MSVC already enforces this. GNU/Linux needs --no-undefined.
target_link_options(mysqlservices INTERFACE
  $<$<PLATFORM_ID:Linux>:LINKER:--no-undefined>
)
```

`PUBLIC` is intentional. It means that a target using `mysqlservices` also
inherits the server-plugin include directory and `MYSQL_DYNAMIC_PLUGIN`.

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")

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")
```

`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)

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_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.

## 7. 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
)

# 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`.

## 8. 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.
- Do not apply `--no-undefined` to dirty server plugins.
- Test after moving the complete installation prefix.

## 9. 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.

```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(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
)
```

The function stops at creating and configuring the `MODULE` target. It does
not install the plugin, choose a package component, or impose third-party
packaging policy.

For the underlying CMake mechanism, see CMake's official
[Importing and Exporting Guide](https://cmake.org/cmake/help/latest/guide/importing-exporting/index.html),
[`install(EXPORT)` documentation](https://cmake.org/cmake/help/latest/command/install.html#export),
and
[`CMakePackageConfigHelpers` documentation](https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html).
