Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Increment the:

## [Unreleased]

* [CMAKE] Add pkg-config install consumer tests
[#4503](https://github.com/open-telemetry/opentelemetry-cpp/issues/4503)

* [DOC] Fix and clarify the `StartSpanOptions` documentation
[#4526](https://github.com/open-telemetry/opentelemetry-cpp/pull/4526)

Expand Down
21 changes: 19 additions & 2 deletions cmake/otel-install-functions.cmake
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
include("${PROJECT_SOURCE_DIR}/cmake/thirdparty-dependency-config.cmake")
include("${PROJECT_SOURCE_DIR}/cmake/pkgconfig.cmake")

########################################################################
# INTERNAL FUNCTIONS - do not call directly. Use the otel_* "Main" functions
Expand Down Expand Up @@ -50,10 +51,11 @@ endfunction()
# OTEL_COMPONENT_FILES_MATCHING_<component>: Matching pattern for the files to be installed
# OTEL_COMPONENT_DEPENDS_<component>: List of components that this component depends on
# OTEL_COMPONENT_THIRDPARTY_DEPENDS_<component>: List of thirdparty dependencies that this component depends on
# OTEL_COMPONENT_DESCRIPTION_<component>: Optional human-readable component description
#-----------------------------------------------------------------------
function(_otel_set_component_properties)
set(optionArgs )
set(oneValueArgs COMPONENT FILES_DIRECTORY FILES_DESTINATION)
set(oneValueArgs COMPONENT DESCRIPTION FILES_DIRECTORY FILES_DESTINATION)
set(multiValueArgs TARGETS TARGETS_ALIAS FILES_MATCHING COMPONENT_DEPENDS THIRDPARTY_DEPENDS)
cmake_parse_arguments(_PROPERTIES "${optionArgs}" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")

Expand Down Expand Up @@ -95,6 +97,10 @@ function(_otel_set_component_properties)
if(_PROPERTIES_THIRDPARTY_DEPENDS)
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY OTEL_COMPONENT_THIRDPARTY_DEPENDS_${_PROPERTIES_COMPONENT} "${_PROPERTIES_THIRDPARTY_DEPENDS}")
endif()

if(_PROPERTIES_DESCRIPTION)
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY OTEL_COMPONENT_DESCRIPTION_${_PROPERTIES_COMPONENT} "${_PROPERTIES_DESCRIPTION}")
endif()
endfunction()

#-----------------------------------------------------------------------
Expand Down Expand Up @@ -283,6 +289,10 @@ function(_otel_install_component _COMPONENT)
COMPONENT ${_COMPONENT}
FILES_MATCHING ${_COMPONENT_FILES_MATCHING})
endif()

foreach(_TARGET IN LISTS _COMPONENT_TARGETS)
_otel_install_target_pkgconfig("${_TARGET}" "${_COMPONENT}")
endforeach()
endfunction()

#-----------------------------------------------------------------------
Expand Down Expand Up @@ -357,6 +367,7 @@ endfunction()
# Usage:
# otel_add_component(
# COMPONENT <component_name>
# [DESCRIPTION "<human-readable component summary>"]
# [DEPRECATED_NAMES <old_name1> <old_name2> ...]
# TARGETS <target1> <target2> ...
# [FILES_DIRECTORY <directory>
Expand All @@ -365,7 +376,7 @@ endfunction()
#-----------------------------------------------------------------------
function(otel_add_component)
set(optionArgs DEPRECATED)
set(oneValueArgs COMPONENT FILES_DIRECTORY FILES_DESTINATION)
set(oneValueArgs COMPONENT DESCRIPTION FILES_DIRECTORY FILES_DESTINATION)
set(multiValueArgs TARGETS FILES_MATCHING DEPRECATED_NAMES)
cmake_parse_arguments(_OTEL_ADD_COMP "${optionArgs}" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")

Expand Down Expand Up @@ -404,9 +415,11 @@ function(otel_add_component)
message(DEBUG " FILES_DIRECTORY: ${_OTEL_ADD_COMP_FILES_DIRECTORY}")
message(DEBUG " FILES_DESTINATION: ${_OTEL_ADD_COMP_FILES_DESTINATION}")
message(DEBUG " FILES_MATCHING: ${_OTEL_ADD_COMP_FILES_MATCHING}")
message(DEBUG " DESCRIPTION: ${_OTEL_ADD_COMP_DESCRIPTION}")

_otel_set_component_properties(
COMPONENT ${_OTEL_ADD_COMP_COMPONENT}
DESCRIPTION ${_OTEL_ADD_COMP_DESCRIPTION}
TARGETS ${_OTEL_ADD_COMP_TARGETS}
TARGETS_ALIAS ${_ALIAS_TARGETS}
FILES_DIRECTORY ${_OTEL_ADD_COMP_FILES_DIRECTORY}
Expand Down Expand Up @@ -450,8 +463,12 @@ function(otel_install_components)
get_property(_COMPONENT_FILES_DIRECTORY DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY OTEL_COMPONENT_FILES_DIRECTORY_${_COMPONENT})
get_property(_COMPONENT_FILES_DESTINATION DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY OTEL_COMPONENT_FILES_DESTINATION_${_COMPONENT})
get_property(_COMPONENT_FILES_MATCHING DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY OTEL_COMPONENT_FILES_MATCHING_${_COMPONENT})
get_property(_COMPONENT_DESCRIPTION DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY OTEL_COMPONENT_DESCRIPTION_${_COMPONENT})

message(STATUS "Install COMPONENT ${_COMPONENT}")
if(_COMPONENT_DESCRIPTION)
message(STATUS " DESCRIPTION: ${_COMPONENT_DESCRIPTION}")
endif()
message(STATUS " TARGETS: ${_COMPONENT_TARGETS}")
message(STATUS " TARGETS_ALIAS: ${_COMPONENT_TARGETS_ALIAS}")
message(STATUS " COMPONENT_DEPENDS: ${_COMPONENT_DEPENDS}")
Expand Down
111 changes: 73 additions & 38 deletions cmake/pkgconfig.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,88 @@ include_guard(GLOBAL)

# Unlike functions, macros do not introduce a scope. This is an advantage when
# trying to set global variables, as we do here.
macro (opentelemetry_set_pkgconfig_paths)
if (IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
set(OPENTELEMETRY_PC_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
else ()
set(OPENTELEMETRY_PC_LIBDIR
"\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
endif ()

if (IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
set(OPENTELEMETRY_PC_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
else ()
set(OPENTELEMETRY_PC_INCLUDEDIR
"\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
endif ()
endmacro ()

# Create the pkgconfig configuration file (aka *.pc files) and the rules to install it.
macro(opentelemetry_set_pkgconfig_paths)
if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
set(OPENTELEMETRY_PC_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
else()
set(OPENTELEMETRY_PC_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
endif()

if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
set(OPENTELEMETRY_PC_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
else()
set(OPENTELEMETRY_PC_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
endif()
endmacro()

# Register pkg-config metadata on a target. Installation is performed later by
# _otel_install_target_pkgconfig() from _otel_install_component().
#
# * library: the short name of the target, such as `api` or `resources`.
# * name: the displayed name of the library, such as "OpenTelemetry API".
# * description: the description of the library.
# * ARGN: the names of any pkgconfig modules the generated module depends on.
#
function (opentelemetry_add_pkgconfig library name description)
function(opentelemetry_add_pkgconfig library name description)

@dbarker dbarker Sep 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

My idea with #4503 is to remove the separate opentelemetry_add_pkgconfig, which redefines export name and dependencies of the target. We would then rely on the otel_add_component function to create the pkgconfig files given the targets provided and their properties.

The opentelemetry_add_pkgconfig function then becomes unecessary and the install of the pkgconfig files is a private implementation detail of installing a component.

Current mapping from the opentelemetry_add_pkgconfig args to target properties could be:

  • library:
    • Set to the target's EXPORT_NAME property (this seems to be generally the case now but needs to be verified)
  • name:
    • To be set to the public facing CMake target alias (i.e: opentelemetry-cpp::api)
  • description:
    • To be set from a new target description property
  • dependencies:
    • To be set from the target's LINK_LIBRARIES and INTERFACE_LINK_LIBRARIES properties. See _otel_collect_component_dependencies for how this is done now at the component level.

Setting the target properties is still done in many places today (EXPORT_NAME , VERSION, and SOVERSION are separate function calls on each target). These can be consolidated to a function otel_set_target_properties that takes the export name and description.

Looking at the current opentelemetry_metrics target we can already see divergence in the target dependencies between the pkgconfig function and the linked libraries.

Current:

add_library(opentelemetry_metrics ....)

set_target_properties(opentelemetry_metrics PROPERTIES EXPORT_NAME metrics)
set_target_version(opentelemetry_metrics)

target_link_libraries(
  opentelemetry_metrics PUBLIC opentelemetry_common opentelemetry_resources
                               opentelemetry_instrumentation_scope)

target_include_directories(opentelemetry_metrics ...)

if(OTELCPP_INSTALL)
  opentelemetry_add_pkgconfig(
    metrics "OpenTelemetry SDK - Metrics"
    "Components for exporting metrics in the OpenTelemetry SDK."
    "opentelemetry_resources" "opentelemetry_instrumentation_scope")
endif()

otel_add_component(
    sdk
    TARGETS
    opentelemetry_metrics
    ...)

Proposed refactor:

add_library(opentelemetry_metrics)

target_include_directories(opentelemetry_metrics ...)

target_link_libraries(
  opentelemetry_metrics PUBLIC opentelemetry_common opentelemetry_resources
                               opentelemetry_instrumentation_scope)

# Set the target export name and description explicitly. 
# The version/soversion are set implicitly. 
otel_set_target_properties(
    opentelemetry_metrics
    NAME metrics
    DESCRIPTION "OpenTelemetry C++ SDK Metrics Library")

# Set the component name, targets, description, and other existing args. 
# The pkgconfig install for each target will occur implicitly when the component is installed.
otel_add_component(
    sdk
    TARGETS
    opentelemetry_metrics
    DESCRIPTION
    "OpenTelemetry C++ SDK libraries")

set(_target "opentelemetry_${library}")

if(NOT TARGET "${_target}")
message(FATAL_ERROR
"opentelemetry_add_pkgconfig: target ${_target} not found")
endif()

set_target_properties(
"${_target}"
PROPERTIES INTERFACE_OTEL_PKGCONFIG_SHORT_NAME "${library}"
INTERFACE_OTEL_PKGCONFIG_NAME "${name}"
INTERFACE_OTEL_PKGCONFIG_DESCRIPTION "${description}"
INTERFACE_OTEL_PKGCONFIG_REQUIRES "${ARGN}")
endfunction()

# Generate and install a pkg-config file for a target that registered metadata
# via opentelemetry_add_pkgconfig().
function(_otel_install_target_pkgconfig _TARGET _COMPONENT)
get_target_property(_short_name "${_TARGET}" INTERFACE_OTEL_PKGCONFIG_SHORT_NAME)
if(NOT _short_name OR _short_name STREQUAL "_short_name-NOTFOUND")
return()
endif()

get_target_property(_pc_name "${_TARGET}" INTERFACE_OTEL_PKGCONFIG_NAME)
get_target_property(_pc_desc "${_TARGET}" INTERFACE_OTEL_PKGCONFIG_DESCRIPTION)
get_target_property(_pc_requires "${_TARGET}" INTERFACE_OTEL_PKGCONFIG_REQUIRES)

opentelemetry_set_pkgconfig_paths()
set(target "opentelemetry_${library}")
set(OPENTELEMETRY_PC_NAME "${name}")
set(OPENTELEMETRY_PC_DESCRIPTION ${description})
string(JOIN " " OPENTELEMETRY_PC_REQUIRES ${ARGN})
get_target_property(target_type ${target} TYPE)
if ("${target_type}" STREQUAL "INTERFACE_LIBRARY")

set(OPENTELEMETRY_PC_NAME "${_pc_name}")
set(OPENTELEMETRY_PC_DESCRIPTION "${_pc_desc}")
string(JOIN " " OPENTELEMETRY_PC_REQUIRES ${_pc_requires})

get_target_property(_target_type "${_TARGET}" TYPE)
if("${_target_type}" STREQUAL "INTERFACE_LIBRARY")
# Interface libraries only contain headers. They do not generate lib files
# to link against with `-l`.
set(OPENTELEMETRY_PC_LIBS "")
else ()
set(OPENTELEMETRY_PC_LIBS "-l${target}")
endif ()
get_target_property(target_defs ${target} INTERFACE_COMPILE_DEFINITIONS)
if (target_defs)
foreach (def ${target_defs})
string(APPEND OPENTELEMETRY_PC_CFLAGS " -D${def}")
endforeach ()
endif ()

# Create and install the pkg-config files.
configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/config.pc.in" "${target}.pc" @ONLY)
else()
set(OPENTELEMETRY_PC_LIBS "-lopentelemetry_${_short_name}")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just windering, would string(GENEX_STRIP "$<TARGET_FILE_BASE_NAME:${_TARGET}>" OPENTELEMETRY_PC_LIBS) be better?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I kept -lopentelemetry_${_short_name} here to preserve the existing pkg-config behavior from before this refactor, where we used -l${target} with target = opentelemetry_${library}.

Using $<TARGET_FILE_BASE_NAME:${_TARGET}> would be more robust if a target ever had a custom OUTPUT_NAME, but in this PR I wanted to avoid changing pkg-config link semantics. Also, since the .pc file is still generated via configure_file(... @ONLY), adopting generator expressions here would need a broader change (likely file(GENERATE ...)) rather than a one-line swap.

I don’t think any otel targets currently diverge from the opentelemetry_<short_name> naming convention, so this should be equivalent for now. Happy to follow up in a separate change if we want OUTPUT_NAME-aware pkg-config generation.

endif()

set(OPENTELEMETRY_PC_CFLAGS "")
get_target_property(_target_defs "${_TARGET}" INTERFACE_COMPILE_DEFINITIONS)
if(_target_defs AND NOT _target_defs STREQUAL "_target_defs-NOTFOUND")
foreach(_def IN LISTS _target_defs)
string(APPEND OPENTELEMETRY_PC_CFLAGS " -D${_def}")
endforeach()
endif()

set(_target "opentelemetry_${_short_name}")
set(_pc_file "${PROJECT_BINARY_DIR}/pkgconfig/${_target}.pc")
file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/pkgconfig")

configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/config.pc.in" "${_pc_file}"
@ONLY)

install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/${target}.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
FILES "${_pc_file}"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
COMPONENT "${_COMPONENT}")
endfunction()
27 changes: 27 additions & 0 deletions install/test/cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,33 @@ add_test(NAME full-package-run-test
COMMAND ${CMAKE_BINARY_DIR}/build-full-package-test/full_test)
# -----------------------------------------------------------

# -----------------------------------------------------------
# Pkg-config consumer test (compile/link/run against installed .pc files)
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
add_test(
NAME pkgconfig-test-config-test
COMMAND
${CMAKE_COMMAND} --log-level=DEBUG -S ${CMAKE_SOURCE_DIR}/pkgconfig_test
-B build-pkgconfig-test "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}"
${INSTALL_TEST_CMAKE_OPTIONS}
"-DINSTALL_TEST_COMPONENTS=${INSTALL_TEST_COMPONENTS}"
"-DINSTALL_TEST_SRC_DIR=${INSTALL_TEST_SRC_DIR}")

set_tests_properties(
pkgconfig-test-config-test
PROPERTIES ENVIRONMENT "PKG_CONFIG_PATH=${CMAKE_PREFIX_PATH}/lib/pkgconfig")

add_test(NAME pkgconfig-test-build-test
COMMAND ${CMAKE_COMMAND} --build
${CMAKE_BINARY_DIR}/build-pkgconfig-test --parallel)

add_test(NAME pkgconfig-test-run-test
COMMAND ${CMAKE_BINARY_DIR}/build-pkgconfig-test/full_pkgconfig_test)
else()
message(STATUS "PkgConfig not found; skipping pkg-config install tests")
endif()

# -----------------------------------------------------------
# Loop over all the components to test
foreach(component ${INSTALL_TEST_COMPONENTS})
Expand Down
60 changes: 60 additions & 0 deletions install/test/cmake/pkgconfig_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.16)

project(opentelemetry-cpp-pkgconfig-install-test LANGUAGES CXX)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This test currently only covers the pkgconfig files in the ostream component dependency chain. Can it take on a form similar to the current package test to cover all components and pkgconfig files installed?

https://github.com/open-telemetry/opentelemetry-cpp/blob/main/install/test/cmake/package_test/CMakeLists.txt


if(NOT DEFINED INSTALL_TEST_SRC_DIR)
message(
FATAL_ERROR
"INSTALL_TEST_SRC_DIR must be defined when running cmake on this test project"
)
endif()

if(NOT INSTALL_TEST_COMPONENTS)
message(FATAL_ERROR "INSTALL_TEST_COMPONENTS is empty")
endif()

find_package(PkgConfig REQUIRED)
find_package(GTest CONFIG REQUIRED)

file(GLOB _pkgconfig_pc_files
"${CMAKE_PREFIX_PATH}/lib/pkgconfig/opentelemetry_*.pc")
if(NOT _pkgconfig_pc_files)
message(
FATAL_ERROR
"No installed opentelemetry pkg-config files found under ${CMAKE_PREFIX_PATH}/lib/pkgconfig"
)
endif()

set(_pkgconfig_modules "")
foreach(_pc_file IN LISTS _pkgconfig_pc_files)
get_filename_component(_module "${_pc_file}" NAME_WE)
list(APPEND _pkgconfig_modules "${_module}")
endforeach()
list(SORT _pkgconfig_modules)

message(STATUS "Testing pkg-config install on modules = ${_pkgconfig_modules}")
message(
STATUS "Testing pkg-config install on components = ${INSTALL_TEST_COMPONENTS}"
)

pkg_check_modules(OTEL_PKGCONFIG REQUIRED ${_pkgconfig_modules})

add_executable(full_pkgconfig_test)

foreach(component IN LISTS INSTALL_TEST_COMPONENTS)
message(STATUS "Adding test source for component ${component}")
target_sources(full_pkgconfig_test
PRIVATE "${INSTALL_TEST_SRC_DIR}/test_${component}.cc")
endforeach()

target_include_directories(full_pkgconfig_test
PRIVATE ${OTEL_PKGCONFIG_INCLUDE_DIRS})
target_compile_options(full_pkgconfig_test
PRIVATE ${OTEL_PKGCONFIG_CFLAGS_OTHER})
target_link_libraries(
full_pkgconfig_test
PRIVATE ${OTEL_PKGCONFIG_LIBRARIES} ${OTEL_PKGCONFIG_LDFLAGS} GTest::gtest
GTest::gtest_main)
Loading