Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 9 additions & 1 deletion .github/actions/cmake-test/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ inputs:
description: 'Boost toolset'
required: false


runs:
using: composite
steps:
Expand All @@ -36,6 +35,15 @@ runs:
# along in the same manner to those test projects via the command line.
BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }}
OPENSSL_ROOT_DIR: ${{ steps.install-openssl.outputs.OPENSSL_ROOT_DIR }}
CMAKE_INSTALL_PREFIX: ../LAUNCHDARKLY_INSTALL
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm arbitrarily installing into this folder here. Later, this folder is injected into the cmake configure step so find_package looks there for launchdarklyConfig.cmake.

That's one use-case, but it would also be good to check:

  1. If you don't give it an install prefix, and just run cmake --install .
  2. Then if you invoke find_package without specifying a custom CMAKE_PREFIX_PATH
  3. It will work, due to find_package's lookup logic magic.

- name: Build the SDK
shell: bash
run: |
cmake --build build
- name: Install the SDK
shell: bash
run: |
cmake --install build
- name: Run CMake Integration Tests
shell: bash
run: |
Expand Down
47 changes: 43 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.19)
include(CMakeDependentOption)

project(
LaunchDarklyCPPSDKs
Copy link
Contributor Author

@cwaldren-ld cwaldren-ld Oct 7, 2024

Choose a reason for hiding this comment

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

Attempting to unify the project name with the target alias namespace (e.g. launchdarkly::server).

This is what users will encounter when they call find_package, so it would be predictable to have that be the same as what they link against.

launchdarkly
VERSION 0.1
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: currently the version number isn't being updated, and I don't have a great plan for how that will work. Likely I will change to exporting two different package configs rather than the monorepo itself.

DESCRIPTION "LaunchDarkly C++ SDK Monorepo (Server/Client)"
LANGUAGES CXX C
Expand All @@ -21,8 +21,6 @@ if (POLICY CMP0144)
cmake_policy(SET CMP0144 NEW)
endif ()

include(GNUInstallDirs)

option(BUILD_TESTING "Top-level switch for testing. Turn off to disable unit and contract tests." ON)

option(LD_BUILD_SHARED_LIBS "Build the SDKs as shared libraries" OFF)
Expand Down Expand Up @@ -155,12 +153,20 @@ endif ()

set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_RUNTIME OFF)

if (POLICY CMP0167)
# TODO: Update to use the Boost project's cmake config directly, since FindBoost was deprecated in
# cmake >= 3.30.
cmake_policy(SET CMP0167 OLD)
endif ()

find_package(Boost 1.81 REQUIRED COMPONENTS json url coroutine)
message(STATUS "LaunchDarkly: using Boost v${Boost_VERSION}")

include(${CMAKE_FILES}/certify.cmake)
set(FOXY_BUILD_TESTING OFF)
add_subdirectory(vendor/foxy)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a new FOXY_BUILD_TESTING to avoid the whole "get the previous value of BUILD_TESTING, set it to off, add_subdir, set the value back.." song and dance.



# Common, internal, and server-sent-events are built as "object" libraries.
add_subdirectory(libs/common)
add_subdirectory(libs/internal)
Expand All @@ -185,3 +191,36 @@ if (LD_BUILD_EXAMPLES)
message(STATUS "LaunchDarkly: building examples")
add_subdirectory(examples)
endif ()


# Support installation of a cmake package.
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)

write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/launchdarklyConfigVersion.cmake"
COMPATIBILITY SameMajorVersion
)

install(FILES
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/launchdarklyConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/launchdarklyConfigVersion.cmake"
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/launchdarkly"
)

configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/launchdarkly.pc.in
${CMAKE_CURRENT_BINARY_DIR}/launchdarkly.pc
)

install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/launchdarkly.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)


install(
EXPORT launchdarklyTargets
NAMESPACE launchdarkly::
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/launchdarkly"
)
51 changes: 40 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ GoogleTest is used for testing.

For information on integrating an SDK package please refer to the SDK specific README.

## CMake Usage
## CMake Options

Various CMake options are available to customize the client/server SDK builds.

Expand All @@ -66,30 +66,59 @@ Various CMake options are available to customize the client/server SDK builds.
| `LD_DYNAMIC_LINK_OPENSSL` | Whether OpenSSL is dynamically linked or not. | Off (static link) | N/A |
| `LD_BUILD_REDIS_SUPPORT` | Whether the server-side Redis Source is built or not. | Off | N/A |

**Note:** _if building the SDKs as shared libraries, then unit tests won't be able to link correctly since the SDK's C++
symbols aren't exposed. To run unit tests, build a static library._

> [!WARNING]
> When building shared libraries C++ symbols are not exported, only the C API will be exported. This is because C++ does
> not have a stable ABI.
> not have a stable ABI. For this reason, the SDK's unit tests are not built in shared library mode.

## Building the SDK from Source

Basic usage example:
To configure the SDK's CMake project:

```bash
mkdir -p build && cd build
cmake -G"Unix Makefiles" ..
# Use 'make' as the build system.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Might as well advocate for "modern" cmake commands (-B build -S .)

cmake -B build -S . -G"Unix Makefiles"
```

Slightly more advanced example - build shared libraries, and don't build any of the testing components:
To pass in config options defined in the table above, add them using `-D`:

```bash
mkdir -p build && cd build
cmake -G"Unix Makefiles" -DLD_BUILD_SHARED_LIBS=On -DBUILD_TESTING=Off ..
# Use 'make' as the build system, build shared libs, and disable testing.
cmake -B build -S . -G"Unix Makefiles" \
-DLD_BUILD_SHARED_LIBS=On \
-DBUILD_TESTING=Off ..
```

The example uses `make`, but you might instead use [Ninja](https://ninja-build.org/),
MSVC, [etc.](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html)

## Incorporating the SDK via `add_subdirectory`

The SDK can be incorporated into an existing application using CMake via `add_subdirectory.`.

```cmake
# Set SDK build options, for example:
set(LD_BUILD_SHARED_LIBS On)

add_subdirectory(path-to-cpp-sdks-repo)
target_link_libraries(your-app PRIVATE launchdarkly::client)
# ... or launchdarkly::server
````

## Incorporating the SDK via `find_package`

> [!WARNING]
> Preliminary support for `find_package` is available. The package configuration is subject to change, do not expect it
> to be stable as long as this notice is present.

If you've installed the SDK on the build system via `cmake --install`, you can consume it from
the target application like so:

```cmake
find_package(launchdarkly REQUIRED)
target_link_libraries(your-app PRIVATE launchdarkly::launchdarkly-cpp-client)
# ... or launchdarkly::launchdarkly-cpp-server
```

## LaunchDarkly overview

[LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves trillions of feature flags
Expand Down
1 change: 1 addition & 0 deletions cmake-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
include(declareProjectTest.cmake)
add_subdirectory(test_find_package)
add_subdirectory(test_add_subdirectory)
7 changes: 7 additions & 0 deletions cmake-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,10 @@ Additionally, certain variables must be forwarded to each test project CMake con

Checks that a project can include the SDK as a sub-project, via `add_subdirectory`.
This would be a likely use-case when the repo is a submodule of another project.

### cmake_projects/test_find_package

Checks that a project can include the SDK via `find_package(ldserverapi)`.
This would be a likely use-case if the SDK was installed on the system by the user.

**NOTE:** Requires SDK to be installed.
7 changes: 4 additions & 3 deletions cmake-tests/declareProjectTest.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ macro(declare_find_package_test name)
NAME ${test_prefix}_configure
COMMAND
${CMAKE_COMMAND}
# Since project/CMakeLists.txt uses find_package(), it needs to know where to find
# ldserverapiConfig.cmake. That can be found where the SDK is installed, which is CMAKE_INSTALL_PREFIX.
-DCMAKE_PREFIX_PATH=${CMAKE_INSTALL_PREFIX}
# The project under test is going to use find_package() to find the launchdarkly SDK. The package config
# is going to in turn find_package() for boost. So, we need to pass in the location of the SDK's package
# config file, as well as boost's.
"-DCMAKE_PREFIX_PATH=${CMAKE_INSTALL_PREFIX};${BOOST_ROOT}"
${CMAKE_CURRENT_SOURCE_DIR}/project
)

Expand Down
3 changes: 3 additions & 0 deletions cmake-tests/test_find_package/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# This test assumes that the SDK has been installed at CMAKE_INSTALL_PREFIX.
declare_find_package_test(test_find_package)
add_build_step(test_find_package)
12 changes: 12 additions & 0 deletions cmake-tests/test_find_package/project/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.19)

project(UseFindPackageTest)

find_package(launchdarkly REQUIRED)

add_executable(use_find_package_server main_server.cpp)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

You may wonder "why launchdarkly::launchdarkly-cpp-server"? Because when I originally named the targets, I was thinking "global namespace, it might clash with someone's server or client target."

Not sure how to make this match our launchdarkly::{client,server} aliases that are usable in the add_subdirectory case.

target_link_libraries(use_find_package_server launchdarkly::launchdarkly-cpp-server)


add_executable(use_find_package_client main_client.cpp)
target_link_libraries(use_find_package_client launchdarkly::launchdarkly-cpp-client)
25 changes: 25 additions & 0 deletions cmake-tests/test_find_package/project/main_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <launchdarkly/client_side/client.hpp>
#include <launchdarkly/context_builder.hpp>

#include <cstring>
#include <iostream>

using namespace launchdarkly;
using namespace launchdarkly::client_side;

int main() {
auto config = ConfigBuilder("sdk-key").Build();
if (!config) {
std::cout << "error: config is invalid: " << config.error() << '\n';
return 1;
}

auto context =
ContextBuilder().Kind("user", "example-user-key").Name("Sandy").Build();

auto client = Client(std::move(*config), std::move(context));

client.StartAsync();

std::cout << client.Initialized() << '\n';
}
23 changes: 23 additions & 0 deletions cmake-tests/test_find_package/project/main_server.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <launchdarkly/server_side/client.hpp>
#include <launchdarkly/server_side/config/config_builder.hpp>

#include <cstring>
#include <iostream>

using namespace launchdarkly;
using namespace launchdarkly::server_side;

int main() {
auto config = ConfigBuilder("sdk-key").Build();
if (!config) {
std::cout << "error: config is invalid: " << config.error() << '\n';
return 1;
}

auto client = Client(std::move(*config));

client.StartAsync();

std::cout << client.Initialized() << '\n';

}
23 changes: 0 additions & 23 deletions cmake/certify.cmake

This file was deleted.

4 changes: 4 additions & 0 deletions cmake/googletest.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ FetchContent_Declare(googletest
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# Disable installation of googletest
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 could guard this with some kind of config option, that maybe is tied into LD_BUILD_UNIT_TESTS or something. For now, don't attempt to install googletest when the SDK is installed.

set(INSTALL_GTEST OFF CACHE BOOL "Disable googletest installation" FORCE)

FetchContent_MakeAvailable(googletest)
8 changes: 0 additions & 8 deletions cmake/launchdarkly-cpp-clientConfig.cmake

This file was deleted.

16 changes: 16 additions & 0 deletions cmake/launchdarklyConfig.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
include(CMakeFindDependencyMacro)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not entirely sure how to support this properly. With find_package, the idea is that the SDK was already built. Now, you may want to decide whether to statically or dynamically link boost/openSSL.

But that depends on how you built the SDK in the first place.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, they basically just need to match.

Unless we start building a bunch of permutations. I don't think that sounds amazing though. (Boost basically does this and it never seems to work when I actually use it.)

if (NOT DEFINED Boost_USE_STATIC_LIBS)
if (LD_DYNAMIC_LINK_BOOST)
set(Boost_USE_STATIC_LIBS OFF)
else ()
set(Boost_USE_STATIC_LIBS ON)
endif ()
endif ()

find_dependency(Boost 1.81 COMPONENTS json url coroutine)
find_dependency(OpenSSL)
find_dependency(tl-expected)
find_dependency(certify)

include(${CMAKE_CURRENT_LIST_DIR}/launchdarklyTargets.cmake)
10 changes: 5 additions & 5 deletions cmake/rfc3339_timestamp.cmake
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FetchContent_Declare(timestamp
GIT_REPOSITORY https://github.com/chansen/c-timestamp
GIT_TAG "b205c407ae6680d23d74359ac00444b80989792f"
)
)

FetchContent_GetProperties(timestamp)
if (NOT timestamp_POPULATED)
Expand All @@ -12,20 +12,20 @@ add_library(timestamp OBJECT
${timestamp_SOURCE_DIR}/timestamp_tm.c
${timestamp_SOURCE_DIR}/timestamp_valid.c
${timestamp_SOURCE_DIR}/timestamp_parse.c
)
)

if (BUILD_SHARED_LIBS)
set_target_properties(timestamp PROPERTIES
POSITION_INDEPENDENT_CODE 1
C_VISIBILITY_PRESET hidden
)
)
endif ()

target_include_directories(timestamp PUBLIC
$<BUILD_INTERFACE:${timestamp_SOURCE_DIR}>
$<INSTALL_INTERFACE:include/timestamp>
)
)
install(
TARGETS timestamp
EXPORT ${PROJECT_NAME}-targets
EXPORT ${PROJECT_NAME}Targets
)
Loading