Skip to content

Cleanup pkg-config #1326

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ list (APPEND CMAKE_MODULE_PATH
)

include (MongoSettings)
include (GeneratePkgConfig)

# Subcomponents:
mongo_bool_setting(ENABLE_MONGOC "Enable the build of libmongoc libraries (The MongoDB C database driver)")
Expand Down
1 change: 1 addition & 0 deletions build/cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ add_subdirectory (make_dist)

set (build_cmake_MODULES
CheckSchedGetCPU.cmake
GeneratePkgConfig.cmake
ResSearch.cmake
FindSASL2.cmake
FindSnappy.cmake
Expand Down
298 changes: 298 additions & 0 deletions build/cmake/GeneratePkgConfig.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
include_guard(GLOBAL)

include(GNUInstallDirs)

define_property(
TARGET PROPERTY pkg_config_REQUIRES INHERITED
BRIEF_DOCS "pkg-config 'Requires:' items"
FULL_DOCS "Specify 'Requires:' items for the targets' pkg-config file"
)
define_property(
TARGET PROPERTY pkg_config_NAME INHERITED
BRIEF_DOCS "The 'Name' for pkg-config"
FULL_DOCS "The 'Name' of the pkg-config target"
)
define_property(
TARGET PROPERTY pkg_config_DESCRIPTION INHERITED
BRIEF_DOCS "The 'Description' pkg-config property"
FULL_DOCS "The 'Description' property to add to a target's pkg-config file"
)
define_property(
TARGET PROPERTY pkg_config_VERSION INHERITED
BRIEF_DOCS "The 'Version' pkg-config property"
FULL_DOCS "The 'Version' property to add to a target's pkg-config file"
)
define_property(
TARGET PROPERTY pkg_config_CFLAGS INHERITED
BRIEF_DOCS "The 'Cflags' pkg-config property"
FULL_DOCS "Set a list of options to add to a target's pkg-config file 'Cflags' field"
)
define_property(
TARGET PROPERTY pkg_config_INCLUDE_DIRECTORIES INHERITED
BRIEF_DOCS "Add -I options to the 'Cflags' pkg-config property"
FULL_DOCS "Set a list of directories that will be added using -I for the 'Cflags' pkg-config field"
)
define_property(
TARGET PROPERTY pkg_config_LIBS INHERITED
BRIEF_DOCS "Add linker options to the 'Libs' pkg-config field"
FULL_DOCS "Set a list of linker options that will joined in a string for the 'Libs' pkg-config field"
)

# Given a string, escape any generator-expression-special characters
function(_genex_escape out in)
# Escape '>'
string(REPLACE ">" "$<ANGLE-R>" str "${in}")
# Escape "$"
string(REPLACE "$" "$<1:$>" str "${str}")
# Undo the escaping of the dollar for $<ANGLE-R>
string(REPLACE "$<1:$><ANGLE-R>" "$<ANGLE-R>" str "${str}")
# Escape ","
string(REPLACE "," "$<COMMA>" str "${str}")
# Escape ";"
string(REPLACE ";" "$<SEMICOLON>" str "${str}")
set("${out}" "${str}" PARENT_SCOPE)
endfunction()

# Create a generator expression that ensures the given input generator expression
# is evaluated within the context of the named target.
function(_bind_genex_to_target out target genex)
_genex_escape(escaped "${genex}")
set("${out}" $<TARGET_GENEX_EVAL:${target},${escaped}> PARENT_SCOPE)
endfunction()

#[==[
Generate a pkg-config .pc file for the Given CMake target, and optionally a
rule to install it::

generate_pkg_config(
<target>
[FILENAME <filename>]
[LIBDIR <libdir>]
[INSTALL [DESTINATION <dir>] [RENAME <filename>]]
[CONDITION <cond>]
)

The `<target>` must name an existing target. The following options are accepted:

FILENAME <filename>
- The generated .pc file will have the given `<filename>`. This name *must*
be only the filename, and not a qualified path. If omitted, the default
filename is generated based on the target name. If using a multi-config
generator, the default filename will include the name of the configuration
for which it was generated.

LIBDIR <libdir>
- Specify the subdirectory of the install prefix in which the target binary
will live. If unspecified, uses `CMAKE_INSTALL_LIBDIR`, which comes from
the GNUInstallDirs module, which has a default of `lib`.

INSTALL [DESTINATION <dir>] [RENAME <filename>]
- Generate a rule to install the generated pkg-config file. This is better
than using a `file(INSTALL)` on the generated file directly, since it
ensures that the installed .pc file will have the correct install prefix
value. The following additional arguments are also accepted:

DESTINATION <dir>
- If provided, specify the *directory* (relative to the install-prefix)
in which the generated file will be installed. If unspecified, the
default destination is `<libdir>/pkgconfig`

RENAME <filename>
- If provided, set the filename of the installed pkg-config file. If
unspecified, the top-level `<filename>` will be used. (Note that the
default top-level `<filename>` will include the configuration type
when built/installed using a multi-config generator!)

CONDITION <cond>
- The file will only be generated/installed if the condition `<cond>`
results in the string "1" after evaluating generator expressions.

All named parameters accept generator expressions.

]==]
function(mongo_generate_pkg_config target)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It baffles me that CMake doesn't provide any utilities to support smart target-based pkg-config file generation. 😞

# Collect some target properties:
# The name:
_genex_escape(proj_name "${PROJECT_NAME}")
_genex_escape(proj_desc "${PROJECT_DESCRIPTION}")
set(tgt_name $<TARGET_PROPERTY:pkg_config_NAME>)
set(tgt_version $<TARGET_PROPERTY:pkg_config_VERSION>)
set(tgt_desc $<TARGET_PROPERTY:pkg_config_DESCRIPTION>)
string(CONCAT gx_name
$<IF:$<STREQUAL:,${tgt_name}>,
${proj_name},
${tgt_name}>)
# Version:
string(CONCAT gx_version
$<IF:$<STREQUAL:,${tgt_version}>,
${PROJECT_VERSION},
${tgt_version}>)
# Description:
string(CONCAT gx_desc
$<IF:$<STREQUAL:,${tgt_desc}>,
${proj_desc},
${tgt_desc}>)

# Parse and validate arguments:
cmake_parse_arguments(PARSE_ARGV 1 ARG "" "FILENAME;LIBDIR;CONDITION" "INSTALL")

# Compute the default FILENAME
if(NOT DEFINED ARG_FILENAME)
# No filename given. Pick a default:
if(DEFINED CMAKE_CONFIGURATION_TYPES)
# Multi-conf: We may want to generate more than one, so qualify the
# filename with the configuration type:
set(ARG_FILENAME "$<TARGET_FILE_BASE_NAME:${target}>-$<LOWER_CASE:$<CONFIG>>.pc")
else()
# Just generate a file based on the basename of the target:
set(ARG_FILENAME "$<TARGET_FILE_BASE_NAME:${target}>.pc")
endif()
endif()

# The defalut CONDITION is just "1" (true)
if(NOT DEFINED ARG_CONDITION)
set(ARG_CONDITION 1)
endif()
_bind_genex_to_target(gx_cond ${target} "${ARG_CONDITION}")

# The default LIBDIR comes from GNUInstallDirs.cmake
if(NOT ARG_LIBDIR)
set(ARG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
endif()
_bind_genex_to_target(gx_libdir ${target} "${ARG_LIBDIR}")

# Evaluate the filename genex in the context of the target:
_bind_genex_to_target(gx_filename ${target} "${ARG_FILENAME}")
if(IS_ABSOLUTE "${ARG_FILENAME}")
set(gx_output "${gx_filename}")
else()
get_filename_component(gx_output "${CMAKE_CURRENT_BINARY_DIR}/${gx_filename}" ABSOLUTE)
endif()

# Generate the content of the file:
_generate_pkg_config_content(content
NAME "${gx_name}"
VERSION "${gx_version}"
DESCRIPTION "${gx_desc}"
PREFIX "%INSTALL_PLACEHOLDER%"
LIBDIR "${gx_libdir}"
GENEX_TARGET "${target}"
)
_bind_genex_to_target(gx_content ${target} "${content}")
string(REPLACE "%INSTALL_PLACEHOLDER%" "${CMAKE_INSTALL_PREFIX}" gx_with_prefix "${gx_content}")
# Now, generate the file:
file(GENERATE
OUTPUT "${gx_output}"
CONTENT "${gx_with_prefix}"
CONDITION "${gx_cond}")
if(NOT "INSTALL" IN_LIST ARGN)
# Nothing more to do here.
return()
endif()

# Installation handling:
# Use file(GENERATE) to generate a temporary file to be picked up at install-time.
# (For some reason, injecting the content directly into install(CODE) fails in corner cases)
set(gx_tmpfile "${CMAKE_CURRENT_BINARY_DIR}/_pkgconfig/${target}-$<LOWER_CASE:$<CONFIG>>-for-install.txt")
file(GENERATE OUTPUT "${gx_tmpfile}"
CONTENT "${gx_content}"
CONDITION "${gx_cond}")
# Parse the install args that we will inspect:
cmake_parse_arguments(inst "" "DESTINATION;RENAME" "" ${ARG_INSTALL})
if(NOT DEFINED inst_DESTINATION)
# Install based on the libdir:
set(inst_DESTINATION "${gx_libdir}/pkgconfig")
endif()
if(NOT DEFINED inst_RENAME)
set(inst_RENAME "${ARG_FILENAME}")
endif()
# install(CODE) will write a simple temporary file:
set(inst_tmp "${CMAKE_CURRENT_BINARY_DIR}/${target}-pkg-config-tmp.txt")
_genex_escape(esc_cond "${ARG_CONDITION}")
string(CONFIGURE [==[
$<@gx_cond@:
# Installation of pkg-config for target @target@
message(STATUS "Generating pkg-config file: @inst_RENAME@")
file(READ [[@gx_tmpfile@]] content)
# Insert the install prefix:
string(REPLACE "%INSTALL_PLACEHOLDER%" "${CMAKE_INSTALL_PREFIX}" content "${content}")
# Write it before installing again:
file(WRITE [[@inst_tmp@]] "${content}")
>
$<$<NOT:@gx_cond@>:
# Installation was disabled for this generation.
message(STATUS "Skipping install of file [@inst_RENAME@]: Disabled by CONDITION “@esc_cond@”")
>
]==] code @ONLY)
install(CODE "${code}")
_bind_genex_to_target(gx_dest ${target} "${inst_DESTINATION}")
_bind_genex_to_target(gx_rename ${target} "${inst_RENAME}")
# Wrap the filename to install with the same condition used to generate it. If the condition
# is not met, then the FILES list will be empty, and nothing will be installed.
install(FILES "$<${gx_cond}:${inst_tmp}>"
DESTINATION ${gx_dest}
RENAME ${gx_rename}
${inst_UNPARSED_ARGUMENTS})
endfunction()

# Generates the actual content of a .pc file.
function(_generate_pkg_config_content out)
cmake_parse_arguments(PARSE_ARGV 1 ARG "" "PREFIX;NAME;VERSION;DESCRIPTION;GENEX_TARGET;LIBDIR" "")
if(ARG_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "Unknown arguments: ${ARG_UNPARSED_ARGUMENTS}")
endif()
set(content)
string(APPEND content
"# pkg-config .pc file generated by CMake ${CMAKE_VERSION} for ${ARG_NAME}-${ARG_VERSION}. DO NOT EDIT!\n"
"prefix=${ARG_PREFIX}\n"
"exec_prefix=\${prefix}\n"
"libdir=\${exec_prefix}/${gx_libdir}\n"
"\n"
"Name: ${ARG_NAME}\n"
"Description: ${ARG_DESCRIPTION}\n"
"Version: ${ARG_VERSION}"
)
# Add Requires:
set(requires_joiner "\nRequires: ")
set(gx_requires $<GENEX_EVAL:$<TARGET_PROPERTY:pkg_config_REQUIRES>>)
set(has_requires $<NOT:$<STREQUAL:,${gx_requires}>>)
string(APPEND content "$<${has_requires}:${requires_joiner}$<JOIN:${gx_requires},${requires_joiner}>>\n")
string(APPEND content "\n")
# Add "Libs:"
set(libs)
# Link options:
set(gx_libs
"-L\${libdir}"
"-l$<TARGET_PROPERTY:OUTPUT_NAME>"
$<GENEX_EVAL:$<TARGET_PROPERTY:pkg_config_LIBS>>
$<TARGET_PROPERTY:INTERFACE_LINK_OPTIONS>
)
string(APPEND libs "$<JOIN:${gx_libs};${gx_linkopts}, >")

# Cflags:
set(cflags)
set(gx_flags
$<REMOVE_DUPLICATES:$<GENEX_EVAL:$<TARGET_PROPERTY:pkg_config_CFLAGS>>>
$<REMOVE_DUPLICATES:$<TARGET_PROPERTY:INTERFACE_COMPILE_OPTIONS>>
)
string(APPEND cflags "$<JOIN:${gx_flags}, >")
# Definitions:
set(gx_defs $<REMOVE_DUPLICATES:$<TARGET_PROPERTY:INTERFACE_COMPILE_DEFINITIONS>>)
set(has_defs $<NOT:$<STREQUAL:,${gx_defs}>>)
set(def_joiner " -D")
string(APPEND cflags $<${has_defs}:${def_joiner}$<JOIN:${gx_defs},${def_joiner}>>)
# Includes:
set(gx_inc $<GENEX_EVAL:$<TARGET_PROPERTY:pkg_config_INCLUDE_DIRECTORIES>>)
set(gx_inc "$<REMOVE_DUPLICATES:${gx_inc}>")
set(gx_abs_inc "$<FILTER:${gx_inc},INCLUDE,^/>")
set(gx_rel_inc "$<FILTER:${gx_inc},EXCLUDE,^/>")
set(has_abs_inc $<NOT:$<STREQUAL:,${gx_abs_inc}>>)
set(has_rel_inc $<NOT:$<STREQUAL:,${gx_rel_inc}>>)
string(APPEND cflags $<${has_rel_inc}: " -I\${prefix}/"
$<JOIN:${gx_rel_inc}, " -I\${prefix}/" >>
$<${has_abs_inc}: " -I"
$<JOIN:${gx_abs_inc}, " -I" >>)
string(APPEND content "Libs: ${libs}\n")
string(APPEND content "Cflags: ${cflags}\n")
set("${out}" "${content}" PARENT_SCOPE)
endfunction()
2 changes: 1 addition & 1 deletion build/cmake/ResSearch.cmake
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
include(CheckSymbolExists)
include(CMakePushCheckState)

cmake_push_check_state(RESET)
cmake_push_check_state()

# The name of the library that performs name resolution, suitable for giving to the "-l" link flag
set(RESOLVE_LIB_NAME)
Expand Down
41 changes: 11 additions & 30 deletions src/libbson/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.1)
cmake_minimum_required (VERSION 3.15)
Copy link
Contributor

@eramongodb eramongodb Jun 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a significant change. I suggest at least creating a CXX C ticket to track/document this new requirement.

Side question: is there a compelling reason we should continue to support a per-library CMake Project configuration instead of using only the root CMake project for all targets defined in the CXX Driver C Driver? I don't think we intend to support add_subdirectory() of a CXX Driver C Driver library (i.e. bsoncxx libbson) without going through the root directory, do we?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're already requiring this at the top level, so this line could likely actually just be removed. It's actually probably bad that we had 3.1 there, since it would rewind a bunch of CMake policies and probably had some unintentional effects. (Such as the problem in ResSearch).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we intend to support add_subdirectory() of a CXX Driver library (i.e. bsoncxx) without going through the root directory, do we?

AFAIK there is no expectation to support add_subdirectory() without going through the root directory.

I expect C++ driver can be included with add_subdirectory of the root CMakeLists.txt, and CXX-2104 was filed to document.

This PR is for the C driver (not C++).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had my repos mixed up. Sorry for the confusion.


project (libbson C)

Expand Down Expand Up @@ -237,6 +237,7 @@ target_include_directories (
)
set_target_properties (bson_shared PROPERTIES VERSION 0.0.0 SOVERSION 0)
set_target_properties (bson_shared PROPERTIES OUTPUT_NAME "${BSON_OUTPUT_BASENAME}-${BSON_API_VERSION}")
mongo_generate_pkg_config (bson_shared FILENAME libbson-1.0.pc INSTALL)

if (ENABLE_APPLE_FRAMEWORK)
set_target_properties(bson_shared PROPERTIES
Expand Down Expand Up @@ -334,6 +335,9 @@ if (MONGOC_ENABLE_STATIC_BUILD)
# gethostbyname
target_link_libraries (bson_static ws2_32)
endif ()
if(MONGOC_ENABLE_STATIC_INSTALL)
mongo_generate_pkg_config(bson_static FILENAME libbson-static-1.0.pc INSTALL)
endif()
endif ()

function (add_example bin src)
Expand Down Expand Up @@ -378,6 +382,12 @@ install (
INCLUDES DESTINATION ${BSON_HEADER_INSTALL_DIR}
FRAMEWORK DESTINATION ${CMAKE_INSTALL_BINDIR}
)
set_target_properties(${TARGETS_TO_INSTALL} PROPERTIES
pkg_config_NAME libbson
pkg_config_DESCRIPTION "The libbson BSON serialization library."
pkg_config_VERSION "${BSON_VERSION}"
)
set_property(TARGET ${TARGETS_TO_INSTALL} APPEND PROPERTY pkg_config_INCLUDE_DIRECTORIES "${BSON_HEADER_INSTALL_DIR}")

install (
FILES ${HEADERS}
Expand Down Expand Up @@ -408,35 +418,6 @@ foreach (_lib ${BSON_SYSTEM_LIBRARIES})
set (LIBBSON_LIBRARIES "${LIBBSON_LIBRARIES} ${_lib}")
endforeach ()

set (VERSION "${BSON_VERSION}")
set (prefix "${CMAKE_INSTALL_PREFIX}")
set (libdir "\${prefix}/${CMAKE_INSTALL_LIBDIR}")
configure_file (
${CMAKE_CURRENT_SOURCE_DIR}/src/libbson-1.0.pc.in
${CMAKE_CURRENT_BINARY_DIR}/src/libbson-1.0.pc
@ONLY)

install (
FILES
${CMAKE_CURRENT_BINARY_DIR}/src/libbson-1.0.pc
DESTINATION
${CMAKE_INSTALL_LIBDIR}/pkgconfig
)

if (MONGOC_ENABLE_STATIC_INSTALL)
configure_file (
${CMAKE_CURRENT_SOURCE_DIR}/src/libbson-static-1.0.pc.in
${CMAKE_CURRENT_BINARY_DIR}/src/libbson-static-1.0.pc
@ONLY)

install (
FILES
${CMAKE_CURRENT_BINARY_DIR}/src/libbson-static-1.0.pc
DESTINATION
${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
endif ()

include (CMakePackageConfigHelpers)
set (INCLUDE_INSTALL_DIRS "${BSON_HEADER_INSTALL_DIR}")
set (LIBRARY_INSTALL_DIRS ${CMAKE_INSTALL_LIBDIR})
Expand Down
Loading