From ba267fafe1b9d69d38c58a4cfd12786b4039daf9 Mon Sep 17 00:00:00 2001 From: Gareth Sylvester-Bradley <31761158+garethsb@users.noreply.github.com> Date: Sun, 18 Feb 2024 08:27:40 +0000 Subject: [PATCH 1/3] Conan 2 (#373) * Update to Conan 2 and use new cmake-conan dependency provider * Fix warning about defining the CMakeDeps generator being mandatory in the future * Generate and log conan lock file * Document the update to Conan 2 * Bump CMake to 3.28.3, apart from on ubuntu-14.04 * Fix conan.io recipe link --- .github/workflows/build-test.yml | 102 ++- .github/workflows/src/build-setup.yml | 38 +- .github/workflows/src/build.yml | 7 +- .github/workflows/src/install-test.yml | 6 +- Development/cmake/NmosCppCommon.cmake | 9 +- Development/cmake/NmosCppConan.cmake | 56 -- Development/cmake/NmosCppDependencies.cmake | 20 +- Development/cmake/nmos-cpp-config.cmake.in | 4 +- Development/conanfile.txt | 5 +- Development/third_party/cmake/README.md | 9 + .../third_party/cmake/conan_provider.cmake | 627 ++++++++++++++++++ Documents/Dependencies.md | 31 +- Documents/Getting-Started.md | 20 +- Documents/Raspberry-Pi.md | 5 +- README.md | 3 +- 15 files changed, 792 insertions(+), 150 deletions(-) delete mode 100644 Development/cmake/NmosCppConan.cmake create mode 100644 Development/third_party/cmake/conan_provider.cmake diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index ad577bf1a..e6704fbfd 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -121,12 +121,16 @@ jobs: - name: install conan if: matrix.use_conan == true run: | - pip install conan~=1.47 - conan config set general.revisions_enabled=1 + pip install conan~=2.0.5 - - name: install cmake + - name: 'ubuntu-14.04: install cmake' + if: matrix.os == 'ubuntu-14.04' uses: lukka/get-cmake@v3.24.2 + - name: install cmake + if: matrix.os != 'ubuntu-14.04' + uses: lukka/get-cmake@v3.28.3 + - name: setup bash path working-directory: ${{ env.GITHUB_WORKSPACE }} shell: bash @@ -139,7 +143,7 @@ jobs: if: runner.os == 'Windows' run: | # set compiler to cl.exe to avoid building with gcc. - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe" >> $env:GITHUB_ENV + echo "CMAKE_COMPILER_ARGS=-DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe" >> $env:GITHUB_ENV # disable unused network interface netsh interface set interface name="vEthernet (nat)" admin=DISABLED # get host IP address @@ -269,13 +273,11 @@ jobs: cmake .. -DCMAKE_BUILD_TYPE:STRING="Release" -DWERROR:BOOL="0" -DBUILD_SAMPLES:BOOL="0" -DBUILD_TESTS:BOOL="0" make -j 2 && sudo make install - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DWEBSOCKETPP_INCLUDE_DIR:PATH=\"${{ env.RUNNER_WORKSPACE }}/cpprestsdk/Release/libs/websocketpp\"" >> $GITHUB_ENV - - - name: disable conan - if: matrix.use_conan == false - shell: bash - run: | - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DNMOS_CPP_USE_CONAN:BOOL=\"0\"" >> $GITHUB_ENV + echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }}" \ + "-DWEBSOCKETPP_INCLUDE_DIR:PATH=\"${{ env.RUNNER_WORKSPACE }}/cpprestsdk/Release/libs/websocketpp\"" \ + "-DNMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR:BOOL=\"1\"" \ + "-DNMOS_CPP_USE_SUPPLIED_JWT_CPP:BOOL=\"1\"" \ + >> $GITHUB_ENV - name: ubuntu avahi setup if: runner.os == 'Linux' && matrix.install_mdns == false @@ -292,10 +294,20 @@ jobs: echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DNMOS_CPP_USE_AVAHI:BOOL=\"1\"" >> $GITHUB_ENV - name: force cpprest asio - if: matrix.force_cpprest_asio == true + if: matrix.force_cpprest_asio == true && matrix.use_conan == true shell: bash run: | - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DNMOS_CPP_CONAN_OPTIONS:STRING=\"cpprestsdk:http_client_impl=asio;cpprestsdk:http_listener_impl=asio\"" >> $GITHUB_ENV + echo "CONAN_INSTALL_EXTRA_ARGS=--options\;cpprestsdk/*:http_client_impl=asio\;--options\;cpprestsdk/*:http_listener_impl=asio" >> $GITHUB_ENV + + - name: enable conan + if: matrix.use_conan == true + shell: bash + run: | + echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }}" \ + "-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES:STRING=\"third_party/cmake/conan_provider.cmake\"" \ + "-DCONAN_INSTALL_ARGS:STRING=\"--build=missing\;${{ env.CONAN_INSTALL_EXTRA_ARGS }}\;--lockfile-out=conan.lock\"" \ + >> $GITHUB_ENV + cat $GITHUB_ENV - name: setup developer command prompt for Microsoft Visual C++ if: runner.os == 'Windows' @@ -307,7 +319,12 @@ jobs: cmakeListsOrSettingsJson: CMakeListsTxtAdvanced cmakeListsTxtPath: '${{ env.GITHUB_WORKSPACE }}/Development/CMakeLists.txt' buildDirectory: '${{ env.RUNNER_WORKSPACE }}/build/' - cmakeAppendedArgs: '-GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${{ env.RUNNER_WORKSPACE }}/install" ${{ env.CMAKE_EXTRA_ARGS }}' + cmakeAppendedArgs: '-GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${{ env.RUNNER_WORKSPACE }}/install" ${{ env.CMAKE_COMPILER_ARGS }} ${{ env.CMAKE_EXTRA_ARGS }}' + + - name: dump conan lockfile + if: matrix.use_conan == true + run: | + cat ${{ env.RUNNER_WORKSPACE }}/build/conan.lock - name: unit test run: | @@ -344,10 +361,10 @@ jobs: cmakeAppendedArgs: '-GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_FIND_PACKAGE_PREFER_CONFIG="1" - -DCMAKE_MODULE_PATH="${{ env.CMAKE_WORKSPACE }}/build" + -DCMAKE_MODULE_PATH="${{ env.CMAKE_WORKSPACE }}/build/conan" -DCMAKE_PREFIX_PATH="${{ env.CMAKE_WORKSPACE }}/install" - -DCMAKE_INSTALL_PREFIX="${{ env.CMAKE_WORKSPACE }}/build" - ${{ env.CMAKE_EXTRA_ARGS }}' + -DCMAKE_INSTALL_PREFIX="${{ env.CMAKE_WORKSPACE }}/build/conan" + ${{ env.CMAKE_COMPILER_ARGS }}' - name: install test log run: | @@ -660,12 +677,16 @@ jobs: - name: install conan if: matrix.use_conan == true run: | - pip install conan~=1.47 - conan config set general.revisions_enabled=1 + pip install conan~=2.0.5 - - name: install cmake + - name: 'ubuntu-14.04: install cmake' + if: matrix.os == 'ubuntu-14.04' uses: lukka/get-cmake@v3.24.2 + - name: install cmake + if: matrix.os != 'ubuntu-14.04' + uses: lukka/get-cmake@v3.28.3 + - name: setup bash path working-directory: ${{ env.GITHUB_WORKSPACE }} shell: bash @@ -678,7 +699,7 @@ jobs: if: runner.os == 'Windows' run: | # set compiler to cl.exe to avoid building with gcc. - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe" >> $env:GITHUB_ENV + echo "CMAKE_COMPILER_ARGS=-DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe" >> $env:GITHUB_ENV # disable unused network interface netsh interface set interface name="vEthernet (nat)" admin=DISABLED # get host IP address @@ -808,13 +829,11 @@ jobs: cmake .. -DCMAKE_BUILD_TYPE:STRING="Release" -DWERROR:BOOL="0" -DBUILD_SAMPLES:BOOL="0" -DBUILD_TESTS:BOOL="0" make -j 2 && sudo make install - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DWEBSOCKETPP_INCLUDE_DIR:PATH=\"${{ env.RUNNER_WORKSPACE }}/cpprestsdk/Release/libs/websocketpp\"" >> $GITHUB_ENV - - - name: disable conan - if: matrix.use_conan == false - shell: bash - run: | - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DNMOS_CPP_USE_CONAN:BOOL=\"0\"" >> $GITHUB_ENV + echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }}" \ + "-DWEBSOCKETPP_INCLUDE_DIR:PATH=\"${{ env.RUNNER_WORKSPACE }}/cpprestsdk/Release/libs/websocketpp\"" \ + "-DNMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR:BOOL=\"1\"" \ + "-DNMOS_CPP_USE_SUPPLIED_JWT_CPP:BOOL=\"1\"" \ + >> $GITHUB_ENV - name: ubuntu avahi setup if: runner.os == 'Linux' && matrix.install_mdns == false @@ -831,10 +850,20 @@ jobs: echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DNMOS_CPP_USE_AVAHI:BOOL=\"1\"" >> $GITHUB_ENV - name: force cpprest asio - if: matrix.force_cpprest_asio == true + if: matrix.force_cpprest_asio == true && matrix.use_conan == true shell: bash run: | - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DNMOS_CPP_CONAN_OPTIONS:STRING=\"cpprestsdk:http_client_impl=asio;cpprestsdk:http_listener_impl=asio\"" >> $GITHUB_ENV + echo "CONAN_INSTALL_EXTRA_ARGS=--options\;cpprestsdk/*:http_client_impl=asio\;--options\;cpprestsdk/*:http_listener_impl=asio" >> $GITHUB_ENV + + - name: enable conan + if: matrix.use_conan == true + shell: bash + run: | + echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }}" \ + "-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES:STRING=\"third_party/cmake/conan_provider.cmake\"" \ + "-DCONAN_INSTALL_ARGS:STRING=\"--build=missing\;${{ env.CONAN_INSTALL_EXTRA_ARGS }}\;--lockfile-out=conan.lock\"" \ + >> $GITHUB_ENV + cat $GITHUB_ENV - name: setup developer command prompt for Microsoft Visual C++ if: runner.os == 'Windows' @@ -846,7 +875,12 @@ jobs: cmakeListsOrSettingsJson: CMakeListsTxtAdvanced cmakeListsTxtPath: '${{ env.GITHUB_WORKSPACE }}/Development/CMakeLists.txt' buildDirectory: '${{ env.RUNNER_WORKSPACE }}/build/' - cmakeAppendedArgs: '-GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${{ env.RUNNER_WORKSPACE }}/install" ${{ env.CMAKE_EXTRA_ARGS }}' + cmakeAppendedArgs: '-GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${{ env.RUNNER_WORKSPACE }}/install" ${{ env.CMAKE_COMPILER_ARGS }} ${{ env.CMAKE_EXTRA_ARGS }}' + + - name: dump conan lockfile + if: matrix.use_conan == true + run: | + cat ${{ env.RUNNER_WORKSPACE }}/build/conan.lock - name: unit test run: | @@ -883,10 +917,10 @@ jobs: cmakeAppendedArgs: '-GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_FIND_PACKAGE_PREFER_CONFIG="1" - -DCMAKE_MODULE_PATH="${{ env.CMAKE_WORKSPACE }}/build" + -DCMAKE_MODULE_PATH="${{ env.CMAKE_WORKSPACE }}/build/conan" -DCMAKE_PREFIX_PATH="${{ env.CMAKE_WORKSPACE }}/install" - -DCMAKE_INSTALL_PREFIX="${{ env.CMAKE_WORKSPACE }}/build" - ${{ env.CMAKE_EXTRA_ARGS }}' + -DCMAKE_INSTALL_PREFIX="${{ env.CMAKE_WORKSPACE }}/build/conan" + ${{ env.CMAKE_COMPILER_ARGS }}' - name: install test log run: | diff --git a/.github/workflows/src/build-setup.yml b/.github/workflows/src/build-setup.yml index 763251fc5..4e2b49f97 100644 --- a/.github/workflows/src/build-setup.yml +++ b/.github/workflows/src/build-setup.yml @@ -1,12 +1,16 @@ - name: install conan if: matrix.use_conan == true run: | - pip install conan~=1.47 - conan config set general.revisions_enabled=1 + pip install conan~=2.0.5 -- name: install cmake +- name: 'ubuntu-14.04: install cmake' + if: matrix.os == 'ubuntu-14.04' uses: lukka/get-cmake@v3.24.2 +- name: install cmake + if: matrix.os != 'ubuntu-14.04' + uses: lukka/get-cmake@v3.28.3 + - name: setup bash path working-directory: ${{ env.GITHUB_WORKSPACE }} shell: bash @@ -19,7 +23,7 @@ if: runner.os == 'Windows' run: | # set compiler to cl.exe to avoid building with gcc. - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe" >> $env:GITHUB_ENV + echo "CMAKE_COMPILER_ARGS=-DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe" >> $env:GITHUB_ENV # disable unused network interface netsh interface set interface name="vEthernet (nat)" admin=DISABLED # get host IP address @@ -149,13 +153,11 @@ cmake .. -DCMAKE_BUILD_TYPE:STRING="Release" -DWERROR:BOOL="0" -DBUILD_SAMPLES:BOOL="0" -DBUILD_TESTS:BOOL="0" make -j 2 && sudo make install - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DWEBSOCKETPP_INCLUDE_DIR:PATH=\"${{ env.RUNNER_WORKSPACE }}/cpprestsdk/Release/libs/websocketpp\"" >> $GITHUB_ENV - -- name: disable conan - if: matrix.use_conan == false - shell: bash - run: | - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DNMOS_CPP_USE_CONAN:BOOL=\"0\"" >> $GITHUB_ENV + echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }}" \ + "-DWEBSOCKETPP_INCLUDE_DIR:PATH=\"${{ env.RUNNER_WORKSPACE }}/cpprestsdk/Release/libs/websocketpp\"" \ + "-DNMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR:BOOL=\"1\"" \ + "-DNMOS_CPP_USE_SUPPLIED_JWT_CPP:BOOL=\"1\"" \ + >> $GITHUB_ENV - name: ubuntu avahi setup if: runner.os == 'Linux' && matrix.install_mdns == false @@ -172,7 +174,17 @@ echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DNMOS_CPP_USE_AVAHI:BOOL=\"1\"" >> $GITHUB_ENV - name: force cpprest asio - if: matrix.force_cpprest_asio == true + if: matrix.force_cpprest_asio == true && matrix.use_conan == true + shell: bash + run: | + echo "CONAN_INSTALL_EXTRA_ARGS=--options\;cpprestsdk/*:http_client_impl=asio\;--options\;cpprestsdk/*:http_listener_impl=asio" >> $GITHUB_ENV + +- name: enable conan + if: matrix.use_conan == true shell: bash run: | - echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }} -DNMOS_CPP_CONAN_OPTIONS:STRING=\"cpprestsdk:http_client_impl=asio;cpprestsdk:http_listener_impl=asio\"" >> $GITHUB_ENV + echo "CMAKE_EXTRA_ARGS=${{ env.CMAKE_EXTRA_ARGS }}" \ + "-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES:STRING=\"third_party/cmake/conan_provider.cmake\"" \ + "-DCONAN_INSTALL_ARGS:STRING=\"--build=missing\;${{ env.CONAN_INSTALL_EXTRA_ARGS }}\;--lockfile-out=conan.lock\"" \ + >> $GITHUB_ENV + cat $GITHUB_ENV diff --git a/.github/workflows/src/build.yml b/.github/workflows/src/build.yml index 9f5e41287..80b669d62 100644 --- a/.github/workflows/src/build.yml +++ b/.github/workflows/src/build.yml @@ -8,4 +8,9 @@ cmakeListsOrSettingsJson: CMakeListsTxtAdvanced cmakeListsTxtPath: '${{ env.GITHUB_WORKSPACE }}/Development/CMakeLists.txt' buildDirectory: '${{ env.RUNNER_WORKSPACE }}/build/' - cmakeAppendedArgs: '-GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${{ env.RUNNER_WORKSPACE }}/install" ${{ env.CMAKE_EXTRA_ARGS }}' + cmakeAppendedArgs: '-GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${{ env.RUNNER_WORKSPACE }}/install" ${{ env.CMAKE_COMPILER_ARGS }} ${{ env.CMAKE_EXTRA_ARGS }}' + +- name: dump conan lockfile + if: matrix.use_conan == true + run: | + cat ${{ env.RUNNER_WORKSPACE }}/build/conan.lock diff --git a/.github/workflows/src/install-test.yml b/.github/workflows/src/install-test.yml index daf0be850..fb8dde85b 100644 --- a/.github/workflows/src/install-test.yml +++ b/.github/workflows/src/install-test.yml @@ -13,10 +13,10 @@ cmakeAppendedArgs: '-GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_FIND_PACKAGE_PREFER_CONFIG="1" - -DCMAKE_MODULE_PATH="${{ env.CMAKE_WORKSPACE }}/build" + -DCMAKE_MODULE_PATH="${{ env.CMAKE_WORKSPACE }}/build/conan" -DCMAKE_PREFIX_PATH="${{ env.CMAKE_WORKSPACE }}/install" - -DCMAKE_INSTALL_PREFIX="${{ env.CMAKE_WORKSPACE }}/build" - ${{ env.CMAKE_EXTRA_ARGS }}' + -DCMAKE_INSTALL_PREFIX="${{ env.CMAKE_WORKSPACE }}/build/conan" + ${{ env.CMAKE_COMPILER_ARGS }}' - name: install test log run: | diff --git a/Development/cmake/NmosCppCommon.cmake b/Development/cmake/NmosCppCommon.cmake index 584f6908c..b9518b7e9 100644 --- a/Development/cmake/NmosCppCommon.cmake +++ b/Development/cmake/NmosCppCommon.cmake @@ -1,9 +1,6 @@ -set(NMOS_CPP_USE_CONAN ON CACHE BOOL "Use Conan to acquire dependencies") -mark_as_advanced(FORCE NMOS_CPP_USE_CONAN) - -if(NMOS_CPP_USE_CONAN) - include(cmake/NmosCppConan.cmake) -endif() +# since moving to Conan 2 and CMake 3.24 or higher, the injection point is used to configure conan +# see https://cmake.org/cmake/help/v3.24/variable/CMAKE_PROJECT_TOP_LEVEL_INCLUDES.html +unset(NMOS_CPP_USE_CONAN CACHE) include(GNUInstallDirs) diff --git a/Development/cmake/NmosCppConan.cmake b/Development/cmake/NmosCppConan.cmake deleted file mode 100644 index e88f61a2a..000000000 --- a/Development/cmake/NmosCppConan.cmake +++ /dev/null @@ -1,56 +0,0 @@ -# the Conan recipe should use its own wrapper CMakeLists.txt to call include(conanbuildinfo.cmake) and conan_basic_setup() -# see https://github.com/conan-io/cmake-conan#creating-packages -if(CONAN_EXPORTED) - return() -endif() - -if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/conan.cmake") - message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan") - file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/0.18.1/conan.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/conan.cmake") -endif() - -include(${CMAKE_CURRENT_BINARY_DIR}/conan.cmake) - -# checking the Conan version produces a more helpful message than the confusing errors -# that are reported when some dependency's recipe uses new features; Conan moves fast! -# it would be nice to output a message if its a more recent version than tested, like: -# "Found Conan version 99.99 that is higher than the current tested version: " ${CONAN_VERSION_CUR}) -set(CONAN_VERSION_MIN "1.47.0") -set(CONAN_VERSION_CUR "1.59.0") -conan_check(VERSION ${CONAN_VERSION_MIN} REQUIRED) - -set(NMOS_CPP_CONAN_BUILD_LIBS "missing" CACHE STRING "Semicolon separated list of libraries to build rather than download") -mark_as_advanced(FORCE NMOS_CPP_CONAN_BUILD_LIBS) -set(NMOS_CPP_CONAN_OPTIONS "" CACHE STRING "Semicolon separated list of Conan options") -mark_as_advanced(FORCE NMOS_CPP_CONAN_OPTIONS) - -if(CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) - # e.g. Visual Studio - conan_cmake_run(CONANFILE conanfile.txt - BASIC_SETUP - GENERATORS cmake_find_package_multi - KEEP_RPATHS - OPTIONS ${NMOS_CPP_CONAN_OPTIONS} - BUILD ${NMOS_CPP_CONAN_BUILD_LIBS}) - - # tell find_package() to try "Config" mode before "Module" mode if no mode was specified - # so a FindXXXX.cmake file in CMake's default modules directory isn't used instead of - # the Config.cmake generated by Conan in the current binary directory - # see https://docs.conan.io/en/1.39/integrations/build_system/cmake/cmake_find_package_multi_generator.html - set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE) - - # ensure Config.cmake config files generated by Conan can be found - list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_BINARY_DIR}) -else() - conan_cmake_run(CONANFILE conanfile.txt - BASIC_SETUP - NO_OUTPUT_DIRS - GENERATORS cmake_find_package - KEEP_RPATHS - OPTIONS ${NMOS_CPP_CONAN_OPTIONS} - BUILD ${NMOS_CPP_CONAN_BUILD_LIBS}) - - # ensure Find.cmake module files generated by Conan can be found - list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_BINARY_DIR}) -endif() diff --git a/Development/cmake/NmosCppDependencies.cmake b/Development/cmake/NmosCppDependencies.cmake index eb6740b84..56e36dbe1 100644 --- a/Development/cmake/NmosCppDependencies.cmake +++ b/Development/cmake/NmosCppDependencies.cmake @@ -187,7 +187,8 @@ add_library(nmos-cpp::OpenSSL ALIAS OpenSSL) # json schema validator library -if(NMOS_CPP_USE_CONAN) +set(NMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR OFF CACHE BOOL "Use supplied third_party/nlohmann") +if(NOT NMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR) set(JSON_SCHEMA_VALIDATOR_VERSION_MIN "2.1.0") set(JSON_SCHEMA_VALIDATOR_VERSION_CUR "2.3.0") find_package(nlohmann_json_schema_validator REQUIRED) @@ -459,7 +460,8 @@ endif() # jwt library -if(NMOS_CPP_USE_CONAN) +set(NMOS_CPP_USE_SUPPLIED_JWT_CPP OFF CACHE BOOL "Use supplied third_party/jwt-cpp") +if(NOT NMOS_CPP_USE_SUPPLIED_JWT_CPP) set(JWT_VERSION_MIN "0.5.1") set(JWT_VERSION_CUR "0.7.0") find_package(jwt-cpp REQUIRED) @@ -476,13 +478,19 @@ if(NMOS_CPP_USE_CONAN) add_library(jwt-cpp INTERFACE) target_link_libraries(jwt-cpp INTERFACE jwt-cpp::jwt-cpp) else() + message(STATUS "Using sources at third_party/jwt-cpp instead of external \"jwt-cpp\" package.") + set(JWT_SOURCES ) set(JWT_HEADERS + third_party/jwt-cpp/base.h third_party/jwt-cpp/jwt.h + third_party/jwt-cpp/traits/nlohmann-json/defaults.h + third_party/jwt-cpp/traits/nlohmann-json/traits.h ) + # hm, header-only so should be INTERFACE library? add_library( jwt-cpp STATIC ${JWT_SOURCES} @@ -492,14 +500,6 @@ else() source_group("Source Files" FILES ${JWT_SOURCES}) source_group("Header Files" FILES ${JWT_HEADERS}) - if(CMAKE_CXX_COMPILER_ID MATCHES GNU) - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) - target_compile_definitions( - jwt-cpp PRIVATE - ) - endif() - endif() - target_link_libraries( jwt-cpp PRIVATE nmos-cpp::compile-settings diff --git a/Development/cmake/nmos-cpp-config.cmake.in b/Development/cmake/nmos-cpp-config.cmake.in index b9e6254a2..9f49527bc 100644 --- a/Development/cmake/nmos-cpp-config.cmake.in +++ b/Development/cmake/nmos-cpp-config.cmake.in @@ -9,8 +9,10 @@ include(CMakeFindDependencyMacro) find_dependency(Boost COMPONENTS @FIND_BOOST_COMPONENTS@) find_dependency(cpprestsdk) find_dependency(OpenSSL) -if(@NMOS_CPP_USE_CONAN@) +if(NOT @NMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR@) find_dependency(nlohmann_json_schema_validator) +endif() +if(NOT @NMOS_CPP_USE_SUPPLIED_JWT_CPP@) find_dependency(jwt-cpp) endif() if(@CMAKE_SYSTEM_NAME@ STREQUAL "Linux") diff --git a/Development/conanfile.txt b/Development/conanfile.txt index 28a9d1fc9..aa55298b1 100644 --- a/Development/conanfile.txt +++ b/Development/conanfile.txt @@ -14,4 +14,7 @@ lib, *.so* -> ./lib lib, *.dylib* -> ./lib [options] -boost:shared=False +boost/*:shared=False + +[generators] +CMakeDeps diff --git a/Development/third_party/cmake/README.md b/Development/third_party/cmake/README.md index e27f9f2c9..11b068e73 100644 --- a/Development/third_party/cmake/README.md +++ b/Development/third_party/cmake/README.md @@ -29,3 +29,12 @@ Original source code: - Licensed under the Apache License, Version 2.0. - Copyright (c) 2021, NVIDIA CORPORATION. + +## CMake Provider for Conan + +Copied from [conan-io/cmake-conan](https://github.com/conan-io/cmake-conan). + +Original source code: + +- Licensed under the MIT License +- Copyright (c) 2019 JFrog diff --git a/Development/third_party/cmake/conan_provider.cmake b/Development/third_party/cmake/conan_provider.cmake new file mode 100644 index 000000000..c21ab38ab --- /dev/null +++ b/Development/third_party/cmake/conan_provider.cmake @@ -0,0 +1,627 @@ +set(CONAN_MINIMUM_VERSION 2.0.5) + + +function(detect_os OS OS_API_LEVEL OS_SDK OS_SUBSYSTEM OS_VERSION) + # it could be cross compilation + message(STATUS "CMake-Conan: cmake_system_name=${CMAKE_SYSTEM_NAME}") + if(CMAKE_SYSTEM_NAME AND NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(${OS} Macos PARENT_SCOPE) + elseif(CMAKE_SYSTEM_NAME STREQUAL "QNX") + set(${OS} Neutrino PARENT_SCOPE) + elseif(CMAKE_SYSTEM_NAME STREQUAL "CYGWIN") + set(${OS} Windows PARENT_SCOPE) + set(${OS_SUBSYSTEM} cygwin PARENT_SCOPE) + elseif(CMAKE_SYSTEM_NAME MATCHES "^MSYS") + set(${OS} Windows PARENT_SCOPE) + set(${OS_SUBSYSTEM} msys2 PARENT_SCOPE) + else() + set(${OS} ${CMAKE_SYSTEM_NAME} PARENT_SCOPE) + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Android") + if(DEFINED ANDROID_PLATFORM) + string(REGEX MATCH "[0-9]+" _OS_API_LEVEL ${ANDROID_PLATFORM}) + elseif(DEFINED CMAKE_SYSTEM_VERSION) + set(_OS_API_LEVEL ${CMAKE_SYSTEM_VERSION}) + endif() + message(STATUS "CMake-Conan: android api level=${_OS_API_LEVEL}") + set(${OS_API_LEVEL} ${_OS_API_LEVEL} PARENT_SCOPE) + endif() + if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS") + # CMAKE_OSX_SYSROOT contains the full path to the SDK for MakeFile/Ninja + # generators, but just has the original input string for Xcode. + if(NOT IS_DIRECTORY ${CMAKE_OSX_SYSROOT}) + set(_OS_SDK ${CMAKE_OSX_SYSROOT}) + else() + if(CMAKE_OSX_SYSROOT MATCHES Simulator) + set(apple_platform_suffix simulator) + else() + set(apple_platform_suffix os) + endif() + if(CMAKE_OSX_SYSROOT MATCHES AppleTV) + set(_OS_SDK "appletv${apple_platform_suffix}") + elseif(CMAKE_OSX_SYSROOT MATCHES iPhone) + set(_OS_SDK "iphone${apple_platform_suffix}") + elseif(CMAKE_OSX_SYSROOT MATCHES Watch) + set(_OS_SDK "watch${apple_platform_suffix}") + endif() + endif() + if(DEFINED _OS_SDK) + message(STATUS "CMake-Conan: cmake_osx_sysroot=${CMAKE_OSX_SYSROOT}") + set(${OS_SDK} ${_OS_SDK} PARENT_SCOPE) + endif() + if(DEFINED CMAKE_OSX_DEPLOYMENT_TARGET) + message(STATUS "CMake-Conan: cmake_osx_deployment_target=${CMAKE_OSX_DEPLOYMENT_TARGET}") + set(${OS_VERSION} ${CMAKE_OSX_DEPLOYMENT_TARGET} PARENT_SCOPE) + endif() + endif() + endif() +endfunction() + + +function(detect_arch ARCH) + # CMAKE_OSX_ARCHITECTURES can contain multiple architectures, but Conan only supports one. + # Therefore this code only finds one. If the recipes support multiple architectures, the + # build will work. Otherwise, there will be a linker error for the missing architecture(s). + if(DEFINED CMAKE_OSX_ARCHITECTURES) + string(REPLACE " " ";" apple_arch_list "${CMAKE_OSX_ARCHITECTURES}") + list(LENGTH apple_arch_list apple_arch_count) + if(apple_arch_count GREATER 1) + message(WARNING "CMake-Conan: Multiple architectures detected, this will only work if Conan recipe(s) produce fat binaries.") + endif() + endif() + if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS" AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL "") + set(host_arch ${CMAKE_OSX_ARCHITECTURES}) + elseif(MSVC) + set(host_arch ${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}) + else() + set(host_arch ${CMAKE_SYSTEM_PROCESSOR}) + endif() + if(host_arch MATCHES "aarch64|arm64|ARM64") + set(_ARCH armv8) + elseif(host_arch MATCHES "armv7|armv7-a|armv7l|ARMV7") + set(_ARCH armv7) + elseif(host_arch MATCHES armv7s) + set(_ARCH armv7s) + elseif(host_arch MATCHES "i686|i386|X86") + set(_ARCH x86) + elseif(host_arch MATCHES "AMD64|amd64|x86_64|x64") + set(_ARCH x86_64) + endif() + message(STATUS "CMake-Conan: cmake_system_processor=${_ARCH}") + set(${ARCH} ${_ARCH} PARENT_SCOPE) +endfunction() + + +function(detect_cxx_standard CXX_STANDARD) + set(${CXX_STANDARD} ${CMAKE_CXX_STANDARD} PARENT_SCOPE) + if(CMAKE_CXX_EXTENSIONS) + set(${CXX_STANDARD} "gnu${CMAKE_CXX_STANDARD}" PARENT_SCOPE) + endif() +endfunction() + + +macro(detect_gnu_libstdcxx) + # _CONAN_IS_GNU_LIBSTDCXX true if GNU libstdc++ + check_cxx_source_compiles(" + #include + #if !defined(__GLIBCXX__) && !defined(__GLIBCPP__) + static_assert(false); + #endif + int main(){}" _CONAN_IS_GNU_LIBSTDCXX) + + # _CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI true if C++11 ABI + check_cxx_source_compiles(" + #include + static_assert(sizeof(std::string) != sizeof(void*), \"using libstdc++\"); + int main () {}" _CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) + + set(_CONAN_GNU_LIBSTDCXX_SUFFIX "") + if(_CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) + set(_CONAN_GNU_LIBSTDCXX_SUFFIX "11") + endif() + unset (_CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) +endmacro() + + +macro(detect_libcxx) + # _CONAN_IS_LIBCXX true if LLVM libc++ + check_cxx_source_compiles(" + #include + #if !defined(_LIBCPP_VERSION) + static_assert(false); + #endif + int main(){}" _CONAN_IS_LIBCXX) +endmacro() + + +function(detect_lib_cxx LIB_CXX) + if(CMAKE_SYSTEM_NAME STREQUAL "Android") + message(STATUS "CMake-Conan: android_stl=${CMAKE_ANDROID_STL_TYPE}") + set(${LIB_CXX} ${CMAKE_ANDROID_STL_TYPE} PARENT_SCOPE) + return() + endif() + + include(CheckCXXSourceCompiles) + + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + detect_gnu_libstdcxx() + set(${LIB_CXX} "libstdc++${_CONAN_GNU_LIBSTDCXX_SUFFIX}" PARENT_SCOPE) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") + set(${LIB_CXX} "libc++" PARENT_SCOPE) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_SYSTEM_NAME MATCHES "Windows") + # Check for libc++ + detect_libcxx() + if(_CONAN_IS_LIBCXX) + set(${LIB_CXX} "libc++" PARENT_SCOPE) + return() + endif() + + # Check for libstdc++ + detect_gnu_libstdcxx() + if(_CONAN_IS_GNU_LIBSTDCXX) + set(${LIB_CXX} "libstdc++${_CONAN_GNU_LIBSTDCXX_SUFFIX}" PARENT_SCOPE) + return() + endif() + + # TODO: it would be an error if we reach this point + elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + # Do nothing - compiler.runtime and compiler.runtime_type + # should be handled separately: https://github.com/conan-io/cmake-conan/pull/516 + return() + else() + # TODO: unable to determine, ask user to provide a full profile file instead + endif() +endfunction() + + +function(detect_compiler COMPILER COMPILER_VERSION COMPILER_RUNTIME COMPILER_RUNTIME_TYPE) + if(DEFINED CMAKE_CXX_COMPILER_ID) + set(_COMPILER ${CMAKE_CXX_COMPILER_ID}) + set(_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) + else() + if(NOT DEFINED CMAKE_C_COMPILER_ID) + message(FATAL_ERROR "C or C++ compiler not defined") + endif() + set(_COMPILER ${CMAKE_C_COMPILER_ID}) + set(_COMPILER_VERSION ${CMAKE_C_COMPILER_VERSION}) + endif() + + message(STATUS "CMake-Conan: CMake compiler=${_COMPILER}") + message(STATUS "CMake-Conan: CMake compiler version=${_COMPILER_VERSION}") + + if(_COMPILER MATCHES MSVC) + set(_COMPILER "msvc") + string(SUBSTRING ${MSVC_VERSION} 0 3 _COMPILER_VERSION) + # Configure compiler.runtime and compiler.runtime_type settings for MSVC + if(CMAKE_MSVC_RUNTIME_LIBRARY) + set(_msvc_runtime_library ${CMAKE_MSVC_RUNTIME_LIBRARY}) + else() + set(_msvc_runtime_library MultiThreaded$<$:Debug>DLL) # default value documented by CMake + endif() + + set(_KNOWN_MSVC_RUNTIME_VALUES "") + list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded MultiThreadedDLL) + list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreadedDebug MultiThreadedDebugDLL) + list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded$<$:Debug> MultiThreaded$<$:Debug>DLL) + + # only accept the 6 possible values, otherwise we don't don't know to map this + if(NOT _msvc_runtime_library IN_LIST _KNOWN_MSVC_RUNTIME_VALUES) + message(FATAL_ERROR "CMake-Conan: unable to map MSVC runtime: ${_msvc_runtime_library} to Conan settings") + endif() + + # Runtime is "dynamic" in all cases if it ends in DLL + if(_msvc_runtime_library MATCHES ".*DLL$") + set(_COMPILER_RUNTIME "dynamic") + else() + set(_COMPILER_RUNTIME "static") + endif() + message(STATUS "CMake-Conan: CMake compiler.runtime=${_COMPILER_RUNTIME}") + + # Only define compiler.runtime_type when explicitly requested + # If a generator expression is used, let Conan handle it conditional on build_type + if(NOT _msvc_runtime_library MATCHES ":Debug>") + if(_msvc_runtime_library MATCHES "Debug") + set(_COMPILER_RUNTIME_TYPE "Debug") + else() + set(_COMPILER_RUNTIME_TYPE "Release") + endif() + message(STATUS "CMake-Conan: CMake compiler.runtime_type=${_COMPILER_RUNTIME_TYPE}") + endif() + + unset(_KNOWN_MSVC_RUNTIME_VALUES) + + elseif(_COMPILER MATCHES AppleClang) + set(_COMPILER "apple-clang") + string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) + list(GET VERSION_LIST 0 _COMPILER_VERSION) + elseif(_COMPILER MATCHES Clang) + set(_COMPILER "clang") + string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) + list(GET VERSION_LIST 0 _COMPILER_VERSION) + elseif(_COMPILER MATCHES GNU) + set(_COMPILER "gcc") + string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) + list(GET VERSION_LIST 0 _COMPILER_VERSION) + endif() + + message(STATUS "CMake-Conan: [settings] compiler=${_COMPILER}") + message(STATUS "CMake-Conan: [settings] compiler.version=${_COMPILER_VERSION}") + if (_COMPILER_RUNTIME) + message(STATUS "CMake-Conan: [settings] compiler.runtime=${_COMPILER_RUNTIME}") + endif() + if (_COMPILER_RUNTIME_TYPE) + message(STATUS "CMake-Conan: [settings] compiler.runtime_type=${_COMPILER_RUNTIME_TYPE}") + endif() + + set(${COMPILER} ${_COMPILER} PARENT_SCOPE) + set(${COMPILER_VERSION} ${_COMPILER_VERSION} PARENT_SCOPE) + set(${COMPILER_RUNTIME} ${_COMPILER_RUNTIME} PARENT_SCOPE) + set(${COMPILER_RUNTIME_TYPE} ${_COMPILER_RUNTIME_TYPE} PARENT_SCOPE) +endfunction() + + +function(detect_build_type BUILD_TYPE) + get_property(_MULTICONFIG_GENERATOR GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(NOT _MULTICONFIG_GENERATOR) + # Only set when we know we are in a single-configuration generator + # Note: we may want to fail early if `CMAKE_BUILD_TYPE` is not defined + set(${BUILD_TYPE} ${CMAKE_BUILD_TYPE} PARENT_SCOPE) + endif() +endfunction() + +macro(set_conan_compiler_if_appleclang lang command output_variable) + if(CMAKE_${lang}_COMPILER_ID STREQUAL "AppleClang") + execute_process(COMMAND xcrun --find ${command} + OUTPUT_VARIABLE _xcrun_out OUTPUT_STRIP_TRAILING_WHITESPACE) + cmake_path(GET _xcrun_out PARENT_PATH _xcrun_toolchain_path) + cmake_path(GET CMAKE_${lang}_COMPILER PARENT_PATH _compiler_parent_path) + if ("${_xcrun_toolchain_path}" STREQUAL "${_compiler_parent_path}") + set(${output_variable} "") + endif() + unset(_xcrun_out) + unset(_xcrun_toolchain_path) + unset(_compiler_parent_path) + endif() +endmacro() + + +macro(append_compiler_executables_configuration) + set(_conan_c_compiler "") + set(_conan_cpp_compiler "") + if(CMAKE_C_COMPILER) + set(_conan_c_compiler "\"c\":\"${CMAKE_C_COMPILER}\",") + set_conan_compiler_if_appleclang(C cc _conan_c_compiler) + else() + message(WARNING "CMake-Conan: The C compiler is not defined. " + "Please define CMAKE_C_COMPILER or enable the C language.") + endif() + if(CMAKE_CXX_COMPILER) + set(_conan_cpp_compiler "\"cpp\":\"${CMAKE_CXX_COMPILER}\"") + set_conan_compiler_if_appleclang(CXX c++ _conan_cpp_compiler) + else() + message(WARNING "CMake-Conan: The C++ compiler is not defined. " + "Please define CMAKE_CXX_COMPILER or enable the C++ language.") + endif() + + if(NOT "x${_conan_c_compiler}${_conan_cpp_compiler}" STREQUAL "x") + string(APPEND PROFILE "tools.build:compiler_executables={${_conan_c_compiler}${_conan_cpp_compiler}}\n") + endif() + unset(_conan_c_compiler) + unset(_conan_cpp_compiler) +endmacro() + + +function(detect_host_profile output_file) + detect_os(MYOS MYOS_API_LEVEL MYOS_SDK MYOS_SUBSYSTEM MYOS_VERSION) + detect_arch(MYARCH) + detect_compiler(MYCOMPILER MYCOMPILER_VERSION MYCOMPILER_RUNTIME MYCOMPILER_RUNTIME_TYPE) + detect_cxx_standard(MYCXX_STANDARD) + detect_lib_cxx(MYLIB_CXX) + detect_build_type(MYBUILD_TYPE) + + set(PROFILE "") + string(APPEND PROFILE "[settings]\n") + if(MYARCH) + string(APPEND PROFILE arch=${MYARCH} "\n") + endif() + if(MYOS) + string(APPEND PROFILE os=${MYOS} "\n") + endif() + if(MYOS_API_LEVEL) + string(APPEND PROFILE os.api_level=${MYOS_API_LEVEL} "\n") + endif() + if(MYOS_VERSION) + string(APPEND PROFILE os.version=${MYOS_VERSION} "\n") + endif() + if(MYOS_SDK) + string(APPEND PROFILE os.sdk=${MYOS_SDK} "\n") + endif() + if(MYOS_SUBSYSTEM) + string(APPEND PROFILE os.subsystem=${MYOS_SUBSYSTEM} "\n") + endif() + if(MYCOMPILER) + string(APPEND PROFILE compiler=${MYCOMPILER} "\n") + endif() + if(MYCOMPILER_VERSION) + string(APPEND PROFILE compiler.version=${MYCOMPILER_VERSION} "\n") + endif() + if(MYCOMPILER_RUNTIME) + string(APPEND PROFILE compiler.runtime=${MYCOMPILER_RUNTIME} "\n") + endif() + if(MYCOMPILER_RUNTIME_TYPE) + string(APPEND PROFILE compiler.runtime_type=${MYCOMPILER_RUNTIME_TYPE} "\n") + endif() + if(MYCXX_STANDARD) + string(APPEND PROFILE compiler.cppstd=${MYCXX_STANDARD} "\n") + endif() + if(MYLIB_CXX) + string(APPEND PROFILE compiler.libcxx=${MYLIB_CXX} "\n") + endif() + if(MYBUILD_TYPE) + string(APPEND PROFILE "build_type=${MYBUILD_TYPE}\n") + endif() + + if(NOT DEFINED output_file) + set(_FN "${CMAKE_BINARY_DIR}/profile") + else() + set(_FN ${output_file}) + endif() + + string(APPEND PROFILE "[conf]\n") + string(APPEND PROFILE "tools.cmake.cmaketoolchain:generator=${CMAKE_GENERATOR}\n") + + # propagate compilers via profile + append_compiler_executables_configuration() + + if(MYOS STREQUAL "Android") + string(APPEND PROFILE "tools.android:ndk_path=${CMAKE_ANDROID_NDK}\n") + endif() + + message(STATUS "CMake-Conan: Creating profile ${_FN}") + file(WRITE ${_FN} ${PROFILE}) + message(STATUS "CMake-Conan: Profile: \n${PROFILE}") +endfunction() + + +function(conan_profile_detect_default) + message(STATUS "CMake-Conan: Checking if a default profile exists") + execute_process(COMMAND ${CONAN_COMMAND} profile path default + RESULT_VARIABLE return_code + OUTPUT_VARIABLE conan_stdout + ERROR_VARIABLE conan_stderr + ECHO_ERROR_VARIABLE # show the text output regardless + ECHO_OUTPUT_VARIABLE + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(NOT ${return_code} EQUAL "0") + message(STATUS "CMake-Conan: The default profile doesn't exist, detecting it.") + execute_process(COMMAND ${CONAN_COMMAND} profile detect + RESULT_VARIABLE return_code + OUTPUT_VARIABLE conan_stdout + ERROR_VARIABLE conan_stderr + ECHO_ERROR_VARIABLE # show the text output regardless + ECHO_OUTPUT_VARIABLE + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() +endfunction() + + +function(conan_install) + cmake_parse_arguments(ARGS CONAN_ARGS ${ARGN}) + set(CONAN_OUTPUT_FOLDER ${CMAKE_BINARY_DIR}/conan) + # Invoke "conan install" with the provided arguments + set(CONAN_ARGS ${CONAN_ARGS} -of=${CONAN_OUTPUT_FOLDER}) + message(STATUS "CMake-Conan: conan install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN}") + + + # In case there was not a valid cmake executable in the PATH, we inject the + # same we used to invoke the provider to the PATH + if(DEFINED PATH_TO_CMAKE_BIN) + set(_OLD_PATH $ENV{PATH}) + set(ENV{PATH} "$ENV{PATH}:${PATH_TO_CMAKE_BIN}") + endif() + + execute_process(COMMAND ${CONAN_COMMAND} install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN} --format=json + RESULT_VARIABLE return_code + OUTPUT_VARIABLE conan_stdout + ERROR_VARIABLE conan_stderr + ECHO_ERROR_VARIABLE # show the text output regardless + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + if(DEFINED PATH_TO_CMAKE_BIN) + set(ENV{PATH} "${_OLD_PATH}") + endif() + + if(NOT "${return_code}" STREQUAL "0") + message(FATAL_ERROR "Conan install failed='${return_code}'") + else() + # the files are generated in a folder that depends on the layout used, if + # one is specified, but we don't know a priori where this is. + # TODO: this can be made more robust if Conan can provide this in the json output + string(JSON CONAN_GENERATORS_FOLDER GET ${conan_stdout} graph nodes 0 generators_folder) + cmake_path(CONVERT ${CONAN_GENERATORS_FOLDER} TO_CMAKE_PATH_LIST CONAN_GENERATORS_FOLDER) + # message("conan stdout: ${conan_stdout}") + message(STATUS "CMake-Conan: CONAN_GENERATORS_FOLDER=${CONAN_GENERATORS_FOLDER}") + set_property(GLOBAL PROPERTY CONAN_GENERATORS_FOLDER "${CONAN_GENERATORS_FOLDER}") + # reconfigure on conanfile changes + string(JSON CONANFILE GET ${conan_stdout} graph nodes 0 label) + message(STATUS "CMake-Conan: CONANFILE=${CMAKE_SOURCE_DIR}/${CONANFILE}") + set_property(DIRECTORY ${CMAKE_SOURCE_DIR} APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/${CONANFILE}") + # success + set_property(GLOBAL PROPERTY CONAN_INSTALL_SUCCESS TRUE) + endif() +endfunction() + + +function(conan_get_version conan_command conan_current_version) + execute_process( + COMMAND ${conan_command} --version + OUTPUT_VARIABLE conan_output + RESULT_VARIABLE conan_result + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(conan_result) + message(FATAL_ERROR "CMake-Conan: Error when trying to run Conan") + endif() + + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" conan_version ${conan_output}) + set(${conan_current_version} ${conan_version} PARENT_SCOPE) +endfunction() + + +function(conan_version_check) + set(options ) + set(oneValueArgs MINIMUM CURRENT) + set(multiValueArgs ) + cmake_parse_arguments(CONAN_VERSION_CHECK + "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT CONAN_VERSION_CHECK_MINIMUM) + message(FATAL_ERROR "CMake-Conan: Required parameter MINIMUM not set!") + endif() + if(NOT CONAN_VERSION_CHECK_CURRENT) + message(FATAL_ERROR "CMake-Conan: Required parameter CURRENT not set!") + endif() + + if(CONAN_VERSION_CHECK_CURRENT VERSION_LESS CONAN_VERSION_CHECK_MINIMUM) + message(FATAL_ERROR "CMake-Conan: Conan version must be ${CONAN_VERSION_CHECK_MINIMUM} or later") + endif() +endfunction() + + +macro(construct_profile_argument argument_variable profile_list) + set(${argument_variable} "") + if("${profile_list}" STREQUAL "CONAN_HOST_PROFILE") + set(_arg_flag "--profile:host=") + elseif("${profile_list}" STREQUAL "CONAN_BUILD_PROFILE") + set(_arg_flag "--profile:build=") + endif() + + set(_profile_list "${${profile_list}}") + list(TRANSFORM _profile_list REPLACE "auto-cmake" "${CMAKE_BINARY_DIR}/conan_host_profile") + list(TRANSFORM _profile_list PREPEND ${_arg_flag}) + set(${argument_variable} ${_profile_list}) + + unset(_arg_flag) + unset(_profile_list) +endmacro() + + +macro(conan_provide_dependency method package_name) + set_property(GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED TRUE) + get_property(_conan_install_success GLOBAL PROPERTY CONAN_INSTALL_SUCCESS) + if(NOT _conan_install_success) + find_program(CONAN_COMMAND "conan" REQUIRED) + conan_get_version(${CONAN_COMMAND} CONAN_CURRENT_VERSION) + conan_version_check(MINIMUM ${CONAN_MINIMUM_VERSION} CURRENT ${CONAN_CURRENT_VERSION}) + message(STATUS "CMake-Conan: first find_package() found. Installing dependencies with Conan") + if("default" IN_LIST CONAN_HOST_PROFILE OR "default" IN_LIST CONAN_BUILD_PROFILE) + conan_profile_detect_default() + endif() + if("auto-cmake" IN_LIST CONAN_HOST_PROFILE) + detect_host_profile(${CMAKE_BINARY_DIR}/conan_host_profile) + endif() + construct_profile_argument(_host_profile_flags CONAN_HOST_PROFILE) + construct_profile_argument(_build_profile_flags CONAN_BUILD_PROFILE) + if(EXISTS "${CMAKE_SOURCE_DIR}/conanfile.py") + file(READ "${CMAKE_SOURCE_DIR}/conanfile.py" outfile) + if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") + message(WARNING "Cmake-conan: CMakeDeps generator was not defined in the conanfile") + endif() + set(generator "") + elseif (EXISTS "${CMAKE_SOURCE_DIR}/conanfile.txt") + file(READ "${CMAKE_SOURCE_DIR}/conanfile.txt" outfile) + if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") + message(WARNING "Cmake-conan: CMakeDeps generator was not defined in the conanfile. " + "Please define the generator as it will be mandatory in the future") + endif() + set(generator "-g;CMakeDeps") + endif() + get_property(_multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(NOT _multiconfig_generator) + message(STATUS "CMake-Conan: Installing single configuration ${CMAKE_BUILD_TYPE}") + conan_install(${_host_profile_flags} ${_build_profile_flags} ${CONAN_INSTALL_ARGS} ${generator}) + else() + message(STATUS "CMake-Conan: Installing both Debug and Release") + conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Release ${CONAN_INSTALL_ARGS} ${generator}) + conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Debug ${CONAN_INSTALL_ARGS} ${generator}) + endif() + unset(_host_profile_flags) + unset(_build_profile_flags) + unset(_multiconfig_generator) + unset(_conan_install_success) + else() + message(STATUS "CMake-Conan: find_package(${ARGV1}) found, 'conan install' already ran") + unset(_conan_install_success) + endif() + + get_property(_conan_generators_folder GLOBAL PROPERTY CONAN_GENERATORS_FOLDER) + + # Ensure that we consider Conan-provided packages ahead of any other, + # irrespective of other settings that modify the search order or search paths + # This follows the guidelines from the find_package documentation + # (https://cmake.org/cmake/help/latest/command/find_package.html): + # find_package ( PATHS paths... NO_DEFAULT_PATH) + # find_package () + + # Filter out `REQUIRED` from the argument list, as the first call may fail + set(_find_args_${package_name} "${ARGN}") + list(REMOVE_ITEM _find_args_${package_name} "REQUIRED") + if(NOT "MODULE" IN_LIST _find_args_${package_name}) + find_package(${package_name} ${_find_args_${package_name}} BYPASS_PROVIDER PATHS "${_conan_generators_folder}" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) + unset(_find_args_${package_name}) + endif() + + # Invoke find_package a second time - if the first call succeeded, + # this will simply reuse the result. If not, fall back to CMake default search + # behaviour, also allowing modules to be searched. + if(NOT ${package_name}_FOUND) + list(FIND CMAKE_MODULE_PATH "${_conan_generators_folder}" _index) + if(_index EQUAL -1) + list(PREPEND CMAKE_MODULE_PATH "${_conan_generators_folder}") + endif() + unset(_index) + find_package(${package_name} ${ARGN} BYPASS_PROVIDER) + list(REMOVE_ITEM CMAKE_MODULE_PATH "${_conan_generators_folder}") + endif() +endmacro() + + +cmake_language( + SET_DEPENDENCY_PROVIDER conan_provide_dependency + SUPPORTED_METHODS FIND_PACKAGE +) + + +macro(conan_provide_dependency_check) + set(_CONAN_PROVIDE_DEPENDENCY_INVOKED FALSE) + get_property(_CONAN_PROVIDE_DEPENDENCY_INVOKED GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED) + if(NOT _CONAN_PROVIDE_DEPENDENCY_INVOKED) + message(WARNING "Conan is correctly configured as dependency provider, " + "but Conan has not been invoked. Please add at least one " + "call to `find_package()`.") + if(DEFINED CONAN_COMMAND) + # supress warning in case `CONAN_COMMAND` was specified but unused. + set(_CONAN_COMMAND ${CONAN_COMMAND}) + unset(_CONAN_COMMAND) + endif() + endif() + unset(_CONAN_PROVIDE_DEPENDENCY_INVOKED) +endmacro() + + +# Add a deferred call at the end of processing the top-level directory +# to check if the dependency provider was invoked at all. +cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL conan_provide_dependency_check) + +# Configurable variables for Conan profiles +set(CONAN_HOST_PROFILE "default;auto-cmake" CACHE STRING "Conan host profile") +set(CONAN_BUILD_PROFILE "default" CACHE STRING "Conan build profile") +set(CONAN_INSTALL_ARGS "--build=missing" CACHE STRING "Command line arguments for conan install") + +find_program(_cmake_program NAMES cmake NO_PACKAGE_ROOT_PATH NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NO_CMAKE_FIND_ROOT_PATH) +if(NOT _cmake_program) + get_filename_component(PATH_TO_CMAKE_BIN "${CMAKE_COMMAND}" DIRECTORY) + set(PATH_TO_CMAKE_BIN "${PATH_TO_CMAKE_BIN}" CACHE INTERNAL "Path where the CMake executable is") +endif() + diff --git a/Documents/Dependencies.md b/Documents/Dependencies.md index d9054f395..5c8490a8a 100644 --- a/Documents/Dependencies.md +++ b/Documents/Dependencies.md @@ -34,7 +34,7 @@ Specific instructions for [cross-compiling for Raspberry Pi](Raspberry-Pi.md) ar 1. Download and install a recent [CMake stable release](https://cmake.org/download/#latest) for your platform Notes: - - Currently, CMake 3.17 or higher is required; version 3.24.2 (latest release at the time) has been tested + - Currently, CMake 3.24 or higher is required in order to use the Conan package manager; version 3.28.3 (latest release at the time) has been tested - Pre-built binary distributions are available for many platforms - On Linux distributions, e.g. Ubuntu 14.04 LTS (long-term support), the pre-built binary version available via ``apt-get`` may be too out-of-date Fetch, build and install a suitable version: @@ -47,7 +47,7 @@ Specific instructions for [cross-compiling for Raspberry Pi](Raspberry-Pi.md) ar sudo make install cd .. ``` - - Some CMake modules derived from third-party sources are included in the [third_party/cmake](../Development/third_party/cmake) directory + - Some CMake modules derived from third-party sources are supplied in the [third_party/cmake](../Development/third_party/cmake) directory ### Conan @@ -55,23 +55,16 @@ By default nmos-cpp uses [Conan](https://conan.io) to download most of its depen 1. Install Python 3 if necessary Note: The Python scripts directory needs to be added to the `PATH`, so the Conan executable can be found -2. Install or upgrade Conan using `pip install --upgrade conan~=1.47` +2. Install or upgrade Conan using `pip install --upgrade conan~=2.0.5` Notes: - - On some platforms with Python 2 and Python 3 both installed this may need to be `pip3 install --upgrade conan~=1.47` - - Currently, Conan 1.47 or higher (and lower than version 2.0) is required by the nmos-cpp recipe; dependencies may require a higher version; version 1.59.0 has been tested - - Conan evolves fairly quickly, so it's worth running `pip install --upgrade conan~=1.47` regularly - - By default [Conan assumes semver compatibility](https://docs.conan.io/en/1.42/creating_packages/define_abi_compatibility.html#versioning-schema). - Boost and other C++ libraries do not meet this expectation and break ABI compatibility between e.g. minor versions. - Unfortunately, the recipes in Conan Center Index do not generally customize their `package_id` method to take this into account. - Therefore it is strongly recommended to change Conan's default package id mode to `minor_mode` or a stricter mode such as `recipe_revision_mode`. - ```sh - conan config set general.default_package_id_mode=minor_mode - ``` + - On some platforms with Python 2 and Python 3 both installed this may need to be `pip3 install --upgrade conan~=2.0.5` + - Conan 2.0.5 or higher is required; dependencies may require a higher version; version 2.0.17 (latest release at the time) has been tested + - Conan 1.X is no longer supported 3. Install a [DNS Service Discovery](#dns-service-discovery) implementation, since this isn't currently handled by Conan Now follow the [Getting Started](Getting-Started.md) instructions directly. Conan is used to download the rest of the dependencies. -If you prefer not to use Conan, you must install Boost, WebSocket++, OpenSSL and C++ REST SDK as detailed below then call CMake with `-DNMOS_CPP_USE_CONAN:BOOL="0"` when building nmos-cpp. +If you prefer not to use Conan, you must install Boost, WebSocket++, OpenSSL and C++ REST SDK as detailed below. ### Boost C++ Libraries @@ -255,9 +248,11 @@ If using Conan, this section can be skipped.
If not using Conan... -A copy of the source code necessary to use this library is included in the [third_party/nlohmann](../Development/third_party/nlohmann) directory. +A copy of the source code necessary to use this library is supplied in the [third_party/nlohmann](../Development/third_party/nlohmann) directory. No installation is necessary. +(The [Getting Started](Getting-Started.md) instructions explain how to set ``NMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR`` in order to use the supplied version when building nmos-cpp.) +
### jwt-cpp @@ -266,9 +261,11 @@ If using Conan, this section can be skipped.
If not using Conan... -A copy of the source code necessary to use this library is included in the [third_party/jwt-cpp](../Development/third_party/jwt-cpp) directory. +A copy of the source code necessary to use this library is supplied in the [third_party/jwt-cpp](../Development/third_party/jwt-cpp) directory. No installation is necessary. +(The [Getting Started](Getting-Started.md) instructions explain how to set ``NMOS_CPP_USE_SUPPLIED_JWT_CPP`` in order to use the supplied version when building nmos-cpp.) +
### DNS Service Discovery @@ -320,7 +317,7 @@ Notes: ### Catch -A copy of the single header version (v1.10.0) is included in the [third_party/catch](../Development/third_party/catch) directory. +A copy of the single header version (v1.10.0) is supplied in the [third_party/catch](../Development/third_party/catch) directory. No installation is necessary. # What Next? diff --git a/Documents/Getting-Started.md b/Documents/Getting-Started.md index ac5c9f1cf..6b5e92bcc 100644 --- a/Documents/Getting-Started.md +++ b/Documents/Getting-Started.md @@ -38,6 +38,9 @@ Notes: - Set ``cpprestsdk_DIR`` (PATH) to the location of the installed *cpprestsdk-config.cmake* - *Either* set ``websocketpp_DIR`` (PATH) to the location of the installed *websocketpp-config.cmake* - *Or* set ``WEBSOCKETPP_INCLUDE_DIR`` (PATH) to the location of the WebSocket++ include files, e.g. *````*``/cpprestsdk/Release/libs/websocketpp`` to use the copy within the C++ REST SDK source tree + - Set flags to use the supplied Modern C++ JSON schema validator and jwt-cpp libraries + - Set ``NMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR`` (BOOL) to ``1`` (true) + - Set ``NMOS_CPP_USE_SUPPLIED_JWT_CPP`` (BOOL) to ``1`` (true) 3. Use CMake to generate build/project files, and then build @@ -49,7 +52,8 @@ Cache Variable | Default | Description -|-|- `NMOS_CPP_BUILD_EXAMPLES` | `ON` | Build example applications `NMOS_CPP_BUILD_TESTS` | `ON` | Build test suite application -`NMOS_CPP_USE_CONAN` | `ON` | Use Conan to acquire dependencies +`NMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR` | `OFF` | Use supplied third_party/nlohmann +`NMOS_CPP_USE_SUPPLIED_JWT_CPP` | `OFF` | Use supplied third_party/jwt-cpp `NMOS_CPP_USE_AVAHI` | `ON` | Use Avahi compatibility library rather than mDNSResponder **Windows** @@ -61,7 +65,8 @@ mkdir build cd build cmake .. ^ -G "Visual Studio 16 2019" ^ - -DCMAKE_CONFIGURATION_TYPES:STRING="Debug;Release" + -DCMAKE_CONFIGURATION_TYPES:STRING="Debug;Release" ^ + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES:STRING="third_party/cmake/conan_provider.cmake" ```
@@ -77,7 +82,9 @@ cmake .. ^ -DBoost_USE_STATIC_LIBS:BOOL="1" ^ -DBOOST_INCLUDEDIR:PATH="/boost_1_83_0" ^ -DBOOST_LIBRARYDIR:PATH="/boost_1_83_0/x64/lib" ^ - -DWEBSOCKETPP_INCLUDE_DIR:PATH="/cpprestsdk/Release/libs/websocketpp" + -DWEBSOCKETPP_INCLUDE_DIR:PATH="/cpprestsdk/Release/libs/websocketpp" ^ + -DNMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR:BOOL="1" ^ + -DNMOS_CPP_USE_SUPPLIED_JWT_CPP:BOOL="1" ```
@@ -97,7 +104,8 @@ cd /nmos-cpp/Development mkdir build cd build cmake .. \ - -DCMAKE_BUILD_TYPE:STRING="" + -DCMAKE_BUILD_TYPE:STRING="" \ + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES:STRING="third_party/cmake/conan_provider.cmake" make ``` @@ -110,7 +118,9 @@ mkdir build cd build cmake .. \ -DCMAKE_BUILD_TYPE:STRING="" \ - -DWEBSOCKETPP_INCLUDE_DIR:PATH="/cpprestsdk/Release/libs/websocketpp" + -DWEBSOCKETPP_INCLUDE_DIR:PATH="/cpprestsdk/Release/libs/websocketpp" \ + -DNMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR:BOOL="1" \ + -DNMOS_CPP_USE_SUPPLIED_JWT_CPP:BOOL="1" make ``` diff --git a/Documents/Raspberry-Pi.md b/Documents/Raspberry-Pi.md index 74a521fc7..4f6cae53f 100644 --- a/Documents/Raspberry-Pi.md +++ b/Documents/Raspberry-Pi.md @@ -217,9 +217,10 @@ cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_TOOLCHAIN_FILE=${RPI_ROOT}/Toolchain-rpi.cmake \ -DWEBSOCKETPP_INCLUDE_DIR:PATH="~/cpprestsdk/Release/libs/websocketpp" \ + -DNMOS_CPP_USE_SUPPLIED_JSON_SCHEMA_VALIDATOR=1 \ + -DNMOS_CPP_USE_SUPPLIED_JWT_CPP=1 \ -DNMOS_CPP_USE_AVAHI=1 \ - -DAvahi_INCLUDE_DIR=${RPI_LIBS}/include/avahi-compat-libdns_sd \ - -DNMOS_CPP_USE_CONAN=0 + -DAvahi_INCLUDE_DIR=${RPI_LIBS}/include/avahi-compat-libdns_sd # Cross-compile the library. make diff --git a/README.md b/README.md index 1ff32210c..4819d8b7b 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ After setting up the dependencies, follow these [instructions](Documents/Getting Next, try out the registry and node applications in the [tutorial](Documents/Tutorial.md). -An [nmos-cpp Conan package](https://conan.io/center/nmos-cpp) is published at Conan Center Index. +An [nmos-cpp Conan package](https://conan.io/center/recipes/nmos-cpp) is occasionally published at Conan Center Index. ## Agile Development @@ -116,6 +116,7 @@ The implementation is designed to be extended. Development is ongoing, following Recent activity on the project (newest first): +- Update to Conan 2; Conan 1.X is no longer supported - Added support for IS-10 Authorization - Added support for HSTS and OCSP stapling - Added support for BCP-006-01 v1.0-dev, which can be demonstrated with **nmos-cpp-node** by using `"video_type": "video/jxsv"` From 62a91659e38828309dc00b32cec55f52d42de0d9 Mon Sep 17 00:00:00 2001 From: Gareth Sylvester-Bradley <31761158+garethsb@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:35:02 +0000 Subject: [PATCH 2/3] Discovery mode revisited (#374) * Remove commented out code * Replace discovery_mode with sneakily stashing Host header in the web::uri --- Development/nmos-cpp-node/config.json | 3 - Development/nmos/authorization_operation.cpp | 2 +- Development/nmos/authorization_state.h | 2 +- Development/nmos/client_utils.cpp | 20 +++++ Development/nmos/client_utils.h | 6 ++ Development/nmos/mdns.cpp | 84 +++++--------------- Development/nmos/node_behaviour.cpp | 14 ++-- Development/nmos/node_system_behaviour.cpp | 2 +- Development/nmos/ocsp_behaviour.cpp | 2 +- Development/nmos/settings.h | 3 - 10 files changed, 60 insertions(+), 78 deletions(-) diff --git a/Development/nmos-cpp-node/config.json b/Development/nmos-cpp-node/config.json index c3367a9d4..95b3362ec 100644 --- a/Development/nmos-cpp-node/config.json +++ b/Development/nmos-cpp-node/config.json @@ -235,9 +235,6 @@ // proxy_port [registry, node]: forward proxy port //"proxy_port": 8080, - // discovery_mode [node]: whether the discovered host name (1) or resolved addresses (2) are used to construct request URLs for Registration APIs or System APIs - //"discovery_mode": 1, - // href_mode [registry, node]: whether the host name (1), addresses (2) or both (3) are used to construct response headers, and host and URL fields in the data model //"href_mode": 1, diff --git a/Development/nmos/authorization_operation.cpp b/Development/nmos/authorization_operation.cpp index aab25170c..338c73d77 100644 --- a/Development/nmos/authorization_operation.cpp +++ b/Development/nmos/authorization_operation.cpp @@ -1200,7 +1200,7 @@ namespace nmos const auto service = top_authorization_service(model.settings); const auto auth_uri = service.second; - client.reset(new web::http::client::http_client(auth_uri, make_authorization_http_client_config(model.settings, load_ca_certificates, gate))); + client = nmos::details::make_http_client(auth_uri, make_authorization_http_client_config(model.settings, load_ca_certificates, gate)); auto token = cancellation_source.get_token(); diff --git a/Development/nmos/authorization_state.h b/Development/nmos/authorization_state.h index 2bd57db4d..74f704ad4 100644 --- a/Development/nmos/authorization_state.h +++ b/Development/nmos/authorization_state.h @@ -28,7 +28,7 @@ namespace nmos , immediate(true) , received(false) {} - pubkeys_shared_state(web::http::client::http_client client, nmos::api_version version, web::uri issuer)//, bool one_shot = false) + pubkeys_shared_state(web::http::client::http_client client, nmos::api_version version, web::uri issuer) : engine(seeder) , client(std::unique_ptr(new web::http::client::http_client(client))) , version(std::move(version)) diff --git a/Development/nmos/client_utils.cpp b/Development/nmos/client_utils.cpp index 393c094c9..bb3282bbd 100644 --- a/Development/nmos/client_utils.cpp +++ b/Development/nmos/client_utils.cpp @@ -256,6 +256,26 @@ namespace nmos return config; } + namespace details + { + // make a client for the specified base_uri and config, with Host header sneakily stashed in user info + std::unique_ptr make_http_client(const web::uri& base_uri, const web::http::client::http_client_config& client_config) + { + // unstash the Host header + // cf. nmos::details::resolve_service + const auto& host_name = base_uri.user_info(); + std::unique_ptr client(new web::http::client::http_client(web::uri_builder(base_uri).set_user_info({}).to_uri(), client_config)); + if (!host_name.empty()) + { + client->add_handler([host_name](web::http::http_request request, std::shared_ptr next_stage) -> pplx::task + { + request.headers().add(web::http::header_names::host, host_name); + return next_stage->propagate(request); + }); + } + return client; + } + } // make a request with logging pplx::task api_request(web::http::client::http_client client, web::http::http_request request, slog::base_gate& gate, const pplx::cancellation_token& token) diff --git a/Development/nmos/client_utils.h b/Development/nmos/client_utils.h index 284846c8a..d702f4bd3 100644 --- a/Development/nmos/client_utils.h +++ b/Development/nmos/client_utils.h @@ -31,6 +31,12 @@ namespace nmos web::websockets::client::websocket_client_config make_websocket_client_config(const nmos::settings& settings, load_ca_certificates_handler load_ca_certificates, nmos::experimental::get_authorization_bearer_token_handler get_authorization_bearer_token, slog::base_gate& gate); web::websockets::client::websocket_client_config make_websocket_client_config(const nmos::settings& settings, load_ca_certificates_handler load_ca_certificates, slog::base_gate& gate); + namespace details + { + // make a client for the specified base_uri and config, with Host header sneakily stashed in user info + std::unique_ptr make_http_client(const web::uri& base_uri_with_host_name_in_user_info, const web::http::client::http_client_config& client_config); + } + // make an API request with logging pplx::task api_request(web::http::client::http_client client, web::http::http_request request, slog::base_gate& gate, const pplx::cancellation_token& token = pplx::cancellation_token::none()); pplx::task api_request(web::http::client::http_client client, const web::http::method& mtd, slog::base_gate& gate, const pplx::cancellation_token& token = pplx::cancellation_token::none()); diff --git a/Development/nmos/mdns.cpp b/Development/nmos/mdns.cpp index ab4b25177..91e4549c3 100644 --- a/Development/nmos/mdns.cpp +++ b/Development/nmos/mdns.cpp @@ -495,44 +495,11 @@ namespace nmos update_service(advertiser, service, domain, settings, std::move(add_records)); } - enum discovery_mode - { - discovery_mode_default = 0, - discovery_mode_name = 1, - discovery_mode_addresses = 2 - }; - namespace details { typedef std::vector resolved_services; - std::vector get_resolved_hosts(const mdns::resolve_result& resolved, const nmos::service_protocol& resolved_proto, discovery_mode mode) - { - std::vector results; - - // by default, use the host name if secure communications are in use - if (mode == discovery_mode_name || (mode == discovery_mode_default && is_service_protocol_secure(resolved_proto))) - { - auto host_name = utility::s2us(resolved.host_name); - // remove a trailing '.' to turn an FQDN into a DNS name, for SSL certificate matching - // hmm, this might be more appropriately done by tweaking the Host header in the client request? - if (!host_name.empty() && U('.') == host_name.back()) host_name.pop_back(); - - results.push_back(host_name); - } - - if (mode == discovery_mode_addresses || (mode == discovery_mode_default && !is_service_protocol_secure(resolved_proto))) - { - for (const auto& ip_address : resolved.ip_addresses) - { - results.push_back(utility::s2us(ip_address)); - } - } - - return results; - } - - pplx::task resolve_service(std::shared_ptr results, mdns::service_discovery& discovery, discovery_mode discovery_mode, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, const std::chrono::steady_clock::time_point& timeout, const pplx::cancellation_token& token) + pplx::task resolve_service(std::shared_ptr results, mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, const std::chrono::steady_clock::time_point& timeout, const pplx::cancellation_token& token) { return discovery.browse([=, &discovery](const mdns::browse_result& resolving) { @@ -588,12 +555,17 @@ namespace nmos .set_path(U("/x-nmos/") + utility::s2us(details::service_api(service))); } - auto resolved_hosts = get_resolved_hosts(resolved, resolved_proto, discovery_mode); + auto host_name = utility::s2us(resolved.host_name); + // remove a trailing '.' to turn an FQDN into a DNS name, for SSL certificate matching + if (!host_name.empty() && U('.') == host_name.back()) host_name.pop_back(); - for (const auto& host : resolved_hosts) + for (const auto& ip_address : resolved.ip_addresses) { + // sneakily stash the Host header in user info + // cf. nmos::details::make_http_client results->push_back({ { *resolved_ver, resolved_pri }, resolved_uri - .set_host(host) + .set_user_info(host_name) + .set_host(utility::s2us(ip_address)) .to_uri() }); } @@ -618,7 +590,9 @@ namespace nmos } } - pplx::task> resolve_service_(mdns::service_discovery& discovery, discovery_mode mode, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token) + // helper function for resolving instances of the specified service (API) + // with the highest version, highest priority instances at the front, and optionally services with the same priority ordered randomly + pplx::task> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token) { const auto absolute_timeout = std::chrono::steady_clock::now() + timeout; @@ -646,8 +620,8 @@ namespace nmos }; const std::vector> both_tasks{ - details::resolve_service(both_results[0], discovery, mode, nmos::service_types::register_, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, linked_token), - details::resolve_service(both_results[1], discovery, mode, service, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, linked_token) + details::resolve_service(both_results[0], discovery, nmos::service_types::register_, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, linked_token), + details::resolve_service(both_results[1], discovery, service, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, linked_token) }; // when either task is completed, cancel and wait for the other to be completed @@ -675,12 +649,12 @@ namespace nmos } else { - resolve_task = details::resolve_service(results, discovery, mode, nmos::service_types::register_, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, token); + resolve_task = details::resolve_service(results, discovery, nmos::service_types::register_, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, token); } } else { - resolve_task = details::resolve_service(results, discovery, mode, service, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, token); + resolve_task = details::resolve_service(results, discovery, service, browse_domain, api_ver, priorities, api_proto, api_auth, absolute_timeout, token); } return resolve_task.then([results, randomize](bool) @@ -722,9 +696,11 @@ namespace nmos }); } - pplx::task> resolve_service(mdns::service_discovery& discovery, discovery_mode mode, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token) + // helper function for resolving instances of the specified service (API) + // with the highest version, highest priority instances at the front, and optionally services with the same priority ordered randomly + pplx::task> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token) { - return resolve_service_(discovery, mode, service, browse_domain, api_ver, priorities, api_proto, api_auth, randomize, timeout, token).then([](std::list resolved_services) + return resolve_service_(discovery, service, browse_domain, api_ver, priorities, api_proto, api_auth, randomize, timeout, token).then([](std::list resolved_services) { // add the version to each uri return boost::copy_range>(resolved_services | boost::adaptors::transformed([](const resolved_service& s) @@ -734,18 +710,10 @@ namespace nmos }); } - // helper function for resolving instances of the specified service (API) - // with the highest version, highest priority instances at the front, and (by default) services with the same priority ordered randomly - pplx::task> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token) - { - return resolve_service(discovery, discovery_mode_default, service, browse_domain, api_ver, priorities, api_proto, api_auth, randomize, timeout, token); - } - // helper function for resolving instances of the specified service (API) based on the specified settings // with the highest version, highest priority instances at the front, and services with the same priority ordered randomly pplx::task> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const nmos::settings& settings, const pplx::cancellation_token& token) { - const auto mode = discovery_mode(nmos::experimental::fields::discovery_mode(settings)); const auto browse_domain = utility::us2s(nmos::get_domain(settings)); const auto versions = details::service_versions(service, settings); const auto priorities = details::service_priorities(service, settings); @@ -756,21 +724,13 @@ namespace nmos // when no cancellation token is specified const auto timeout = token.is_cancelable() ? nmos::fields::discovery_backoff_max(settings) : 1; - return resolve_service(discovery, mode, service, browse_domain, versions, priorities, protocols, authorization, true, std::chrono::seconds(timeout), token); - } - - // helper function for resolving instances of the specified service (API) - // with the highest version, highest priority instances at the front, and (by default) services with the same priority ordered randomly - pplx::task> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token) - { - return resolve_service_(discovery, discovery_mode_default, service, browse_domain, api_ver, priorities, api_proto, api_auth, randomize, timeout, token); + return resolve_service(discovery, service, browse_domain, versions, priorities, protocols, authorization, true, std::chrono::duration_cast(std::chrono::seconds(timeout)), token); } // helper function for resolving instances of the specified service (API) based on the specified settings // with the highest version, highest priority instances at the front, and services with the same priority ordered randomly pplx::task> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const nmos::settings& settings, const pplx::cancellation_token& token) { - const auto mode = discovery_mode(nmos::experimental::fields::discovery_mode(settings)); const auto browse_domain = utility::us2s(nmos::get_domain(settings)); const auto versions = details::service_versions(service, settings); const auto priorities = details::service_priorities(service, settings); @@ -781,7 +741,7 @@ namespace nmos // when no cancellation token is specified const auto timeout = token.is_cancelable() ? nmos::fields::discovery_backoff_max(settings) : 1; - return resolve_service_(discovery, mode, service, browse_domain, versions, priorities, protocols, authorization, true, std::chrono::seconds(timeout), token); + return resolve_service_(discovery, service, browse_domain, versions, priorities, protocols, authorization, true, std::chrono::duration_cast(std::chrono::seconds(timeout)), token); } } } diff --git a/Development/nmos/node_behaviour.cpp b/Development/nmos/node_behaviour.cpp index 6b0c7a26b..85ba40e0d 100644 --- a/Development/nmos/node_behaviour.cpp +++ b/Development/nmos/node_behaviour.cpp @@ -760,7 +760,8 @@ namespace nmos grain.updated = strictly_increasing_update(resources); }); - registration_client.reset(new web::http::client::http_client(base_uri, make_registration_client_config(model.settings, load_ca_certificates, get_authorization_bearer_token ? get_authorization_bearer_token() : web::http::oauth2::experimental::oauth2_token{}, gate))); + const auto bearer_token = get_authorization_bearer_token ? get_authorization_bearer_token() : web::http::oauth2::experimental::oauth2_token{}; + registration_client = nmos::details::make_http_client(base_uri, make_registration_client_config(model.settings, load_ca_certificates, bearer_token, gate)); } events = web::json::value::array(); @@ -901,8 +902,8 @@ namespace nmos if (registry_version != grain->version) break; const auto bearer_token = get_authorization_bearer_token ? get_authorization_bearer_token() : web::http::oauth2::experimental::oauth2_token{}; - registration_client.reset(new web::http::client::http_client(base_uri, make_registration_client_config(model.settings, load_ca_certificates, bearer_token, gate))); - heartbeat_client.reset(new web::http::client::http_client(base_uri, make_heartbeat_client_config(model.settings, load_ca_certificates, bearer_token, gate))); + registration_client = nmos::details::make_http_client(base_uri, make_registration_client_config(model.settings, load_ca_certificates, bearer_token, gate)); + heartbeat_client = nmos::details::make_http_client(base_uri, make_heartbeat_client_config(model.settings, load_ca_certificates, bearer_token, gate)); // "The first interaction with a new Registration API [after a server side or connectivity issue] // should be a heartbeat to confirm whether whether the Node is still present in the registry" @@ -946,6 +947,7 @@ namespace nmos { heartbeat_time = std::chrono::steady_clock::now(); + // renew heartbeat_client if bearer token has changed if (get_authorization_bearer_token) { const auto& bearer_token = get_authorization_bearer_token(); @@ -954,7 +956,7 @@ namespace nmos slog::log(gate, SLOG_FLF) << "Update heartbeat client with new authorization token"; heartbeat_bearer_token = bearer_token; - heartbeat_client.reset(new web::http::client::http_client(base_uri, make_heartbeat_client_config(model.settings, load_ca_certificates, bearer_token, gate))); + heartbeat_client = nmos::details::make_http_client(base_uri, make_heartbeat_client_config(model.settings, load_ca_certificates, bearer_token, gate)); } } @@ -1003,7 +1005,7 @@ namespace nmos auto token = cancellation_source.get_token(); - // renew regsitration_client if bearer token has changed + // renew registration_client if bearer token has changed if (get_authorization_bearer_token) { const auto& bearer_token = get_authorization_bearer_token(); @@ -1012,7 +1014,7 @@ namespace nmos slog::log(gate, SLOG_FLF) << "Update registration client with new authorization token"; registration_bearer_token = bearer_token; - registration_client.reset(new web::http::client::http_client(registration_client->base_uri(), make_registration_client_config(model.settings, load_ca_certificates, bearer_token, gate))); + registration_client = nmos::details::make_http_client(registration_client->base_uri(), make_registration_client_config(model.settings, load_ca_certificates, bearer_token, gate)); } } diff --git a/Development/nmos/node_system_behaviour.cpp b/Development/nmos/node_system_behaviour.cpp index 2e215824b..bc29d0da5 100644 --- a/Development/nmos/node_system_behaviour.cpp +++ b/Development/nmos/node_system_behaviour.cpp @@ -396,7 +396,7 @@ namespace nmos if (!state.client) { const auto base_uri = top_system_service(model.settings); - state.client.reset(new web::http::client::http_client(base_uri, make_system_client_config(model.settings, load_ca_certificates, gate))); + state.client = nmos::details::make_http_client(base_uri, make_system_client_config(model.settings, load_ca_certificates, gate)); } auto token = cancellation_source.get_token(); diff --git a/Development/nmos/ocsp_behaviour.cpp b/Development/nmos/ocsp_behaviour.cpp index f16140be6..a835d5937 100644 --- a/Development/nmos/ocsp_behaviour.cpp +++ b/Development/nmos/ocsp_behaviour.cpp @@ -347,7 +347,7 @@ namespace nmos { const auto ocsp_uri = ocsp_uris.front(); const auto secure = web::is_secure_uri_scheme(ocsp_uri.scheme()); - state.client.reset(new web::http::client::http_client(ocsp_uri, make_ocsp_client_config(secure, model.settings, state.load_ca_certificates, gate))); + state.client = nmos::details::make_http_client(ocsp_uri, make_ocsp_client_config(secure, model.settings, state.load_ca_certificates, gate)); } auto token = cancellation_source.get_token(); diff --git a/Development/nmos/settings.h b/Development/nmos/settings.h index b046a0293..300ee7295 100644 --- a/Development/nmos/settings.h +++ b/Development/nmos/settings.h @@ -294,9 +294,6 @@ namespace nmos // proxy_port [registry, node]: forward proxy port const web::json::field_as_integer_or proxy_port{ U("proxy_port"), 8080 }; - // discovery_mode [node]: whether the discovered host name (1) or resolved addresses (2) are used to construct request URLs for Registration APIs or System APIs - const web::json::field_as_integer_or discovery_mode{ U("discovery_mode"), 0 }; // when omitted, a default heuristic is used - // href_mode [registry, node]: whether the host name (1), addresses (2) or both (3) are used to construct response headers, and host and URL fields in the data model const web::json::field_as_integer_or href_mode{ U("href_mode"), 0 }; // when omitted, a default heuristic is used From 638d40a2f296eba9cd47160212c0d511db3d5da9 Mon Sep 17 00:00:00 2001 From: Gareth Sylvester-Bradley Date: Tue, 20 Feb 2024 16:53:34 +0000 Subject: [PATCH 3/3] Add port to the Host header --- Development/nmos/client_utils.cpp | 16 ++++++++++------ Development/nmos/client_utils.h | 2 +- Development/nmos/mdns.cpp | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Development/nmos/client_utils.cpp b/Development/nmos/client_utils.cpp index bb3282bbd..ccf6c086c 100644 --- a/Development/nmos/client_utils.cpp +++ b/Development/nmos/client_utils.cpp @@ -258,18 +258,22 @@ namespace nmos namespace details { - // make a client for the specified base_uri and config, with Host header sneakily stashed in user info + // make a client for the specified base_uri and config, with host name for the Host header sneakily stashed in user info std::unique_ptr make_http_client(const web::uri& base_uri, const web::http::client::http_client_config& client_config) { - // unstash the Host header + // unstash the host name for the Host header // cf. nmos::details::resolve_service - const auto& host_name = base_uri.user_info(); std::unique_ptr client(new web::http::client::http_client(web::uri_builder(base_uri).set_user_info({}).to_uri(), client_config)); - if (!host_name.empty()) + if (!base_uri.user_info().empty()) { - client->add_handler([host_name](web::http::http_request request, std::shared_ptr next_stage) -> pplx::task + auto host = base_uri.user_info(); + if (base_uri.port() > 0) { - request.headers().add(web::http::header_names::host, host_name); + host.append(U(":")).append(utility::conversions::details::to_string_t(base_uri.port())); + } + client->add_handler([host](web::http::http_request request, std::shared_ptr next_stage) -> pplx::task + { + request.headers().add(web::http::header_names::host, host); return next_stage->propagate(request); }); } diff --git a/Development/nmos/client_utils.h b/Development/nmos/client_utils.h index d702f4bd3..c1f83e3f7 100644 --- a/Development/nmos/client_utils.h +++ b/Development/nmos/client_utils.h @@ -33,7 +33,7 @@ namespace nmos namespace details { - // make a client for the specified base_uri and config, with Host header sneakily stashed in user info + // make a client for the specified base_uri and config, with host name for the Host header sneakily stashed in user info std::unique_ptr make_http_client(const web::uri& base_uri_with_host_name_in_user_info, const web::http::client::http_client_config& client_config); } diff --git a/Development/nmos/mdns.cpp b/Development/nmos/mdns.cpp index 91e4549c3..2549e5f43 100644 --- a/Development/nmos/mdns.cpp +++ b/Development/nmos/mdns.cpp @@ -561,7 +561,7 @@ namespace nmos for (const auto& ip_address : resolved.ip_addresses) { - // sneakily stash the Host header in user info + // sneakily stash the host name for the Host header in user info // cf. nmos::details::make_http_client results->push_back({ { *resolved_ver, resolved_pri }, resolved_uri .set_user_info(host_name)