From b53df58c5e1591b95dd4bb6fb7d85d649685de8e Mon Sep 17 00:00:00 2001 From: Aliaksandr Adziareika <8034372+alexadereyko@users.noreply.github.com> Date: Sat, 24 Oct 2026 12:19:55 +0200 Subject: [PATCH 01/44] Make the bundle's stagings available by FetchLayer, prebuilt or from sources --- CMakeLists.txt | 20 +- CMakePresets.json | 61 +++++- cmake/FetchLayer.cmake | 305 +++++++++++++++++++++++++++ cmake/FetchLayer/axis.cmake | 49 +++++ cmake/FetchLayer/compile.cmake | 44 ++++ cmake/FetchLayer/git.cmake | 161 ++++++++++++++ cmake/FetchLayer/identity.cmake | 76 +++++++ cmake/FetchLayer/import.cmake | 96 +++++++++ cmake/FetchLayer/instance.cmake | 43 ++++ cmake/FetchLayer/layer.cmake | 37 ++++ cmake/FetchLayer/policy.cmake | 86 ++++++++ cmake/FetchLayer/staging.cmake | 32 +++ cmake/FetchLayer/system.cmake | 81 +++++++ cmake/FetchLayer/variant.cmake | 146 +++++++++++++ cmake/Layers.cmake | 124 +++++++++++ cmake/Packing.cmake | 67 ++++++ cmake/PackingProject.cmake | 39 ++++ tests/axis.cmake | 77 +++++++ tests/fetchlayer-axes/CMakeLists.txt | 77 +++++++ tests/fetchlayer-git/CMakeLists.txt | 100 +++++++++ tests/fetchlayer/CMakeLists.txt | 49 +++++ tests/fetchlayer/smoke.cpp | 12 ++ tests/identity.cmake | 84 ++++++++ tools/fill-store.cmake | 161 ++++++++++++++ 24 files changed, 2024 insertions(+), 3 deletions(-) create mode 100644 cmake/FetchLayer.cmake create mode 100644 cmake/FetchLayer/axis.cmake create mode 100644 cmake/FetchLayer/compile.cmake create mode 100644 cmake/FetchLayer/git.cmake create mode 100644 cmake/FetchLayer/identity.cmake create mode 100644 cmake/FetchLayer/import.cmake create mode 100644 cmake/FetchLayer/instance.cmake create mode 100644 cmake/FetchLayer/layer.cmake create mode 100644 cmake/FetchLayer/policy.cmake create mode 100644 cmake/FetchLayer/staging.cmake create mode 100644 cmake/FetchLayer/system.cmake create mode 100644 cmake/FetchLayer/variant.cmake create mode 100644 cmake/Layers.cmake create mode 100644 cmake/Packing.cmake create mode 100644 cmake/PackingProject.cmake create mode 100644 tests/axis.cmake create mode 100644 tests/fetchlayer-axes/CMakeLists.txt create mode 100644 tests/fetchlayer-git/CMakeLists.txt create mode 100644 tests/fetchlayer/CMakeLists.txt create mode 100644 tests/fetchlayer/smoke.cpp create mode 100644 tests/identity.cmake create mode 100644 tools/fill-store.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d16b94..b7bee14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,15 +18,31 @@ option(${REPO_OPTION_PREFIX}_ENABLE_ASAM_CMP_MODULE "Enable ASAM-CMP modules" ON include(FetchContent) -# fetch the cmake utilities first, as they are used by the other modules +# the cmake utilities first: the triplet, Boost, and what the layers built here use; the tag the core takes FetchContent_Declare( opendaq-cmake-utils GIT_REPOSITORY https://github.com/openDAQ/opendaq-cmake-utils.git - GIT_TAG v1.0.1 + GIT_TAG v1.1.3 GIT_PROGRESS ON ) FetchContent_MakeAvailable(opendaq-cmake-utils) +# The bundle by FetchLayer: the stagings named, from where the policy says; prebuilt only for now, the source +# build below stays until FetchLayer compiles +set(BUNDLE_STAGINGS "" CACHE STRING "The stagings to make available, dependencies first; empty: the source build below") +if (BUNDLE_STAGINGS) + include(cmake/FetchLayer.cmake) + # the identity keys are the annotations staging-push writes + set(FETCHLAYER_IDENTITY_REVISION_KEY "org.opencontainers.image.revision") + set(FETCHLAYER_IDENTITY_DEPENDENCY_KEY "com.opendaq.dependencies..revision") + set(FETCHLAYER_IDENTITY_AXIS_KEY "com.opendaq.package.") + + include(cmake/Layers.cmake) + FetchLayer_MakeAvailable(${BUNDLE_STAGINGS}) + include(cmake/Packing.cmake) + return() +endif() + FetchContent_Declare( opendaq GIT_REPOSITORY https://github.com/openDAQ/openDAQ.git diff --git a/CMakePresets.json b/CMakePresets.json index ac83377..2e8e218 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,5 +1,5 @@ { - "version": 4, + "version": 6, "configurePresets": [ { "name": "debug", @@ -100,6 +100,31 @@ "msvc-x86" ] }, + { + "name": "revisions", + "hidden": true, + "description": "The bundle: one commit per layer", + "cacheVariables": { + "BUNDLE_REVISION_OPENDAQ": "737a0e22a6cdfc56aec8a88a1dd4cc58379f0e03", + "BUNDLE_REVISION_OPCUAMODULES": "485710c37d06bcc6b6c852e57fd27c0b2cce7d08", + "BUNDLE_REVISION_MQTTSTREAMINGMODULE": "3a55f4bf710ca10513ed64464e74680246e35e9f", + "BUNDLE_REVISION_LTSTREAMINGMODULESMODERN": "322d770b08e033eb6033d417c2310fdb832e088a", + "BUNDLE_REVISION_ASAM-CMP-MODULES": "a95b8cea31cca2c5e437c7e55d38bc9efc7703a6" + } + }, + { + "name": "bundle-native", + "displayName": "Bundle by FetchLayer, this machine's triplet", + "inherits": [ + "release", + "ninja", + "revisions" + ], + "cacheVariables": { + "BUNDLE_STAGINGS": "opendaq;opcua-modules;mqtt-module;lt-streaming-modules;asam-cmp-modules", + "FETCHLAYER_STORE": "${sourceDir}/build/store" + } + }, { "name": "x64/msvc-17", "displayName": "[MSVC 17] Release - x64", @@ -163,5 +188,39 @@ "ninja" ] } + ], + "buildPresets": [ + { + "name": "bundle-native", + "configurePreset": "bundle-native" + } + ], + "packagePresets": [ + { + "name": "core", + "displayName": "The core: runtime, C++ API, C", + "configurePreset": "bundle-native", + "generators": [ + "TGZ" + ], + "variables": { + "BUNDLE_PACK_STAGINGS": "opendaq", + "BUNDLE_PACK_COMPONENTS": "Runtime;Development", + "CPACK_OPENDAQ_META_PACKAGE_NAME": "opendaq-core" + } + }, + { + "name": "modules", + "displayName": "The modules, without the core", + "configurePreset": "bundle-native", + "generators": [ + "TGZ" + ], + "variables": { + "BUNDLE_PACK_STAGINGS": "opcua-modules;mqtt-module;lt-streaming-modules;asam-cmp-modules", + "BUNDLE_PACK_COMPONENTS": "Runtime", + "CPACK_OPENDAQ_META_PACKAGE_NAME": "opendaq-modules" + } + } ] } diff --git a/cmake/FetchLayer.cmake b/cmake/FetchLayer.cmake new file mode 100644 index 0000000..367a926 --- /dev/null +++ b/cmake/FetchLayer.cmake @@ -0,0 +1,305 @@ +## +## FetchLayer — FetchContent for layers, its sources swapped for prebuilt stagings where they exist. A layer is a +## repository at a revision; a staging is what a layer gives as one whole, with its own dependencies and identity. +## Stagings are made available, for one or more triplets, from where the policy says — prebuilt (System, later +## OCI) or built from their layer's sources (Git). +## +## FetchLayer_Declare( REVISION […]) +## FetchLayer_DeclareStaging( LAYER [AXES …] [DEPENDS …] [FIND_PACKAGE_ARGS …]) +## FetchLayer_DeclareAxis( NATIVE [VALUES …]) +## FetchLayer_MakeAvailable(…) +## FetchLayer_GetProperties( [VARIANT | AXES =…]) +## +## Declare records a Layer, DeclareStaging a Staging of a layer declared before it, DeclareAxis a row of the +## axes table; the first declaration of a name wins, a layer's and a staging's taken without case. +## MakeAvailable makes an Instance of each variant of each staging, in the order given — the variants the axes +## table names, each combination of the VALUES of the staging's axes: the policy, resolve, populate, integrate; +## a staging's DEPENDS must be available first. It sets nothing in the caller's scope. GetProperties copies one +## Instance into _* in the caller's scope. +## +## The axis triplet, when nothing declared it, is declared from FETCHLAYER_TRIPLET, the triplet this build is +## configured for. Only this build's own variant — each axis at its NATIVE value — is built from sources and +## installed; another comes prebuilt, files only. A variable for one layer or staging ends in its name +## upper-cased, as FetchContent's do (FETCHLAYER_PREFIX_OPCUA-MODULES, FETCHCONTENT_SOURCE_DIR_OPCUAMODULES). +## + +include_guard(GLOBAL) + +set(_FETCHLAYER_DIR "${CMAKE_CURRENT_LIST_DIR}/FetchLayer") +include("${_FETCHLAYER_DIR}/layer.cmake") +include("${_FETCHLAYER_DIR}/staging.cmake") +include("${_FETCHLAYER_DIR}/axis.cmake") +include("${_FETCHLAYER_DIR}/variant.cmake") +include("${_FETCHLAYER_DIR}/instance.cmake") +include("${_FETCHLAYER_DIR}/identity.cmake") +include("${_FETCHLAYER_DIR}/policy.cmake") +include("${_FETCHLAYER_DIR}/system.cmake") +include("${_FETCHLAYER_DIR}/git.cmake") +include("${_FETCHLAYER_DIR}/import.cmake") +include("${_FETCHLAYER_DIR}/compile.cmake") + +set(FETCHLAYER_TRIPLET "" CACHE STRING "The triplet this build is configured for") +set(FETCHLAYER_STORE "" CACHE PATH "The store of prebuilt stagings: //idx-/") + + +# === FetchLayer_Declare ====================================================================================== + +# FetchLayer_Declare( REVISION […]) +# Records the layer; the first declaration of a name wins +function(FetchLayer_Declare NAME) + __FetchLayer_layer_get("${NAME}" REVISION _known) + if (_known) + return() + endif() + + cmake_parse_arguments(PARSE_ARGV 1 _arg "" "REVISION" "DEPENDS;FIND_PACKAGE_ARGS") + if (NOT _arg_REVISION) + message(FATAL_ERROR "FetchLayer_Declare(${NAME}): REVISION is required") + endif() + if (DEFINED _arg_DEPENDS OR DEFINED _arg_FIND_PACKAGE_ARGS) + message(FATAL_ERROR "FetchLayer_Declare(${NAME}): DEPENDS and FIND_PACKAGE_ARGS are a staging's, " + "FetchLayer_DeclareStaging( LAYER ${NAME} …)") + endif() + + __FetchLayer_layer_set("${NAME}" REVISION "${_arg_REVISION}") + __FetchLayer_layer_set("${NAME}" ARGUMENTS "${_arg_UNPARSED_ARGUMENTS}") +endfunction() + +# FetchLayer_DeclareStaging( LAYER [AXES …] [DEPENDS …] [FIND_PACKAGE_ARGS …]) +# Records the staging of a layer declared before it; the first declaration of a name wins. AXES are what it has +# variants by: triplet when not given, none when given empty; they are checked when it is made available, so +# an axis may be declared after it +function(FetchLayer_DeclareStaging NAME) + __FetchLayer_staging_get("${NAME}" LAYER _known) + if (_known) + return() + endif() + + cmake_parse_arguments(PARSE_ARGV 1 _arg "" "LAYER" "AXES;DEPENDS;FIND_PACKAGE_ARGS") + if (_arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "FetchLayer_DeclareStaging(${NAME}): unknown arguments ${_arg_UNPARSED_ARGUMENTS}") + endif() + __FetchLayer_layer_get("${_arg_LAYER}" REVISION _layer_known) + if (NOT _arg_LAYER OR NOT _layer_known) + message(FATAL_ERROR "FetchLayer_DeclareStaging(${NAME}): LAYER must name a layer declared before it") + endif() + + set(_axes triplet) + if (DEFINED _arg_AXES OR "AXES" IN_LIST _arg_KEYWORDS_MISSING_VALUES) + set(_axes ${_arg_AXES}) + endif() + + __FetchLayer_staging_set("${NAME}" LAYER "${_arg_LAYER}") + __FetchLayer_staging_set("${NAME}" AXES "${_axes}") + __FetchLayer_staging_set("${NAME}" DEPENDS "${_arg_DEPENDS}") + __FetchLayer_staging_set("${NAME}" FIND_PACKAGE_ARGS "${_arg_FIND_PACKAGE_ARGS}") + __FetchLayer_layer_get("${_arg_LAYER}" STAGINGS _stagings) + list(APPEND _stagings "${NAME}") + __FetchLayer_layer_set("${_arg_LAYER}" STAGINGS "${_stagings}") +endfunction() + + +# FetchLayer_DeclareAxis( NATIVE [VALUES …]) +# Adds the row of to the axes table: its native value, and the values made available (the native one +# when none is given); the first declaration of an axis wins +function(FetchLayer_DeclareAxis AXIS) + __FetchLayer_axis_property("${AXIS}" NATIVE _property) + get_property(_known GLOBAL PROPERTY "${_property}") + if (_known) + return() + endif() + if (NOT AXIS MATCHES "^${_FETCHLAYER_AXIS_NAME}$") + message(FATAL_ERROR "FetchLayer_DeclareAxis(${AXIS}): an axis is named in lower case, a letter first") + endif() + + cmake_parse_arguments(PARSE_ARGV 1 _arg "" "NATIVE" "VALUES") + if (_arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "FetchLayer_DeclareAxis(${AXIS}): unknown arguments ${_arg_UNPARSED_ARGUMENTS}") + endif() + if (NOT _arg_NATIVE) + message(FATAL_ERROR "FetchLayer_DeclareAxis(${AXIS}): NATIVE, this build's value, is required") + endif() + set(_values ${_arg_VALUES}) + if (NOT _values) + set(_values "${_arg_NATIVE}") + endif() + list(REMOVE_DUPLICATES _values) + foreach(_value IN LISTS _arg_NATIVE _values) + if (_value MATCHES "[+=]") + message(FATAL_ERROR "FetchLayer_DeclareAxis(${AXIS}): a value has no + or =: ${_value}") + endif() + endforeach() + + set_property(GLOBAL PROPERTY "${_property}" "${_arg_NATIVE}") + __FetchLayer_axis_property("${AXIS}" VALUES _property) + set_property(GLOBAL PROPERTY "${_property}" "${_values}") + set_property(GLOBAL APPEND PROPERTY _FetchLayer_axes "${AXIS}") +endfunction() + + +# === FetchLayer_MakeAvailable ================================================================================ + +# FetchLayer_MakeAvailable(…) +# Makes an Instance of each variant of each staging, in the order given, with all it has: its Runtime, and +# its Development when it has a CMake package. The variants are those the axes table names for the staging +# (__FetchLayer_variant_list). As FetchContent_MakeAvailable it takes no components and no ordering of its +# own: a staging's DEPENDS must be available before it, so they are named first. +function(FetchLayer_MakeAvailable) + if (ARGN MATCHES "^TRIPLETS$|;TRIPLETS;|;TRIPLETS$") + message(FATAL_ERROR "FetchLayer_MakeAvailable: TRIPLETS is gone; the triplets are the VALUES of the axis " + "triplet, FetchLayer_DeclareAxis(triplet NATIVE … VALUES …)") + endif() + foreach(_name IN LISTS ARGN) + __FetchLayer_staging_get("${_name}" LAYER _layer) + if (NOT _layer) + message(FATAL_ERROR "FetchLayer_MakeAvailable: no staging ${_name} is declared") + endif() + __FetchLayer_staging_get("${_name}" AXES _axes) + __FetchLayer_axis_check(${_axes}) + # what of its layer is asked for: the layer's decision looks at these + __FetchLayer_layer_get("${_layer}" REQUESTED _requested) + string(TOLOWER "${_name}" _lower) + if (NOT _lower IN_LIST _requested) + list(APPEND _requested "${_lower}") + __FetchLayer_layer_set("${_layer}" REQUESTED "${_requested}") + endif() + endforeach() + + foreach(_name IN LISTS ARGN) + __FetchLayer_variant_list("${_name}" _variants) + foreach(_variant IN LISTS _variants) + __FetchLayer_instance_get("${_name}" "${_variant}" POPULATED _populated) + if (_populated) + continue() + endif() + + __FetchLayer_find("${_name}" "${_variant}") + + __FetchLayer_instance_get("${_name}" "${_variant}" ORIGIN _origin) + cmake_language(CALL __FetchLayer_${_origin}_populate "${_name}" "${_variant}") + # integrate: a prefix is imported, sources are compiled + __FetchLayer_instance_get("${_name}" "${_variant}" PREFIX _prefix) + __FetchLayer_instance_get("${_name}" "${_variant}" SOURCE_DIR _source_dir) + if (_prefix) + __FetchLayer_import_make_available("${_variant}" "${_name}") + set(_where "${_prefix}") + elseif (_source_dir) + __FetchLayer_compile_make_available("${_variant}" "${_name}") + set(_where "${_source_dir}") + else() + message(FATAL_ERROR "FetchLayer: ${_name} from ${_origin} brought neither a prefix nor sources") + endif() + __FetchLayer_instance_set("${_name}" "${_variant}" POPULATED TRUE) + __FetchLayer_instance_get("${_name}" "${_variant}" REVISION _revision) + __FetchLayer_instance_get("${_name}" "${_variant}" IDENTITY_MATCH _match) + message(STATUS "FetchLayer: ${_name}@${_revision} ${_variant} from ${_origin} (${_match}): ${_where}") + endforeach() + endforeach() +endfunction() + +# __FetchLayer_find( ) +# The first origin that has it, of those the policy names, in order, recorded in the Instance; +# must be declared and its DEPENDS available for ; none fails, with the facts the policy +# went by +function(__FetchLayer_find STAGING VARIANT) + __FetchLayer_staging_get("${STAGING}" LAYER _layer) + if (NOT _layer) + message(FATAL_ERROR "FetchLayer: no staging ${STAGING} is declared") + endif() + __FetchLayer_request("${STAGING}" "${VARIANT}" _request) + + # the first origin that has it, of those the policy names + __FetchLayer_policy_lookup("${STAGING}" "${VARIANT}" _origins) + foreach(_origin IN LISTS _origins) + set(_candidate_REF "") + cmake_language(CALL __FetchLayer_${_origin}_resolve "${STAGING}" "${VARIANT}" _request _candidate) + if (_candidate_REF) + foreach(_field ORIGIN REF REVISION DEPENDS_REVISIONS VARIANT IDENTITY_MATCH) + __FetchLayer_instance_set("${STAGING}" "${VARIANT}" ${_field} "${_candidate_${_field}}") + endforeach() + return() + endif() + endforeach() + + # none has it: the facts the policy went by + __FetchLayer_staging_get("${STAGING}" LAYER _layer) + __FetchLayer_policy_build("${_layer}" _build) + __FetchLayer_variant_select("${STAGING}" "" _own) + list(JOIN _origins ", " _asked) + message(FATAL_ERROR "FetchLayer: no ${STAGING}@${_request_REVISION} for ${VARIANT} (depends " + "${_request_DEPENDS_REVISIONS}; asked ${_asked}; FETCHLAYER_BUILD for ${_layer}: ${_build}; " + "this build's own variant: ${_own})") +endfunction() + + +# __FetchLayer_request( ) +# The identity asked for, _*: its layer's revision as a commit, the revisions of its DEPENDS for the +# variants that go with , the variant. A dependency of another layer must be available already; one +# of the same layer is at the layer's commit, available or not +function(__FetchLayer_request STAGING VARIANT REQUEST) + __FetchLayer_staging_get("${STAGING}" LAYER _layer) + __FetchLayer_git_commit("${_layer}" _revision) + __FetchLayer_staging_get("${STAGING}" DEPENDS _depends) + set(_pairs) + foreach(_dependency IN LISTS _depends) + string(TOLOWER "${_dependency}" _dependency) + # the dependency's variant that goes with this one: by its own axes + __FetchLayer_variant_select("${_dependency}" "${VARIANT}" _dependency_variant) + __FetchLayer_instance_get("${_dependency}" "${_dependency_variant}" REVISION _dependency_revision) + __FetchLayer_staging_get("${_dependency}" LAYER _dependency_layer) + if (NOT _dependency_revision AND _dependency_layer STREQUAL _layer) + set(_dependency_revision "${_revision}") + endif() + if (NOT _dependency_revision) + message(FATAL_ERROR "FetchLayer: ${STAGING} depends on ${_dependency}, which is not available for " + "${_dependency_variant} yet; make it available first") + endif() + list(APPEND _pairs "${_dependency}=${_dependency_revision}") + endforeach() + list(SORT _pairs) + set(${REQUEST}_NAME "${STAGING}" PARENT_SCOPE) + set(${REQUEST}_REVISION "${_revision}" PARENT_SCOPE) + set(${REQUEST}_DEPENDS_REVISIONS "${_pairs}" PARENT_SCOPE) + set(${REQUEST}_VARIANT "${VARIANT}" PARENT_SCOPE) +endfunction() + + +# === FetchLayer_GetProperties ================================================================================ + +# FetchLayer_GetProperties( [VARIANT | AXES =…]) +# Copies one Instance of into _ in the caller's scope: this build's own variant; the +# variant given, as a string of _VARIANTS; or the one with these axes' values, its other axes at their +# native values. With it, each axis's value, _ (opcua-modules_TRIPLET), and the variants made +# available, _VARIANTS +function(FetchLayer_GetProperties NAME) + cmake_parse_arguments(PARSE_ARGV 1 _arg "" "VARIANT" "AXES") + if (_arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "FetchLayer_GetProperties(${NAME}): unknown arguments ${_arg_UNPARSED_ARGUMENTS}") + endif() + if (DEFINED _arg_VARIANT AND DEFINED _arg_AXES) + message(FATAL_ERROR "FetchLayer_GetProperties(${NAME}): VARIANT or AXES, not both") + endif() + if (DEFINED _arg_VARIANT) + set(_variant "${_arg_VARIANT}") + else() + __FetchLayer_variant_format(_given ${_arg_AXES}) + if (_given STREQUAL "any") + set(_given "") + endif() + __FetchLayer_variant_select("${NAME}" "${_given}" _variant) + endif() + + string(TOLOWER "${NAME}" _name) + foreach(_field IN LISTS _FETCHLAYER_INSTANCE_FIELDS) + __FetchLayer_instance_get("${_name}" "${_variant}" ${_field} _value) + set(${_name}_${_field} "${_value}" PARENT_SCOPE) + endforeach() + __FetchLayer_staging_get("${_name}" AXES _axes) + foreach(_axis IN LISTS _axes) + __FetchLayer_variant_extract("${_variant}" "${_axis}" _value) + string(TOUPPER "${_axis}" _upper) + set(${_name}_${_upper} "${_value}" PARENT_SCOPE) + endforeach() + __FetchLayer_variant_list("${_name}" _variants) + set(${_name}_VARIANTS "${_variants}" PARENT_SCOPE) +endfunction() diff --git a/cmake/FetchLayer/axis.cmake b/cmake/FetchLayer/axis.cmake new file mode 100644 index 0000000..5b22221 --- /dev/null +++ b/cmake/FetchLayer/axis.cmake @@ -0,0 +1,49 @@ +## +## Axes: what a staging has several variants by — triplet for every staging unless it says otherwise, python for +## the Python binding, and so on. A table, one row per axis: +## +## axis NATIVE VALUES +## triplet x86_64-macos-apple-clang-21-release x86_64-macos-apple-clang-21-release;x86_64-linux-gcc-11-release +## python 3.12 3.11;3.12 +## +## NATIVE this build's own value: the variant whose every axis is native is the one built from source and installed +## VALUES the values made available; NATIVE when none is given +## +## One global property per cell, _FetchLayer_axis__; the rows, in _FetchLayer_axes. A row is added +## by FetchLayer_DeclareAxis; triplet, when nothing declared it, is declared from FETCHLAYER_TRIPLET. +## + +include_guard(GLOBAL) + +# what an axis is named: lower case, a letter first — it goes into a variant's string and an identity's key +set(_FETCHLAYER_AXIS_NAME "[a-z][a-z0-9_]*") + +# __FetchLayer_axis_property( ) +# The name of the global property that holds of the row +function(__FetchLayer_axis_property AXIS COLUMN OUT) + set(${OUT} "_FetchLayer_axis_${AXIS}_${COLUMN}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_axis_get( ) +# NATIVE or VALUES of ; triplet, when nothing declared it, declared from FETCHLAYER_TRIPLET first +function(__FetchLayer_axis_get AXIS COLUMN OUT) + __FetchLayer_axis_property("${AXIS}" ${COLUMN} _property) + get_property(_value GLOBAL PROPERTY "${_property}") + if (NOT _value AND AXIS STREQUAL "triplet" AND FETCHLAYER_TRIPLET) + FetchLayer_DeclareAxis(triplet NATIVE "${FETCHLAYER_TRIPLET}") + get_property(_value GLOBAL PROPERTY "${_property}") + endif() + set(${OUT} "${_value}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_axis_check(…) +# Fails on an axis not declared +function(__FetchLayer_axis_check) + foreach(_axis IN LISTS ARGN) + __FetchLayer_axis_get("${_axis}" NATIVE _native) + if (NOT _native) + message(FATAL_ERROR "FetchLayer: no axis ${_axis} is declared; FetchLayer_DeclareAxis(${_axis} NATIVE …)" + " (triplet: or set FETCHLAYER_TRIPLET)") + endif() + endforeach() +endfunction() diff --git a/cmake/FetchLayer/compile.cmake b/cmake/FetchLayer/compile.cmake new file mode 100644 index 0000000..216f237 --- /dev/null +++ b/cmake/FetchLayer/compile.cmake @@ -0,0 +1,44 @@ +## +## The strategy compile: a layer's sources added to this build, as FetchContent_MakeAvailable adds them. +## +## Once per layer, at its first staging: add_subdirectory( SYSTEM) — SYSTEM, so the layer's +## headers do not turn its warnings into the consumers' errors; never EXCLUDE_FROM_ALL, so its install() rules +## pack. Its settings are the set(... CACHE ...) before MakeAvailable. Its install components are those that +## appear while it is added (get_cmake_property(COMPONENTS) before and after); what it installs without a +## COMPONENT goes to _Development. They go to the staging of the layer; a layer whose several stagings +## come from its sources would have to split them between them, which is not done yet. +## +## Only this build's own variant is compiled: the policy never sends another one here. +## + +include_guard(GLOBAL) + +# __FetchLayer_compile_make_available( ) +function(__FetchLayer_compile_make_available VARIANT STAGING) + __FetchLayer_staging_get("${STAGING}" LAYER _layer) + __FetchLayer_layer_get("${_layer}" COMPILED _compiled) + if (NOT _compiled) + __FetchLayer_layer_get("${_layer}" REQUESTED _requested) + list(LENGTH _requested _count) + if (_count GREATER 1) + message(FATAL_ERROR "FetchLayer: ${_layer} is built from its sources for ${_requested}; giving its " + "install components to several stagings is not done yet") + endif() + __FetchLayer_instance_get("${STAGING}" "${VARIANT}" SOURCE_DIR _source_dir) + __FetchLayer_instance_get("${STAGING}" "${VARIANT}" BINARY_DIR _binary_dir) + string(MAKE_C_IDENTIFIER "${STAGING}" _c) + + get_cmake_property(_before COMPONENTS) + set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "${_c}_Development") + add_subdirectory("${_source_dir}" "${_binary_dir}" SYSTEM) + get_cmake_property(_components COMPONENTS) + if (_before) + list(REMOVE_ITEM _components ${_before}) + endif() + + __FetchLayer_layer_set("${_layer}" COMPONENTS "${_components}") + __FetchLayer_layer_set("${_layer}" COMPILED TRUE) + endif() + __FetchLayer_layer_get("${_layer}" COMPONENTS _components) + __FetchLayer_instance_set("${STAGING}" "${VARIANT}" INSTALL_COMPONENTS "${_components}") +endfunction() diff --git a/cmake/FetchLayer/git.cmake b/cmake/FetchLayer/git.cmake new file mode 100644 index 0000000..aa8d287 --- /dev/null +++ b/cmake/FetchLayer/git.cmake @@ -0,0 +1,161 @@ +## +## The origin git: a layer's sources at a commit, by FetchContent. +## +## A layer is git's when its declaration names GIT_REPOSITORY; the other content options pass to +## FetchContent_Declare as they are, but GIT_TAG and SOURCE_SUBDIR, which are git's own: the commit resolved, +## and a subdirectory that does not exist, so FetchContent downloads only and adds nothing. The layer is the +## content FetchContent knows, under the layer's name; its stagings come from that one tree. +## +## Before any origin is asked, find turns the declared revision into a commit, __FetchLayer_git_commit: a +## full sha as it is, a tag or a branch by git ls-remote; a short sha is refused — no ref names it, and +## finding it needs the history. The commit replaces the declared revision, so the next triplet takes it as +## it is, and every triplet gets the same one; every origin then looks for it. +## +## resolve, for a staging: sources are built for this build's own variant only, so another variant gets nothing. +## The identity is exact: the staging is built here against what is available. +## populate downloads its layer: the Instance's SOURCE_DIR and BINARY_DIR. FetchContent's own variables hold, +## among them FETCHCONTENT_SOURCE_DIR_ (the layer's name upper-cased, as FetchContent does), a local +## checkout taken as it is — its HEAD is the revision, and one other than declared, or with changes, is +## warned about. +## + +include_guard(GLOBAL) + +include(FetchContent) +find_package(Git QUIET) + +set(_FETCHLAYER_GIT_NO_SUBDIR "__fetchlayer_download_only__") + +# __FetchLayer_git_repository( ) +# The GIT_REPOSITORY of the layer, empty when none +function(__FetchLayer_git_repository LAYER OUT) + __FetchLayer_layer_get("${LAYER}" ARGUMENTS _arguments) + cmake_parse_arguments(_git "" "GIT_REPOSITORY;GIT_TAG;SOURCE_SUBDIR" "" ${_arguments}) + if (DEFINED _git_GIT_TAG OR DEFINED _git_SOURCE_SUBDIR) + message(FATAL_ERROR "FetchLayer_Declare(${LAYER}): GIT_TAG and SOURCE_SUBDIR are FetchLayer's; " + "the commit is REVISION") + endif() + set(${OUT} "${_git_GIT_REPOSITORY}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_git_run( …) +# git …, its output stripped; fails with git's error +function(__FetchLayer_git_run OUT) + if (NOT GIT_EXECUTABLE) + message(FATAL_ERROR "FetchLayer: git is not found") + endif() + execute_process(COMMAND "${GIT_EXECUTABLE}" ${ARGN} + OUTPUT_VARIABLE _out ERROR_VARIABLE _err RESULT_VARIABLE _rc + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (NOT _rc EQUAL 0) + list(JOIN ARGN " " _command) + message(FATAL_ERROR "FetchLayer: git ${_command}:\n${_err}") + endif() + set(${OUT} "${_out}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_git_commit( ) +# The declared revision of as a full sha: as it is, or a tag (an annotated one by the commit it points +# to) or a branch of its GIT_REPOSITORY by git ls-remote, then written over the declared REVISION: once per +# layer, every variant the same commit, though the branch moves meanwhile +function(__FetchLayer_git_commit LAYER OUT) + __FetchLayer_layer_get("${LAYER}" REVISION _revision) + string(LENGTH "${_revision}" _length) + if (_revision MATCHES "^[0-9a-f]+$" AND _length EQUAL 40) + set(_sha "${_revision}") + else() + __FetchLayer_git_repository("${LAYER}" _repository) + if (NOT _repository) + message(FATAL_ERROR "FetchLayer: ${LAYER} revision ${_revision} is not a full sha, and the layer has no " + "GIT_REPOSITORY to resolve it by") + endif() + if (FETCHCONTENT_FULLY_DISCONNECTED) + message(FATAL_ERROR "FetchLayer: ${LAYER} revision ${_revision} is not a full sha, and " + "FETCHCONTENT_FULLY_DISCONNECTED forbids asking ${_repository}") + endif() + __FetchLayer_git_run(_lines ls-remote "${_repository}" + "refs/tags/${_revision}" "refs/tags/${_revision}^{}" "refs/heads/${_revision}") + # \t lines, as a = list + string(REPLACE "\n" ";" _lines "${_lines}") + set(_refs) + foreach(_line IN LISTS _lines) + string(REPLACE "\t" ";" _fields "${_line}") + list(GET _fields 0 _line_sha) + list(GET _fields 1 _line_ref) + list(APPEND _refs "${_line_ref}=${_line_sha}") + endforeach() + foreach(_ref_name "refs/tags/${_revision}^{}" "refs/tags/${_revision}" "refs/heads/${_revision}") + foreach(_entry IN LISTS _refs) + if (_entry MATCHES "^(.*)=([0-9a-f]+)$" AND CMAKE_MATCH_1 STREQUAL _ref_name) + set(_sha "${CMAKE_MATCH_2}") + endif() + endforeach() + if (_sha) + break() + endif() + endforeach() + if (NOT _sha) + message(FATAL_ERROR "FetchLayer: ${LAYER} revision ${_revision} is neither a full sha nor a tag or a branch " + "of ${_repository}; a short sha is not resolved, give the full one") + endif() + message(STATUS "FetchLayer: ${LAYER} ${_revision} is ${_sha}") + __FetchLayer_layer_set("${LAYER}" REVISION "${_sha}") + endif() + set(${OUT} "${_sha}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_git_resolve( ) +function(__FetchLayer_git_resolve STAGING VARIANT REQUEST CANDIDATE) + __FetchLayer_variant_native("${VARIANT}" _native) + if (NOT _native) + return() + endif() + __FetchLayer_staging_get("${STAGING}" LAYER _layer) + __FetchLayer_git_repository("${_layer}" _repository) + if (NOT _repository) + message(WARNING "FetchLayer: ${STAGING} is to be built from sources, and its layer ${_layer} has no " + "GIT_REPOSITORY to build from") + return() + endif() + set(_wanted "${${REQUEST}_REVISION}") + string(TOUPPER "${_layer}" _upper) + set(_local "${FETCHCONTENT_SOURCE_DIR_${_upper}}") + + if (_local) + # a local checkout, as it is: its HEAD + __FetchLayer_git_run(_sha -C "${_local}" rev-parse HEAD) + __FetchLayer_git_run(_changes -C "${_local}" status --porcelain --untracked-files=no) + if (NOT _sha STREQUAL _wanted OR _changes) + set(_with "") + if (_changes) + set(_with " with changes") + endif() + message(WARNING "FetchLayer: ${_layer} from the local checkout ${_local}: ${_sha}${_with}, " + "not the declared ${_wanted}") + endif() + set(_ref "${_local}") + else() + set(_sha "${_wanted}") + set(_ref "${_sha}") + endif() + + set(${CANDIDATE}_ORIGIN git PARENT_SCOPE) + set(${CANDIDATE}_REF "${_ref}" PARENT_SCOPE) + set(${CANDIDATE}_REVISION "${_sha}" PARENT_SCOPE) + set(${CANDIDATE}_DEPENDS_REVISIONS "${${REQUEST}_DEPENDS_REVISIONS}" PARENT_SCOPE) + set(${CANDIDATE}_VARIANT "${VARIANT}" PARENT_SCOPE) + set(${CANDIDATE}_IDENTITY_MATCH EXACT PARENT_SCOPE) +endfunction() + +# __FetchLayer_git_populate( ) +# Downloads the Instance's REVISION of its layer, once for the layer; the Instance's SOURCE_DIR and BINARY_DIR +function(__FetchLayer_git_populate STAGING VARIANT) + __FetchLayer_staging_get("${STAGING}" LAYER _layer) + __FetchLayer_layer_get("${_layer}" ARGUMENTS _arguments) + __FetchLayer_instance_get("${STAGING}" "${VARIANT}" REVISION _sha) + FetchContent_Declare(${_layer} ${_arguments} GIT_TAG "${_sha}" SOURCE_SUBDIR "${_FETCHLAYER_GIT_NO_SUBDIR}") + FetchContent_MakeAvailable(${_layer}) + FetchContent_GetProperties(${_layer} SOURCE_DIR _source_dir BINARY_DIR _binary_dir) + __FetchLayer_instance_set("${STAGING}" "${VARIANT}" SOURCE_DIR "${_source_dir}") + __FetchLayer_instance_set("${STAGING}" "${VARIANT}" BINARY_DIR "${_binary_dir}") +endfunction() diff --git a/cmake/FetchLayer/identity.cmake b/cmake/FetchLayer/identity.cmake new file mode 100644 index 0000000..df0116d --- /dev/null +++ b/cmake/FetchLayer/identity.cmake @@ -0,0 +1,76 @@ +## +## The identity of a staging and of a variant of it, its hash and its place in the store. +## +## An identity is variables with one prefix, _*: +## +## _NAME the staging opcua-modules +## _REVISION the commit it is built from ad11c59ad433… +## _DEPENDS_REVISIONS the commits it is built against opendaq=3039ee6d…;libx=… +## _VARIANT what it is built for (variant.cmake); empty for the identity of the staging itself +## +## Its hash is that of its key=value lines — the revision, one per dependency, one per axis of the variant — +## sorted bytewise and joined by \n, sha256, after a prefix: idx- for a staging, pkg- for a variant. +## The keys are those the registry annotates its packages with, so the hash names the tags there too; whoever +## names them sets them: +## +## FETCHLAYER_IDENTITY_REVISION_KEY the revision +## FETCHLAYER_IDENTITY_DEPENDENCY_KEY a dependency's revision, standing for the dependency +## FETCHLAYER_IDENTITY_AXIS_KEY the value of an axis, standing for the axis +## +## Only stagings (DEPENDS) and axes enter an identity: what else a package is annotated with must use other keys. +## A variant lies in the store at //idx-/. +## + +include_guard(GLOBAL) + +include("${CMAKE_CURRENT_LIST_DIR}/variant.cmake") + +set(FETCHLAYER_IDENTITY_REVISION_KEY "revision" CACHE STRING "Key of the revision in an identity") +set(FETCHLAYER_IDENTITY_DEPENDENCY_KEY "dependency..revision" CACHE STRING "Key of a dependency's revision in an identity; is the dependency") +set(FETCHLAYER_IDENTITY_AXIS_KEY "" CACHE STRING "Key of an axis's value in an identity; is the axis") + +# __FetchLayer_identity_hash( ) +# - of the identity ; with the variant left out for a staging's own hash +function(__FetchLayer_identity_hash ID PREFIX OUT) + if (NOT ${ID}_REVISION) + message(FATAL_ERROR "FetchLayer: the identity ${ID} has no revision") + endif() + set(_lines "${FETCHLAYER_IDENTITY_REVISION_KEY}=${${ID}_REVISION}") + foreach(_pair IN LISTS ${ID}_DEPENDS_REVISIONS) + if (NOT _pair MATCHES "^([^=]+)=(.+)$") + message(FATAL_ERROR "FetchLayer: not a = pair in ${ID}: ${_pair}") + endif() + string(REPLACE "" "${CMAKE_MATCH_1}" _key "${FETCHLAYER_IDENTITY_DEPENDENCY_KEY}") + list(APPEND _lines "${_key}=${CMAKE_MATCH_2}") + endforeach() + if (${ID}_VARIANT) + __FetchLayer_variant_parse("${${ID}_VARIANT}" _axes) + foreach(_pair IN LISTS _axes) + string(REGEX MATCH "^([^=]+)=(.*)$" _ "${_pair}") + string(REPLACE "" "${CMAKE_MATCH_1}" _key "${FETCHLAYER_IDENTITY_AXIS_KEY}") + list(APPEND _lines "${_key}=${CMAKE_MATCH_2}") + endforeach() + endif() + list(SORT _lines) + list(JOIN _lines "\n" _text) + string(SHA256 _hash "${_text}") + set(${OUT} "${PREFIX}-${_hash}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_identity_path( ) +# Where the variant lies in the store: //idx-/ +function(__FetchLayer_identity_path ID OUT) + if (NOT FETCHLAYER_STORE) + message(FATAL_ERROR "FetchLayer: FETCHLAYER_STORE is not set") + endif() + if (NOT ${ID}_NAME OR NOT ${ID}_VARIANT) + message(FATAL_ERROR "FetchLayer: the identity ${ID} needs a name and a variant for a place in the store") + endif() + # the staging's own hash: the same revisions, without the variant + set(_staging_NAME "${${ID}_NAME}") + set(_staging_REVISION "${${ID}_REVISION}") + set(_staging_DEPENDS_REVISIONS "${${ID}_DEPENDS_REVISIONS}") + set(_staging_VARIANT "") + __FetchLayer_identity_hash(_staging idx _idx) + set(${OUT} "${FETCHLAYER_STORE}/${${ID}_NAME}/${_idx}/${${ID}_VARIANT}" PARENT_SCOPE) +endfunction() diff --git a/cmake/FetchLayer/import.cmake b/cmake/FetchLayer/import.cmake new file mode 100644 index 0000000..9b55b35 --- /dev/null +++ b/cmake/FetchLayer/import.cmake @@ -0,0 +1,96 @@ +## +## The strategy import: a prefix made available to this build. +## +## For this build's own variant, the prefix is installed under the staging's install components, +## _Runtime — shared libraries and modules — and _Development — the rest: headers, CMake +## configs, static and import libraries. When the prefix holds the staging's CMake package, it is found from +## the prefix only (find_package … GLOBAL), so its targets exist. Each module file becomes an imported MODULE +## target, ::. +## For another variant nothing is installed or imported: the prefix is only recorded, files for whoever +## lays them out (a multi-variant target). +## +## in a component name is the staging's name made a C identifier (opcua_modules_Runtime). +## + +include_guard(GLOBAL) + +set(_FETCHLAYER_RUNTIME_PATTERNS "*.so" "*.so.*" "*.dylib" "*.dll") + +# __FetchLayer_import_make_available( …) +function(__FetchLayer_import_make_available VARIANT) + foreach(_name IN LISTS ARGN) + __FetchLayer_instance_get("${_name}" "${VARIANT}" PREFIX _prefix) + __FetchLayer_variant_native("${VARIANT}" _native) + if (NOT _native) + __FetchLayer_instance_set("${_name}" "${VARIANT}" INSTALL_COMPONENTS "") + continue() + endif() + string(MAKE_C_IDENTIFIER "${_name}" _c) + set(_components) + + # the prefix's files, split: shared libraries and modules are Runtime, the rest Development; + # file by file, so no empty directory lands in a component + file(GLOB_RECURSE _files LIST_DIRECTORIES false RELATIVE "${_prefix}" "${_prefix}/*") + set(_dirs) + foreach(_f IN LISTS _files) + get_filename_component(_n "${_f}" NAME) + get_filename_component(_d "${_f}" DIRECTORY) + set(_kind Development) + foreach(_p IN LISTS _FETCHLAYER_RUNTIME_PATTERNS) + string(REPLACE "." "\\." _rx "${_p}") + string(REPLACE "*" ".*" _rx "${_rx}") + if (_n MATCHES "^${_rx}$") + set(_kind Runtime) + endif() + endforeach() + string(MAKE_C_IDENTIFIER "${_kind}_${_d}" _key) + list(APPEND _files_${_key} "${_prefix}/${_f}") + list(APPEND _dirs "${_kind}|${_d}") + endforeach() + list(REMOVE_DUPLICATES _dirs) + foreach(_e IN LISTS _dirs) + string(REPLACE "|" ";" _e "${_e}") + list(GET _e 0 _kind) + list(LENGTH _e _len) + set(_d "") + if (_len GREATER 1) + list(GET _e 1 _d) + endif() + string(MAKE_C_IDENTIFIER "${_kind}_${_d}" _key) + if (_d STREQUAL "") + set(_d .) + endif() + install(FILES ${_files_${_key}} DESTINATION "${_d}" COMPONENT ${_c}_${_kind}) + list(APPEND _components ${_c}_${_kind}) + endforeach() + list(REMOVE_DUPLICATES _components) + + # a module file → an imported MODULE target + file(GLOB _modules "${_prefix}/lib/modules/*" "${_prefix}/bin/modules/*") + foreach(_m IN LISTS _modules) + get_filename_component(_file "${_m}" NAME) + string(REGEX REPLACE "^lib" "" _stem "${_file}") + string(REGEX REPLACE "(-[0-9]+-[0-9]+)?\\.module\\..*$" "" _stem "${_stem}") + set(_target "${_name}::${_stem}") + if (NOT TARGET ${_target}) + add_library(${_target} MODULE IMPORTED GLOBAL) + set_target_properties(${_target} PROPERTIES IMPORTED_LOCATION "${_m}") + endif() + endforeach() + + # the staging's package, when the prefix holds it, found from the prefix only + __FetchLayer_system_config("${_name}" "${_prefix}" _config) + if (_config) + __FetchLayer_staging_get("${_name}" FIND_PACKAGE_ARGS _args) + cmake_parse_arguments(_find "" "" "NAMES" ${_args}) + set(_package "${_name}") + if (_find_NAMES) + list(GET _find_NAMES 0 _package) + endif() + list(PREPEND CMAKE_PREFIX_PATH "${_prefix}") # for the package's own dependencies + find_package(${_package} CONFIG REQUIRED GLOBAL BYPASS_PROVIDER PATHS "${_prefix}" NO_DEFAULT_PATH) + endif() + + __FetchLayer_instance_set("${_name}" "${VARIANT}" INSTALL_COMPONENTS "${_components}") + endforeach() +endfunction() diff --git a/cmake/FetchLayer/instance.cmake b/cmake/FetchLayer/instance.cmake new file mode 100644 index 0000000..9e9e9ee --- /dev/null +++ b/cmake/FetchLayer/instance.cmake @@ -0,0 +1,43 @@ +## +## The Instance: a variant of a staging made available, one per staging and variant. One global property per +## field, _FetchLayer_instance___, the name lower-cased: +## +## ORIGIN where it came from: system, git find +## REF the origin's reference: a store path, a sha, a local checkout find +## REVISION the commit of its layer it is built from find +## DEPENDS_REVISIONS the commits it is built against, =… find +## VARIANT what it is built for, one value per axis (variant.cmake) find +## IDENTITY_MATCH EXACT: its identity was read or it is built here; VERSION: guessed find +## PREFIX prebuilt: its install prefix populate +## SOURCE_DIR from source: its sources … populate +## BINARY_DIR … and their build tree populate +## INSTALL_COMPONENTS its install components, _Runtime / _Development integration +## POPULATED made available MakeAvailable +## +## FetchLayer_GetProperties copies every field into _. +## + +include_guard(GLOBAL) + +set(_FETCHLAYER_INSTANCE_FIELDS + POPULATED ORIGIN REF PREFIX SOURCE_DIR BINARY_DIR REVISION DEPENDS_REVISIONS VARIANT IDENTITY_MATCH INSTALL_COMPONENTS) + +# __FetchLayer_instance_property( ) +# The name of the global property that holds of the staging for +function(__FetchLayer_instance_property STAGING VARIANT FIELD OUT) + string(TOLOWER "${STAGING}" _name) + set(${OUT} "_FetchLayer_instance_${_name}_${VARIANT}_${FIELD}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_instance_set( ) +function(__FetchLayer_instance_set STAGING VARIANT FIELD VALUE) + __FetchLayer_instance_property("${STAGING}" "${VARIANT}" ${FIELD} _property) + set_property(GLOBAL PROPERTY "${_property}" "${VALUE}") +endfunction() + +# __FetchLayer_instance_get( ) +function(__FetchLayer_instance_get STAGING VARIANT FIELD OUT) + __FetchLayer_instance_property("${STAGING}" "${VARIANT}" ${FIELD} _property) + get_property(_value GLOBAL PROPERTY "${_property}") + set(${OUT} "${_value}" PARENT_SCOPE) +endfunction() diff --git a/cmake/FetchLayer/layer.cmake b/cmake/FetchLayer/layer.cmake new file mode 100644 index 0000000..8418796 --- /dev/null +++ b/cmake/FetchLayer/layer.cmake @@ -0,0 +1,37 @@ +## +## The Layer: a repository at a revision, as FetchLayer_Declare was given it — what FetchContent knows. One +## global property per field, _FetchLayer_layer__, the name lower-cased: +## +## REVISION a full sha, a tag or a branch; a tag or a branch is replaced by its commit when one of its +## stagings is first looked for (__FetchLayer_git_commit) +## ARGUMENTS the rest, as given — FetchContent's content options; origins parse their own keywords from it +## (GIT_REPOSITORY, …) +## STAGINGS the stagings it gives, as FetchLayer_DeclareStaging adds them +## REQUESTED those of its stagings FetchLayer_MakeAvailable was asked for +## ORIGIN where this build's own variants of its stagings come from, decided once for the layer: +## system, all prebuilt, or git, all from its sources (__FetchLayer_policy_layer_origin) +## COMPILED its sources added to this build, once (compile.cmake) +## COMPONENTS the install components its sources gave +## + +include_guard(GLOBAL) + +# __FetchLayer_layer_property( ) +# The name of the global property that holds of the layer +function(__FetchLayer_layer_property LAYER FIELD OUT) + string(TOLOWER "${LAYER}" _name) + set(${OUT} "_FetchLayer_layer_${_name}_${FIELD}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_layer_set( ) +function(__FetchLayer_layer_set LAYER FIELD VALUE) + __FetchLayer_layer_property("${LAYER}" ${FIELD} _property) + set_property(GLOBAL PROPERTY "${_property}" "${VALUE}") +endfunction() + +# __FetchLayer_layer_get( ) +function(__FetchLayer_layer_get LAYER FIELD OUT) + __FetchLayer_layer_property("${LAYER}" ${FIELD} _property) + get_property(_value GLOBAL PROPERTY "${_property}") + set(${OUT} "${_value}" PARENT_SCOPE) +endfunction() diff --git a/cmake/FetchLayer/policy.cmake b/cmake/FetchLayer/policy.cmake new file mode 100644 index 0000000..d0d21b2 --- /dev/null +++ b/cmake/FetchLayer/policy.cmake @@ -0,0 +1,86 @@ +## +## The policy: whether a staging is built from its layer's sources. One setting, as Conan's --build, for all +## layers and, overriding it, for one: +## +## FETCHLAYER_BUILD for all layers, MISSING by default +## FETCHLAYER_BUILD_ for one layer, the layer's name upper-cased as FetchContent's +## (FETCHLAYER_BUILD_OPCUAMODULES) +## +## NEVER prebuilt stagings only; one missing is an error +## MISSING prebuilt where there is one, else built from sources +## ALWAYS built from sources, prebuilt ones not looked for +## +## A layer from a local checkout (FETCHCONTENT_SOURCE_DIR_) is always built from it, as the bundle spec +## says. A setting is a plain variable, as FetchContent's: set by a preset, -D, cmake -C or the project; +## __FetchLayer_policy_build reads it for a layer. +## +## Not the policy, a fact: only this build's own variant can be built here; another variant — another +## triplet, another Python — comes prebuilt or not at all. +## + +include_guard(GLOBAL) + +set(FETCHLAYER_BUILD "MISSING" CACHE STRING "Whether stagings are built from sources: NEVER, MISSING, ALWAYS") +set_property(CACHE FETCHLAYER_BUILD PROPERTY STRINGS NEVER MISSING ALWAYS) + +# __FetchLayer_policy_build( ) +# NEVER, MISSING or ALWAYS for : ALWAYS for a local checkout, else FETCHLAYER_BUILD_, else +# FETCHLAYER_BUILD +function(__FetchLayer_policy_build LAYER OUT) + string(TOUPPER "${LAYER}" _upper) + if (FETCHCONTENT_SOURCE_DIR_${_upper}) + set(_build ALWAYS) + elseif (DEFINED FETCHLAYER_BUILD_${_upper}) + set(_build "${FETCHLAYER_BUILD_${_upper}}") + else() + set(_build "${FETCHLAYER_BUILD}") + endif() + set(${OUT} "${_build}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_policy_layer_origin( ) +# Where this build's own variants of the layer's stagings come from, decided once for the layer and kept as its +# ORIGIN: git when FETCHLAYER_BUILD is ALWAYS (or the layer is a local checkout); system when it is NEVER; when +# it is MISSING, system if the store has the own variant of every staging of the layer asked for, else git — +# for all of them, as two copies of a layer's targets cannot live in one tree +function(__FetchLayer_policy_layer_origin LAYER OUT) + __FetchLayer_layer_get("${LAYER}" ORIGIN _origin) + if (NOT _origin) + __FetchLayer_policy_build("${LAYER}" _build) + if (_build STREQUAL "ALWAYS") + set(_origin git) + elseif (_build STREQUAL "NEVER") + set(_origin system) + else() + set(_origin system) + __FetchLayer_layer_get("${LAYER}" REQUESTED _requested) + foreach(_staging IN LISTS _requested) + __FetchLayer_variant_select("${_staging}" "" _variant) + __FetchLayer_request("${_staging}" "${_variant}" _request) + set(_candidate_REF "") + __FetchLayer_system_resolve("${_staging}" "${_variant}" _request _candidate) + if (NOT _candidate_REF) + message(STATUS "FetchLayer: ${LAYER} from its sources: no prebuilt ${_staging} for ${_variant}") + set(_origin git) + break() + endif() + endforeach() + endif() + __FetchLayer_layer_set("${LAYER}" ORIGIN "${_origin}") + endif() + set(${OUT} "${_origin}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_policy_lookup( ) +# The origin to ask: for this build's own variant, its layer's (__FetchLayer_policy_layer_origin); for another +# variant, system — it cannot be built here +function(__FetchLayer_policy_lookup STAGING VARIANT OUT) + __FetchLayer_variant_native("${VARIANT}" _native) + if (_native) + __FetchLayer_staging_get("${STAGING}" LAYER _layer) + __FetchLayer_policy_layer_origin("${_layer}" _origin) + else() + set(_origin system) + endif() + set(${OUT} "${_origin}" PARENT_SCOPE) +endfunction() diff --git a/cmake/FetchLayer/staging.cmake b/cmake/FetchLayer/staging.cmake new file mode 100644 index 0000000..249f5d1 --- /dev/null +++ b/cmake/FetchLayer/staging.cmake @@ -0,0 +1,32 @@ +## +## The Staging: what a layer gives as one whole — declared, made available and published with one identity; +## its builds are its Instances. One global property per field, _FetchLayer_staging__, the name +## lower-cased: +## +## LAYER the layer that gives it, its revision and its sources +## AXES what it has variants by: triplet by default, none when declared empty (axis.cmake) +## DEPENDS the stagings it is built against +## FIND_PACKAGE_ARGS how to find it as a package +## + +include_guard(GLOBAL) + +# __FetchLayer_staging_property( ) +# The name of the global property that holds of the staging +function(__FetchLayer_staging_property STAGING FIELD OUT) + string(TOLOWER "${STAGING}" _name) + set(${OUT} "_FetchLayer_staging_${_name}_${FIELD}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_staging_set( ) +function(__FetchLayer_staging_set STAGING FIELD VALUE) + __FetchLayer_staging_property("${STAGING}" ${FIELD} _property) + set_property(GLOBAL PROPERTY "${_property}" "${VALUE}") +endfunction() + +# __FetchLayer_staging_get( ) +function(__FetchLayer_staging_get STAGING FIELD OUT) + __FetchLayer_staging_property("${STAGING}" ${FIELD} _property) + get_property(_value GLOBAL PROPERTY "${_property}") + set(${OUT} "${_value}" PARENT_SCOPE) +endfunction() diff --git a/cmake/FetchLayer/system.cmake b/cmake/FetchLayer/system.cmake new file mode 100644 index 0000000..eba1d6d --- /dev/null +++ b/cmake/FetchLayer/system.cmake @@ -0,0 +1,81 @@ +## +## The origin system: a staging already on the disk. +## +## resolve looks first in the store, //idx-/, by the hash of the identity +## requested (its revision a full sha: find turns a tag or a branch into one first). A directory there is the +## variant requested by construction — its path is its identity — so the identity is exact; and it is complete, +## renamed into place once unpacked. Else FETCHLAYER_PREFIX_, a directory named by hand for the staging (its +## name upper-cased): taken for what was asked, as asked for, its identity a version. +## Whatever the prefix holds is taken: Runtime always, Development when the staging's CMake config is there. +## populate brings nothing: the prefix is where it was found. +## + +include_guard(GLOBAL) + +# __FetchLayer_system_config( ) +# The staging's CMake config in , by the package name FIND_PACKAGE_ARGS gives (NAMES …), else the staging's +function(__FetchLayer_system_config STAGING PREFIX OUT) + __FetchLayer_staging_get("${STAGING}" FIND_PACKAGE_ARGS _args) + cmake_parse_arguments(_F "" "" "NAMES" ${_args}) + set(_pkgs ${_F_NAMES}) + if (NOT _pkgs) + set(_pkgs "${STAGING}") + endif() + set(_found "") + foreach(_p IN LISTS _pkgs) + string(TOLOWER "${_p}" _lp) + foreach(_dir lib/cmake/${_p} lib/cmake/${_lp} lib/${_p}/cmake share/cmake/${_p} CMake cmake) + foreach(_f ${_p}Config.cmake ${_lp}-config.cmake) + if (EXISTS "${PREFIX}/${_dir}/${_f}") + set(_found "${PREFIX}/${_dir}/${_f}") + break() + endif() + endforeach() + if (_found) + break() + endif() + endforeach() + if (_found) + break() + endif() + endforeach() + set(${OUT} "${_found}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_system_resolve( ) +function(__FetchLayer_system_resolve STAGING VARIANT REQUEST CANDIDATE) + string(TOUPPER "${STAGING}" _upper) + set(_prefix "") + set(_match VERSION) + + # the store, by the hash of the identity requested: exact + if (FETCHLAYER_STORE) + __FetchLayer_identity_path(${REQUEST} _path) + if (IS_DIRECTORY "${_path}") + set(_prefix "${_path}") + set(_match EXACT) + endif() + endif() + + # else a prefix named by hand for the staging: taken for what was asked, as a version + if (NOT _prefix AND FETCHLAYER_PREFIX_${_upper}) + set(_prefix "${FETCHLAYER_PREFIX_${_upper}}") + endif() + + if (NOT _prefix OR NOT IS_DIRECTORY "${_prefix}") + return() + endif() + set(${CANDIDATE}_ORIGIN system PARENT_SCOPE) + set(${CANDIDATE}_REF "${_prefix}" PARENT_SCOPE) + set(${CANDIDATE}_REVISION "${${REQUEST}_REVISION}" PARENT_SCOPE) + set(${CANDIDATE}_DEPENDS_REVISIONS "${${REQUEST}_DEPENDS_REVISIONS}" PARENT_SCOPE) + set(${CANDIDATE}_VARIANT "${VARIANT}" PARENT_SCOPE) + set(${CANDIDATE}_IDENTITY_MATCH ${_match} PARENT_SCOPE) +endfunction() + +# __FetchLayer_system_populate( ) +# The Instance's REF is its prefix already +function(__FetchLayer_system_populate STAGING VARIANT) + __FetchLayer_instance_get("${STAGING}" "${VARIANT}" REF _ref) + __FetchLayer_instance_set("${STAGING}" "${VARIANT}" PREFIX "${_ref}") +endfunction() diff --git a/cmake/FetchLayer/variant.cmake b/cmake/FetchLayer/variant.cmake new file mode 100644 index 0000000..72d75e3 --- /dev/null +++ b/cmake/FetchLayer/variant.cmake @@ -0,0 +1,146 @@ +## +## Variants: a variant of a staging is one value of each of its axes — what it is built for, as against what it +## is built from. Its identity, pkg-, is the staging's at a revision, idx- (what it is built from), together with +## these values. +## +## A variant is written as one string: the triplet, then += for each other axis by name — +## x86_64-macos-apple-clang-21-release+python=3.12; without a triplet the pairs alone, python=3.12; without any +## axis, any. The same values give the same string, whatever their order. It names the Instance and the +## variant's directory under idx- in the store. +## + +include_guard(GLOBAL) + +# __FetchLayer_variant_format( =…) +# The variant with these values, written as one string; a pair not =, or an axis given twice, is +# an error +function(__FetchLayer_variant_format OUT) + set(_triplet "") + set(_pairs) + set(_seen) + foreach(_pair IN LISTS ARGN) + if (NOT _pair MATCHES "^(${_FETCHLAYER_AXIS_NAME})=.+$") + message(FATAL_ERROR "FetchLayer: not an = pair: ${_pair}") + endif() + set(_axis "${CMAKE_MATCH_1}") + if (_axis IN_LIST _seen) + message(FATAL_ERROR "FetchLayer: the axis ${_axis} is given twice: ${ARGN}") + endif() + list(APPEND _seen "${_axis}") + if (_pair MATCHES "^triplet=(.+)$") + set(_triplet "${CMAKE_MATCH_1}") + else() + list(APPEND _pairs "${_pair}") + endif() + endforeach() + list(SORT _pairs) + set(_variant "${_triplet}") + foreach(_pair IN LISTS _pairs) + if (_variant) + string(APPEND _variant "+") + endif() + string(APPEND _variant "${_pair}") + endforeach() + if (NOT _variant) + set(_variant "any") + endif() + set(${OUT} "${_variant}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_variant_parse( ) +# The = pairs of the variant : the string variant_format wrote, taken apart +function(__FetchLayer_variant_parse VARIANT OUT) + set(_pairs) + if (NOT VARIANT STREQUAL "any") + string(REPLACE "+" ";" _parts "${VARIANT}") + foreach(_part IN LISTS _parts) + if (_part MATCHES "=") + list(APPEND _pairs "${_part}") + else() + list(APPEND _pairs "triplet=${_part}") + endif() + endforeach() + endif() + set(${OUT} "${_pairs}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_variant_extract( ) +# The value of in the variant , empty when it has none +function(__FetchLayer_variant_extract VARIANT AXIS OUT) + __FetchLayer_variant_parse("${VARIANT}" _pairs) + set(_value "") + foreach(_pair IN LISTS _pairs) + if (_pair MATCHES "^${AXIS}=(.*)$") + set(_value "${CMAKE_MATCH_1}") + endif() + endforeach() + set(${OUT} "${_value}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_variant_select( ) +# The variant of that goes with : its own axes' values in it, the others dropped — a +# dependency's variant. An axis of that lacks takes its native value +function(__FetchLayer_variant_select STAGING VARIANT OUT) + __FetchLayer_staging_get("${STAGING}" AXES _axes) + set(_pairs) + foreach(_axis IN LISTS _axes) + __FetchLayer_variant_extract("${VARIANT}" "${_axis}" _value) + if (NOT _value) + __FetchLayer_axis_get("${_axis}" NATIVE _value) + endif() + list(APPEND _pairs "${_axis}=${_value}") + endforeach() + __FetchLayer_variant_format(_variant ${_pairs}) + set(${OUT} "${_variant}" PARENT_SCOPE) +endfunction() + +# __FetchLayer_variant_native( ) +# TRUE when every axis of has its native value +function(__FetchLayer_variant_native VARIANT OUT) + __FetchLayer_variant_parse("${VARIANT}" _pairs) + set(_native TRUE) + foreach(_pair IN LISTS _pairs) + string(REGEX MATCH "^([^=]+)=(.*)$" _ "${_pair}") + set(_axis "${CMAKE_MATCH_1}") + set(_given "${CMAKE_MATCH_2}") + __FetchLayer_axis_get("${_axis}" NATIVE _value) + if (NOT _given STREQUAL _value) + set(_native FALSE) + endif() + endforeach() + set(${OUT} ${_native} PARENT_SCOPE) +endfunction() + +# __FetchLayer_variant_list( ) +# Every variant of to make available: each combination of its axes' VALUES +function(__FetchLayer_variant_list STAGING OUT) + __FetchLayer_staging_get("${STAGING}" AXES _axes) + set(_combinations "") # each a list of pairs, joined by + for now + set(_first TRUE) + foreach(_axis IN LISTS _axes) + __FetchLayer_axis_get("${_axis}" VALUES _values) + set(_next) + foreach(_value IN LISTS _values) + if (_first) + list(APPEND _next "${_axis}=${_value}") + else() + foreach(_combination IN LISTS _combinations) + list(APPEND _next "${_combination}+${_axis}=${_value}") + endforeach() + endif() + endforeach() + set(_combinations ${_next}) + set(_first FALSE) + endforeach() + set(_variants) + if (_first) + set(_variants any) + else() + foreach(_combination IN LISTS _combinations) + string(REPLACE "+" ";" _pairs "${_combination}") + __FetchLayer_variant_format(_variant ${_pairs}) + list(APPEND _variants "${_variant}") + endforeach() + endif() + set(${OUT} "${_variants}" PARENT_SCOPE) +endfunction() diff --git a/cmake/Layers.cmake b/cmake/Layers.cmake new file mode 100644 index 0000000..a089751 --- /dev/null +++ b/cmake/Layers.cmake @@ -0,0 +1,124 @@ +## +## What the bundle is made of: the axes its stagings have variants by; one FetchLayer_Declare per layer — a +## repository, named as in the manifest — its stagings under it, and its settings — they matter only when the +## layer compiles here. +## +## From the cache (a preset or -D): the revisions, one per layer, BUNDLE_REVISION_, the name upper-cased — +## a full sha, a tag or a branch; and each axis's values, BUNDLE_AXIS_, and its own value where it can be +## given, BUNDLE_. +## + +include_guard(GLOBAL) + + +# === the axes ================================================================================================ + +# triplet: this build's own, as the utils compose it; the ones made available, this build's own alone by default +set(BUNDLE_TRIPLET "" CACHE STRING "The triplet this build is for; empty: the one the utils compose") +set(BUNDLE_AXIS_TRIPLET "" CACHE STRING "The triplets to make available; empty: this build's own alone") +set(_triplet "${BUNDLE_TRIPLET}") +if (NOT _triplet) + opendaq_compose_package_triplet(OUTPUT_VARIABLE _triplet) +endif() +FetchLayer_DeclareAxis( + triplet + NATIVE "${_triplet}" + VALUES ${BUNDLE_AXIS_TRIPLET} +) + + +# === the layers ============================================================================================== + +foreach(_layer openDAQ OpcUaModules MQTTStreamingModule LTStreamingModulesModern ASAM-CMP-Modules) + string(TOUPPER "${_layer}" _upper) + set(BUNDLE_REVISION_${_upper} "" CACHE STRING "The revision of ${_layer} in the bundle: a full sha, a tag or a branch") +endforeach() + + +# === the core ================================================================================================ + +FetchLayer_Declare( + openDAQ + REVISION "${BUNDLE_REVISION_OPENDAQ}" + GIT_REPOSITORY https://github.com/openDAQ/openDAQ.git +) +FetchLayer_DeclareStaging( + opendaq + LAYER openDAQ + FIND_PACKAGE_ARGS NAMES openDAQ +) + +set(OPENDAQ_ENABLE_OPCUA OFF CACHE BOOL "") +set(OPENDAQ_GENERATE_DELPHI_BINDINGS OFF CACHE BOOL "") +set(OPENDAQ_GENERATE_PYTHON_BINDINGS ON CACHE BOOL "") +set(OPENDAQ_GENERATE_CSHARP_BINDINGS ON CACHE BOOL "") +set(OPENDAQ_GENERATE_C_BINDINGS ON CACHE BOOL "") +set(OPENDAQ_ENABLE_NATIVE_STREAMING ON CACHE BOOL "") +set(OPENDAQ_ENABLE_WEBSOCKET_STREAMING OFF CACHE BOOL "") +set(OPENDAQ_ENABLE_TESTS OFF CACHE BOOL "") +set(OPENDAQ_DOCUMENTATION_TESTS OFF CACHE BOOL "") +set(APP_ENABLE_WEBPAGE_EXAMPLES OFF CACHE BOOL "") +set(DAQMODULES_PARQUET_RECORDER_MODULE OFF CACHE BOOL "") + + +# === the modules ============================================================================================= + +FetchLayer_Declare( + OpcUaModules + REVISION "${BUNDLE_REVISION_OPCUAMODULES}" + GIT_REPOSITORY https://github.com/openDAQ/OpcUaModules.git +) +FetchLayer_DeclareStaging( + opcua-modules + LAYER OpcUaModules + DEPENDS opendaq +) + +set(DAQMODULES_OPCUA_ENABLE_EXAMPLE_APP OFF CACHE BOOL "") +set(DAQMODULES_OPCUA_ENABLE_CLIENT ON CACHE BOOL "") +set(DAQMODULES_OPCUA_ENABLE_SERVER ON CACHE BOOL "") +set(DAQMODULES_OPCUA_ENABLE_GENERIC_CLIENT ON CACHE BOOL "") + +FetchLayer_Declare( + MQTTStreamingModule + REVISION "${BUNDLE_REVISION_MQTTSTREAMINGMODULE}" + GIT_REPOSITORY https://github.com/openDAQ/MQTTStreamingModule.git +) +FetchLayer_DeclareStaging( + mqtt-module + LAYER MQTTStreamingModule + DEPENDS opendaq +) + +set(DAQMODULES_MQTT_ENABLE_EXAMPLE_APP OFF CACHE BOOL "") +set(DAQMODULES_MQTT_ENABLE_SSL OFF CACHE BOOL "") + +FetchLayer_Declare( + LTStreamingModulesModern + REVISION "${BUNDLE_REVISION_LTSTREAMINGMODULESMODERN}" + GIT_REPOSITORY https://github.com/openDAQ/LTStreamingModulesModern.git +) +FetchLayer_DeclareStaging( + lt-streaming-modules + LAYER LTStreamingModulesModern + DEPENDS opendaq +) + +set(DAQMODULES_LT_STREAMING_ENABLE_EXAMPLE_APP OFF CACHE BOOL "") +set(DAQMODULES_LT_STREAMING_ENABLE_CLIENT ON CACHE BOOL "") +set(DAQMODULES_LT_STREAMING_ENABLE_SERVER ON CACHE BOOL "") + +FetchLayer_Declare( + ASAM-CMP-Modules + REVISION "${BUNDLE_REVISION_ASAM-CMP-MODULES}" + GIT_REPOSITORY https://github.com/openDAQ/ASAM-CMP-Modules.git +) +FetchLayer_DeclareStaging( + asam-cmp-modules + LAYER ASAM-CMP-Modules + DEPENDS opendaq +) + +set(ASAM_CMP_ENABLE_EXAMPLE OFF CACHE BOOL "") +set(ASAM_CMP_BUILD_DATA_SINK ON CACHE BOOL "") +set(ASAM_CMP_BUILD_CAPTURE_MODULE ON CACHE BOOL "") diff --git a/cmake/Packing.cmake b/cmake/Packing.cmake new file mode 100644 index 0000000..05df24d --- /dev/null +++ b/cmake/Packing.cmake @@ -0,0 +1,67 @@ +## +## The bundle's targets as packages. A package preset picks what goes in — stagings × components — and the +## generator and the name; see PackingProject.cmake. The utils name the package --, +## as the core's and the modules', and write staging-meta.json next to it. +## +## cpack --preset core opendaq × Runtime, Development +## cpack --preset modules the four module stagings × Runtime +## + +include_guard(GLOBAL) + +set(BUNDLE_VERSION "${PROJECT_VERSION}" CACHE STRING "Version of the bundle, the version of its packages") + +# each staging's install components, of this build's own variant, by what they are for: Runtime when the name +# says so, Development else — prebuilt, _Runtime/_Development; from sources, the names the layer gave +# (openDAQ_opcua_client_module_Runtime, External); for PackingProject.cmake to pick from at cpack time +foreach(_staging IN LISTS BUNDLE_STAGINGS) + FetchLayer_GetProperties(${_staging}) + string(TOLOWER "${_staging}" _name) + string(MAKE_C_IDENTIFIER "${_name}" _c) + set(CPACK_BUNDLE_${_c}_Runtime) + set(CPACK_BUNDLE_${_c}_Development) + foreach(_component IN LISTS ${_name}_INSTALL_COMPONENTS) + if (_component MATCHES "_Runtime$") + list(APPEND CPACK_BUNDLE_${_c}_Runtime ${_component}) + else() + list(APPEND CPACK_BUNDLE_${_c}_Development ${_component}) + endif() + endforeach() +endforeach() + +# the name, the version and the triplet, by the utils; a package preset renames it +set(CPACK_OPENDAQ_META_PACKAGE_NAME "opendaq-bundle") +set(CPACK_OPENDAQ_META_PACKAGE_VERSION "${BUNDLE_VERSION}") +opendaq_detect_settings() +opendaq_detect_package() +__FetchLayer_axis_get(triplet NATIVE _triplet) # this build's own, BUNDLE_TRIPLET when given +opendaq_compose_package_triplet() +set(CPACK_OPENDAQ_META_PACKAGE_TRIPLET "${_triplet}") +opendaq_compose_package_file_name() + +set(CPACK_PACKAGE_NAME "${CPACK_OPENDAQ_META_PACKAGE_NAME}") +set(CPACK_PACKAGE_VENDOR "openDAQ d.o.o.") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "openDAQ SDK with its modules") +set(CPACK_PACKAGE_HOMEPAGE_URL "https://opendaq.io/") +set(CPACK_PACKAGE_CONTACT "info@opendaq.io") +set(CPACK_PACKAGE_VERSION "${BUNDLE_VERSION}") +set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}/packages") + +set(CPACK_GENERATOR TGZ) +set(CPACK_VERBATIM_VARIABLES ON) +set(CPACK_THREADS 0) + +# the components picked go into one package +set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) +set(CPACK_DEB_COMPONENT_INSTALL ON) +set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) + +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_VENDOR} <${CPACK_PACKAGE_CONTACT}>") +set(CPACK_DEBIAN_PACKAGE_SECTION "devel") +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS "opt/opendaq/lib") + +# the layers and components, the name and the install prefix, at cpack time +set(CPACK_PROJECT_CONFIG_FILE "${CMAKE_CURRENT_LIST_DIR}/PackingProject.cmake") + +include(CPack) diff --git a/cmake/PackingProject.cmake b/cmake/PackingProject.cmake new file mode 100644 index 0000000..59e88b5 --- /dev/null +++ b/cmake/PackingProject.cmake @@ -0,0 +1,39 @@ +## +## Read by cpack for every generator (CPACK_PROJECT_CONFIG_FILE): an installer puts the bundle +## under /opt/opendaq, where the core installs itself; an archive holds the tree as it is. +## +## The package preset picks the stagings, BUNDLE_PACK_STAGINGS, and their components, BUNDLE_PACK_COMPONENTS +## (Runtime, Development): the install components Packing.cmake recorded for each, CPACK_BUNDLE__, +## the staging's name made a C identifier. The utils' config then names the package by CPACK_OPENDAQ_META_PACKAGE_NAME and writes its +## staging-meta.json. +## + +if (CPACK_GENERATOR MATCHES "^(DEB|RPM|productbuild)$") + set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/opendaq") +else() + set(CPACK_PACKAGING_INSTALL_PREFIX "/") +endif() + +if (NOT BUNDLE_PACK_STAGINGS OR NOT BUNDLE_PACK_COMPONENTS) + message(FATAL_ERROR "cpack: pick what to pack, -D BUNDLE_PACK_STAGINGS=… -D BUNDLE_PACK_COMPONENTS=Runtime… " + "(a package preset does); the build has: ${CPACK_COMPONENTS_ALL}") +endif() +set(_picked) +foreach(_staging IN LISTS BUNDLE_PACK_STAGINGS) + string(MAKE_C_IDENTIFIER "${_staging}" _c) + set(_found FALSE) + foreach(_component IN LISTS BUNDLE_PACK_COMPONENTS) + if (CPACK_BUNDLE_${_c}_${_component}) + list(APPEND _picked ${CPACK_BUNDLE_${_c}_${_component}}) + set(_found TRUE) + endif() + endforeach() + if (NOT _found) + message(FATAL_ERROR "cpack: nothing of ${_staging} to pack (${BUNDLE_PACK_COMPONENTS}); the build has: ${CPACK_COMPONENTS_ALL}") + endif() +endforeach() +set(CPACK_COMPONENTS_ALL ${_picked}) + +include("${CPACK_OPENDAQ_PROJECT_CONFIG}") +set(CPACK_PACKAGE_NAME "${CPACK_OPENDAQ_META_PACKAGE_NAME}") +set(CPACK_DEBIAN_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}.deb") diff --git a/tests/axis.cmake b/tests/axis.cmake new file mode 100644 index 0000000..337d8b0 --- /dev/null +++ b/tests/axis.cmake @@ -0,0 +1,77 @@ +# Axes and variants, without a store: cmake -P tests/axis.cmake +cmake_minimum_required(VERSION 3.25) +include("${CMAKE_CURRENT_LIST_DIR}/../cmake/FetchLayer.cmake") + +set(_failed FALSE) +function(expect WHAT GOT WANT) + if (GOT STREQUAL WANT) + message(STATUS "ok ${WHAT}: ${GOT}") + else() + message(SEND_ERROR "FAIL ${WHAT}: ${GOT}, not ${WANT}") + endif() +endfunction() + +set(A x86_64-macos-apple-clang-21-release) +set(B x86_64-linux-gcc-11-release) + +# triplet, declared from FETCHLAYER_TRIPLET when nothing declared it +set(FETCHLAYER_TRIPLET ${A}) +__FetchLayer_axis_get(triplet NATIVE _native) +expect("triplet from FETCHLAYER_TRIPLET" "${_native}" "${A}") +FetchLayer_DeclareAxis(triplet NATIVE ${B}) # ignored: first wins +__FetchLayer_axis_get(triplet NATIVE _native) +expect("first declaration wins" "${_native}" "${A}") + +FetchLayer_DeclareAxis(python NATIVE 3.12 VALUES 3.11 3.12) +__FetchLayer_axis_get(python VALUES _values) +expect("values" "${_values}" "3.11;3.12") + +# variants: the triplet first, then the others by name; values back +__FetchLayer_variant_format(_variant python=3.12 triplet=${A}) +expect("variant, triplet and python" "${_variant}" "${A}+python=3.12") +__FetchLayer_variant_format(_variant triplet=${A}) +expect("variant, triplet alone: the triplet" "${_variant}" "${A}") +__FetchLayer_variant_format(_variant python=3.12) +expect("variant, no triplet" "${_variant}" "python=3.12") +__FetchLayer_variant_format(_variant) +expect("variant, no axis" "${_variant}" "any") +__FetchLayer_variant_parse("${A}+python=3.12" _pairs) +expect("values of a variant" "${_pairs}" "triplet=${A};python=3.12") +__FetchLayer_variant_extract("${A}+python=3.12" python _python) +expect("value of an axis" "${_python}" "3.12") + +# native: every axis at its native value +__FetchLayer_variant_native("${A}+python=3.12" _native) +expect("native" "${_native}" "TRUE") +__FetchLayer_variant_native("${A}+python=3.11" _native) +expect("another python is not native" "${_native}" "FALSE") +__FetchLayer_variant_native("${B}" _native) +expect("another triplet is not native" "${_native}" "FALSE") +__FetchLayer_variant_native("any" _native) +expect("no axis is native" "${_native}" "TRUE") + +# stagings: the variants to make available; a dependency's variant by its own axes +FetchLayer_Declare(core REVISION 0000000000000000000000000000000000000000) +FetchLayer_DeclareStaging(runtime LAYER core) # triplet by default +FetchLayer_DeclareStaging(binding LAYER core AXES triplet python DEPENDS runtime) +FetchLayer_DeclareStaging(schema LAYER core AXES) # none +__FetchLayer_staging_get(runtime AXES _axes) +expect("axes by default" "${_axes}" "triplet") +__FetchLayer_staging_get(binding AXES _axes) +expect("axes declared" "${_axes}" "triplet;python") +__FetchLayer_staging_get(binding DEPENDS _depends) +expect("DEPENDS after AXES" "${_depends}" "runtime") +__FetchLayer_staging_get(schema AXES _axes) +expect("axes declared empty" "${_axes}" "") +__FetchLayer_variant_list(runtime _variants) +expect("variants, triplet" "${_variants}" "${A}") +__FetchLayer_variant_list(binding _variants) +expect("variants, triplet × python" "${_variants}" "${A}+python=3.11;${A}+python=3.12") +__FetchLayer_variant_list(schema _variants) +expect("variants, no axis" "${_variants}" "any") +__FetchLayer_variant_select(runtime "${A}+python=3.11" _dependency) +expect("a dependency by its own axes" "${_dependency}" "${A}") +__FetchLayer_variant_select(binding "${A}" _variant) +expect("an axis missing takes its native value" "${_variant}" "${A}+python=3.12") +__FetchLayer_variant_select(schema "${A}+python=3.11" _variant) +expect("a dependency without axes" "${_variant}" "any") diff --git a/tests/fetchlayer-axes/CMakeLists.txt b/tests/fetchlayer-axes/CMakeLists.txt new file mode 100644 index 0000000..67c8d1a --- /dev/null +++ b/tests/fetchlayer-axes/CMakeLists.txt @@ -0,0 +1,77 @@ +# Variants by two axes, triplet and python, against a store this test lays out itself; no network. +# cmake -S tests/fetchlayer-axes -B build/fetchlayer-axes +# The layer's decision: -DWITHOUT_OWN_BINDING=ON leaves binding for python 3.12, this build's own, out of the +# store, so the layer core is to come from its sources — core too — or, with -DFETCHLAYER_BUILD=NEVER, fails. +cmake_minimum_required(VERSION 3.25) +project(fetchlayer_axes_test NONE) + +include("${CMAKE_CURRENT_LIST_DIR}/../../cmake/FetchLayer.cmake") + +function(expect WHAT GOT WANT) + if (GOT STREQUAL WANT) + message(STATUS "ok ${WHAT}: ${GOT}") + else() + message(SEND_ERROR "FAIL ${WHAT}: ${GOT}, not ${WANT}") + endif() +endfunction() + +set(_triplet x86_64-macos-apple-clang-21-release) +set(_commit 1111111111111111111111111111111111111111) + +FetchLayer_DeclareAxis(triplet NATIVE ${_triplet}) +FetchLayer_DeclareAxis(python NATIVE 3.12 VALUES 3.11 3.12) + +FetchLayer_Declare(core REVISION ${_commit}) +FetchLayer_DeclareStaging(core LAYER core) +FetchLayer_DeclareStaging(binding LAYER core AXES triplet python DEPENDS core) + +# the store: core for the triplet, binding for python 3.11 and 3.12 — each a directory with a library in it +set(FETCHLAYER_STORE "${CMAKE_BINARY_DIR}/store") +file(REMOVE_RECURSE "${FETCHLAYER_STORE}") +function(lay NAME VARIANT DEPENDS LIBRARY) + set(r_NAME "${NAME}") + set(r_REVISION "${_commit}") + set(r_DEPENDS_REVISIONS "${DEPENDS}") + set(r_VARIANT "${VARIANT}") + __FetchLayer_identity_path(r _path) + file(WRITE "${_path}/lib/${LIBRARY}" "") +endfunction() +lay(core "${_triplet}" "" "libcore.dylib") +lay(binding "${_triplet}+python=3.11" "core=${_commit}" "binding.cpython-311-darwin.so") +if (NOT WITHOUT_OWN_BINDING) + lay(binding "${_triplet}+python=3.12" "core=${_commit}" "binding.cpython-312-darwin.so") +endif() + +FetchLayer_MakeAvailable(core binding) + +# the layer's decision: every own variant asked for is in the store, so all prebuilt +__FetchLayer_layer_get(core ORIGIN _origin) +expect("the layer, prebuilt" "${_origin}" "system") + +# the variants: one of core, two of binding — binding found in the store only if core, its dependency, was +# taken by its own axis alone: the path is the hash of core's commit +FetchLayer_GetProperties(core) +expect("core, its variants" "${core_VARIANTS}" "${_triplet}") +FetchLayer_GetProperties(binding) +expect("binding, its variants" "${binding_VARIANTS}" "${_triplet}+python=3.11;${_triplet}+python=3.12") + +# binding by default: the own variant, python 3.12, installed +expect("binding, own variant" "${binding_VARIANT}" "${_triplet}+python=3.12") +expect("binding, own: python" "${binding_PYTHON}" "3.12") +expect("binding, own: triplet" "${binding_TRIPLET}" "${_triplet}") +expect("binding, own: exact" "${binding_IDENTITY_MATCH}" "EXACT") +expect("binding, own: installed" "${binding_INSTALL_COMPONENTS}" "binding_Runtime") +expect("binding, own: against core" "${binding_DEPENDS_REVISIONS}" "core=${_commit}") + +# binding for python 3.11, by its axis: found, files only +FetchLayer_GetProperties(binding AXES python=3.11) +expect("binding 3.11, variant" "${binding_VARIANT}" "${_triplet}+python=3.11") +expect("binding 3.11, populated" "${binding_POPULATED}" "TRUE") +expect("binding 3.11, files only" "${binding_INSTALL_COMPONENTS}" "") +if (NOT EXISTS "${binding_PREFIX}/lib/binding.cpython-311-darwin.so") + message(SEND_ERROR "FAIL binding 3.11, prefix: ${binding_PREFIX}") +endif() + +# by its string, as one of binding_VARIANTS +FetchLayer_GetProperties(binding VARIANT "${_triplet}+python=3.12") +expect("binding by its string" "${binding_PYTHON}" "3.12") diff --git a/tests/fetchlayer-git/CMakeLists.txt b/tests/fetchlayer-git/CMakeLists.txt new file mode 100644 index 0000000..d071fc5 --- /dev/null +++ b/tests/fetchlayer-git/CMakeLists.txt @@ -0,0 +1,100 @@ +# The origin git alone: resolve and populate called directly, as find and MakeAvailable will. +# cmake -S tests/fetchlayer-git -B build/fetchlayer-git -G Ninja +cmake_minimum_required(VERSION 3.25) +project(fetchlayer_git_test NONE) + +include("${CMAKE_CURRENT_LIST_DIR}/../../cmake/FetchLayer.cmake") +set(FETCHLAYER_TRIPLET x86_64-macos-apple-clang-21-release) +set(_repository https://github.com/openDAQ/OpcUaModules.git) + +function(expect WHAT GOT WANT) + if (GOT STREQUAL WANT) + message(STATUS "ok ${WHAT}: ${GOT}") + else() + message(SEND_ERROR "FAIL ${WHAT}: ${GOT}, not ${WANT}") + endif() +endfunction() + +# resolve [] → _c_* +function(resolve NAME REVISION) + set(_triplet "${FETCHLAYER_TRIPLET}") + if (ARGC GREATER 2) + set(_triplet "${ARGV2}") + endif() + set(r_NAME "${NAME}") + set(r_REVISION "${REVISION}") + set(r_DEPENDS_REVISIONS "opendaq=3039ee6d068c6c5e6e4d3de367c91820088ab30e") + set(r_TRIPLET "${_triplet}") + __FetchLayer_git_resolve("${NAME}" "${_triplet}" r c) + foreach(_field ORIGIN REF REVISION DEPENDS_REVISIONS TRIPLET IDENTITY_MATCH) + set(${NAME}_c_${_field} "${c_${_field}}" PARENT_SCOPE) + endforeach() +endfunction() + +# resolve: the commit, exact, the dependencies as requested +FetchLayer_Declare(opcua-sha REVISION ad11c59ad43348f7099b794d8d8c5c94b8e9ccf5 GIT_REPOSITORY ${_repository}) +FetchLayer_DeclareStaging(opcua-sha LAYER opcua-sha) +resolve(opcua-sha ad11c59ad43348f7099b794d8d8c5c94b8e9ccf5) +expect("resolve" "${opcua-sha_c_REVISION}" "ad11c59ad43348f7099b794d8d8c5c94b8e9ccf5") +expect("resolve, exact" "${opcua-sha_c_IDENTITY_MATCH}" "EXACT") +expect("resolve, depends as requested" "${opcua-sha_c_DEPENDS_REVISIONS}" "opendaq=3039ee6d068c6c5e6e4d3de367c91820088ab30e") + +# the revision as a commit, before any origin: a full sha as it is; an annotated tag by the commit it points to; +# a lightweight tag; a branch +__FetchLayer_git_commit(opcua-sha _commit) +expect("commit, full sha" "${_commit}" "ad11c59ad43348f7099b794d8d8c5c94b8e9ccf5") +FetchLayer_Declare(opcua-tag REVISION v4.0.0 GIT_REPOSITORY ${_repository}) +FetchLayer_DeclareStaging(opcua-tag LAYER opcua-tag) +__FetchLayer_git_commit(opcua-tag _commit) +expect("commit, annotated tag" "${_commit}" "3e02ec1330224b819bfc5342d9b00a303dd4d9ee") +__FetchLayer_layer_get(opcua-tag REVISION _declared) +expect("commit, written over the declared tag" "${_declared}" "3e02ec1330224b819bfc5342d9b00a303dd4d9ee") +FetchLayer_Declare(opcua-light REVISION v4.0.1 GIT_REPOSITORY ${_repository}) +FetchLayer_DeclareStaging(opcua-light LAYER opcua-light) +__FetchLayer_git_commit(opcua-light _commit) +expect("commit, lightweight tag" "${_commit}" "4ed45f0102abc5d9489b8d3151e5c72da1440544") +FetchLayer_Declare(opcua-branch REVISION release/4.0 GIT_REPOSITORY ${_repository}) +FetchLayer_DeclareStaging(opcua-branch LAYER opcua-branch) +__FetchLayer_git_commit(opcua-branch _commit) +expect("commit, branch" "${_commit}" "4ed45f0102abc5d9489b8d3151e5c72da1440544") + +# not git's: another triplet; a layer without GIT_REPOSITORY +resolve(opcua-sha ad11c59ad43348f7099b794d8d8c5c94b8e9ccf5 x86_64-linux-gcc-11-release) +expect("another triplet" "${opcua-sha_c_ORIGIN}" "") +FetchLayer_Declare(no-repository REVISION ad11c59ad43348f7099b794d8d8c5c94b8e9ccf5) +FetchLayer_DeclareStaging(no-repository LAYER no-repository) +resolve(no-repository ad11c59ad43348f7099b794d8d8c5c94b8e9ccf5) +expect("no GIT_REPOSITORY" "${no-repository_c_ORIGIN}" "") + +# populate: the commit, downloaded only; main's (ad11c59 is on a branch since deleted, out of a clone's reach) +FetchLayer_Declare(opcua-main REVISION 485710c37d06bcc6b6c852e57fd27c0b2cce7d08 GIT_REPOSITORY ${_repository}) +FetchLayer_DeclareStaging(opcua-main LAYER opcua-main) +resolve(opcua-main 485710c37d06bcc6b6c852e57fd27c0b2cce7d08) +__FetchLayer_instance_set(opcua-main ${FETCHLAYER_TRIPLET} REVISION "${opcua-main_c_REVISION}") +__FetchLayer_git_populate(opcua-main ${FETCHLAYER_TRIPLET}) +__FetchLayer_instance_get(opcua-main ${FETCHLAYER_TRIPLET} SOURCE_DIR _source) +__FetchLayer_instance_get(opcua-main ${FETCHLAYER_TRIPLET} BINARY_DIR _binary) +expect("populate, sources" "${_source}" "${CMAKE_BINARY_DIR}/_deps/opcua-main-src") +expect("populate, binary dir" "${_binary}" "${CMAKE_BINARY_DIR}/_deps/opcua-main-build") +if (NOT EXISTS "${_source}/CMakeLists.txt") + message(SEND_ERROR "FAIL populate: no ${_source}/CMakeLists.txt") +endif() +__FetchLayer_git_run(_head -C "${_source}" rev-parse HEAD) +expect("populate, commit" "${_head}" "485710c37d06bcc6b6c852e57fd27c0b2cce7d08") +if (TARGET opcua_client_module) + message(SEND_ERROR "FAIL populate added the project") +endif() + +# a local checkout: its HEAD, warned when not the declared one +get_filename_component(_local "${CMAKE_CURRENT_LIST_DIR}/../../../OpcUaModules" ABSOLUTE) +set(FETCHCONTENT_SOURCE_DIR_OPCUA-LOCAL "${_local}") +FetchLayer_Declare(opcua-local REVISION ad11c59ad43348f7099b794d8d8c5c94b8e9ccf5 GIT_REPOSITORY ${_repository}) +FetchLayer_DeclareStaging(opcua-local LAYER opcua-local) +resolve(opcua-local ad11c59ad43348f7099b794d8d8c5c94b8e9ccf5) +__FetchLayer_git_run(_local_head -C "${_local}" rev-parse HEAD) +expect("local checkout, its HEAD" "${opcua-local_c_REVISION}" "${_local_head}") +expect("local checkout, exact" "${opcua-local_c_IDENTITY_MATCH}" "EXACT") +__FetchLayer_instance_set(opcua-local ${FETCHLAYER_TRIPLET} REVISION "${opcua-local_c_REVISION}") +__FetchLayer_git_populate(opcua-local ${FETCHLAYER_TRIPLET}) +__FetchLayer_instance_get(opcua-local ${FETCHLAYER_TRIPLET} SOURCE_DIR _source) +expect("local checkout, sources" "${_source}" "${_local}") diff --git a/tests/fetchlayer/CMakeLists.txt b/tests/fetchlayer/CMakeLists.txt new file mode 100644 index 0000000..161cfff --- /dev/null +++ b/tests/fetchlayer/CMakeLists.txt @@ -0,0 +1,49 @@ +# The first iteration of FetchLayer against a filled store: the core and the four module layers, +# prebuilt, for this machine's triplet. +# cmake -S tests/fetchlayer -B build/fetchlayer -G Ninja -DFETCHLAYER_STORE=$PWD/build/store \ +# -DFETCHLAYER_TRIPLET=x86_64-macos-apple-clang-21-release +cmake_minimum_required(VERSION 3.25) +project(fetchlayer_test LANGUAGES CXX) + +include("${CMAKE_CURRENT_LIST_DIR}/../../cmake/FetchLayer.cmake") +set(FETCHLAYER_IDENTITY_REVISION_KEY "org.opencontainers.image.revision") +set(FETCHLAYER_IDENTITY_DEPENDENCY_KEY "com.opendaq.dependencies..revision") +set(FETCHLAYER_IDENTITY_AXIS_KEY "com.opendaq.package.") + +FetchLayer_Declare(openDAQ REVISION 3039ee6d068c6c5e6e4d3de367c91820088ab30e) +FetchLayer_Declare(OpcUaModules REVISION ad11c59ad43348f7099b794d8d8c5c94b8e9ccf5) +FetchLayer_Declare(MQTTStreamingModule REVISION 892f654d11a1dac7b18f9f21265c148010568c59) +FetchLayer_Declare(LTStreamingModulesModern REVISION caa7a57c3a6f838bcf1477b96937073ef4b2b0e3) +FetchLayer_Declare(ASAM-CMP-Modules REVISION 2f643a2c83f59ade54c156900e1f1088268429bb) +FetchLayer_Declare(openDAQ REVISION 0000000000000000000000000000000000000000) # ignored: first wins + +FetchLayer_DeclareStaging(opendaq LAYER openDAQ FIND_PACKAGE_ARGS NAMES openDAQ) +FetchLayer_DeclareStaging(opcua-modules LAYER OpcUaModules DEPENDS opendaq) +FetchLayer_DeclareStaging(mqtt-module LAYER MQTTStreamingModule DEPENDS opendaq) +FetchLayer_DeclareStaging(lt-streaming-modules LAYER LTStreamingModulesModern DEPENDS opendaq) +FetchLayer_DeclareStaging(asam-cmp-modules LAYER ASAM-CMP-Modules DEPENDS opendaq) +FetchLayer_DeclareStaging(opendaq LAYER OpcUaModules) # ignored: first wins + +FetchLayer_MakeAvailable(opendaq opcua-modules mqtt-module lt-streaming-modules asam-cmp-modules) + +foreach(_l opendaq opcua-modules mqtt-module lt-streaming-modules asam-cmp-modules) + FetchLayer_GetProperties(${_l}) + message(STATUS "${_l}: ${${_l}_ORIGIN} ${${_l}_IDENTITY_MATCH} rev=${${_l}_REVISION} deps=${${_l}_DEPENDS_REVISIONS}") + message(STATUS " components=${${_l}_INSTALL_COMPONENTS}") +endforeach() +foreach(_t daq::opendaq opcua-modules::opcua_client_module asam-cmp-modules::asam_cmp_capture_module) + if (TARGET ${_t}) + message(STATUS "target ${_t}: yes") + else() + message(SEND_ERROR "target ${_t}: missing") + endif() +endforeach() + +# links against the core from the store and loads the modules from it +add_executable(smoke smoke.cpp) +target_link_libraries(smoke PRIVATE daq::opendaq) +FetchLayer_GetProperties(opcua-modules) +get_filename_component(_modules "${opcua-modules_PREFIX}/lib/modules" ABSOLUTE) +target_compile_definitions(smoke PRIVATE MODULES_DIR="${_modules}") +FetchLayer_GetProperties(opendaq) +set_target_properties(smoke PROPERTIES BUILD_RPATH "${opendaq_PREFIX}/lib") diff --git a/tests/fetchlayer/smoke.cpp b/tests/fetchlayer/smoke.cpp new file mode 100644 index 0000000..aea146a --- /dev/null +++ b/tests/fetchlayer/smoke.cpp @@ -0,0 +1,12 @@ +// Loads the modules of one layer from the store into the core from the store. +#include +#include + +int main() +{ + const auto instance = daq::Instance(MODULES_DIR); + const auto modules = instance.getModuleManager().getModules(); + for (const auto& module : modules) + std::cout << "loaded " << module.getModuleInfo().getId() << "\n"; + return modules.getCount() > 0 ? 0 : 1; +} diff --git a/tests/identity.cmake b/tests/identity.cmake new file mode 100644 index 0000000..9816549 --- /dev/null +++ b/tests/identity.cmake @@ -0,0 +1,84 @@ +# cmake -P tests/identity.cmake — the identity hash against values computed apart (Python, the registry tags) + +include("${CMAKE_CURRENT_LIST_DIR}/../cmake/FetchLayer/identity.cmake") + +# the keys staging-push annotates packages with +set(FETCHLAYER_IDENTITY_REVISION_KEY "org.opencontainers.image.revision") +set(FETCHLAYER_IDENTITY_DEPENDENCY_KEY "com.opendaq.dependencies..revision") +set(FETCHLAYER_IDENTITY_AXIS_KEY "com.opendaq.package.") + +set(_failed 0) +function(expect WHAT GOT WANT) + if (GOT STREQUAL WANT) + message(STATUS "ok ${WHAT}") + else() + message(STATUS "FAIL ${WHAT}\n got ${GOT}\n want ${WANT}") + set(_failed 1 PARENT_SCOPE) + endif() +endfunction() + +set(_core 3039ee6d068c6c5e6e4d3de367c91820088ab30e) +set(_opcua ad11c59ad43348f7099b794d8d8c5c94b8e9ccf5) +set(_triplet x86_64-macos-apple-clang-21-release) + +# the core: no dependencies +set(core_NAME opendaq) +set(core_REVISION ${_core}) +__FetchLayer_identity_hash(core idx _got) +expect("core, layer" "${_got}" "idx-371b0bd304e31a41c4d93940e84aa4630c7bbdb1ba1d5f9d6d3376c6e7930f1e") +set(core_VARIANT ${_triplet}) +__FetchLayer_identity_hash(core pkg _got) +expect("core, build" "${_got}" "pkg-293b916ec80f1e85b403b0819e799ca6a873b109511e552f429ac56483db665c") + +# a module against the core +set(opcua_NAME opcua-modules) +set(opcua_REVISION ${_opcua}) +set(opcua_DEPENDS_REVISIONS "opendaq=${_core}") +__FetchLayer_identity_hash(opcua idx _got) +expect("module, layer" "${_got}" "idx-8bf766d81df141f0afbf3004975cb0996f54338dffeea01a05ac2a6ff8c3881e") +set(opcua_VARIANT ${_triplet}) +__FetchLayer_identity_hash(opcua pkg _got) +expect("module, build" "${_got}" "pkg-e2337ceee0d7acbc2b20adc6e951948125d12f6ec737bc694ace919f142f6f0b") + +# the order of the dependencies does not count +set(a_REVISION ${_opcua}) +set(a_DEPENDS_REVISIONS "opendaq=${_core};libx=1111111") +set(b_REVISION ${_opcua}) +set(b_DEPENDS_REVISIONS "libx=1111111;opendaq=${_core}") +__FetchLayer_identity_hash(a idx _a) +__FetchLayer_identity_hash(b idx _b) +expect("order of dependencies" "${_a}" "${_b}") + +# another axis: a line of its own in the variant's hash; the staging's hash is the same +set(binding_NAME opendaq-python) +set(binding_REVISION ${_core}) +set(binding_DEPENDS_REVISIONS "opendaq=${_core}") +__FetchLayer_identity_hash(binding idx _binding_idx) +set(binding_VARIANT "${_triplet}+python=3.12") +__FetchLayer_identity_hash(binding pkg _binding_312) +set(binding_VARIANT "${_triplet}+python=3.11") +__FetchLayer_identity_hash(binding pkg _binding_311) +if (_binding_312 STREQUAL _binding_311) + expect("another python, another variant's hash" "${_binding_312}" "not ${_binding_311}") +else() + expect("another python, another variant's hash" "" "") +endif() +set(_lines "com.opendaq.dependencies.opendaq.revision=${_core}\ncom.opendaq.package.python=3.12\ncom.opendaq.package.triplet=${_triplet}\norg.opencontainers.image.revision=${_core}") +string(SHA256 _want "${_lines}") +set(binding_VARIANT "${_triplet}+python=3.12") +__FetchLayer_identity_hash(binding pkg _got) +expect("a variant's hash is its annotations' lines" "${_got}" "pkg-${_want}") +set(binding_VARIANT "") +__FetchLayer_identity_hash(binding idx _got) +expect("a staging's hash leaves the variant out" "${_got}" "${_binding_idx}") + +# the place in the store: the staging's hash, then the variant +set(FETCHLAYER_STORE /store) +__FetchLayer_identity_path(opcua _got) +expect("store path" "${_got}" + "/store/opcua-modules/idx-8bf766d81df141f0afbf3004975cb0996f54338dffeea01a05ac2a6ff8c3881e/${_triplet}") + +if (_failed) + message(FATAL_ERROR "identity: some checks failed") +endif() +message(STATUS "identity: all checks passed") diff --git a/tools/fill-store.cmake b/tools/fill-store.cmake new file mode 100644 index 0000000..62fffa2 --- /dev/null +++ b/tools/fill-store.cmake @@ -0,0 +1,161 @@ +# A debugging aid, not part of the design: fills a store from the registry, every triplet of every layer. +# +# cmake -DSTORE=build/store -DREGISTRY=ghcr.io/opendaq/dev/staging \ +# -DLAYERS="opendaq=;opcua-modules=;…" \ +# [-DORAS_REGISTRY_CONFIG=~/.oras/config.json] -P tools/fill-store.cmake +# +# For each layer: the newest index whose revision is the one given; for each package in it, the identity +# from its annotations, the place from cmake/FetchLayer/identity.cmake, the tarball into +# /.downloads/sha256-.tar.gz, unpacked beside the place and renamed into it (opt/opendaq taken +# off), so a place is whole once it exists. A place already there is kept. + +cmake_minimum_required(VERSION 3.25) +include("${CMAKE_CURRENT_LIST_DIR}/../cmake/FetchLayer/identity.cmake") + +set(FETCHLAYER_IDENTITY_REVISION_KEY "org.opencontainers.image.revision") +set(FETCHLAYER_IDENTITY_DEPENDENCY_KEY "com.opendaq.dependencies..revision") +set(FETCHLAYER_IDENTITY_AXIS_KEY "com.opendaq.package.") + +foreach(_var STORE REGISTRY LAYERS) + if (NOT ${_var}) + message(FATAL_ERROR "fill-store: -D${_var}= is required") + endif() +endforeach() +get_filename_component(STORE "${STORE}" ABSOLUTE) +set(FETCHLAYER_STORE "${STORE}") + +find_program(ORAS oras REQUIRED) +set(_config) +if (ORAS_REGISTRY_CONFIG) + set(_config --registry-config "${ORAS_REGISTRY_CONFIG}") +endif() + +function(oras OUT) + cmake_parse_arguments(PARSE_ARGV 1 ARG "OPTIONAL" "" "") + execute_process(COMMAND "${ORAS}" ${ARG_UNPARSED_ARGUMENTS} ${_config} + OUTPUT_VARIABLE _out ERROR_VARIABLE _err RESULT_VARIABLE _rc OUTPUT_STRIP_TRAILING_WHITESPACE) + if (NOT _rc EQUAL 0) + if (NOT ARG_OPTIONAL) + list(JOIN ARG_UNPARSED_ARGUMENTS " " _args) + message(FATAL_ERROR "oras ${_args}:\n${_err}") + endif() + set(_out "") + endif() + set(${OUT} "${_out}" PARENT_SCOPE) +endfunction() + +# the newest index of whose revision is +function(find_index OUT REPO REVISION) + oras(_tags repo tags "${REPO}") + string(REGEX REPLACE "[\r\n]+" ";" _tags "${_tags}") + set(_best "") + set(_best_created "") + set(_seen) + foreach(_tag IN LISTS _tags) + if (NOT _tag OR _tag MATCHES "^pkg-") + continue() + endif() + oras(_index manifest fetch "${REPO}:${_tag}" OPTIONAL) + string(SHA256 _h "${_index}") + if (NOT _index OR _h IN_LIST _seen) + continue() + endif() + list(APPEND _seen ${_h}) + string(JSON _rev ERROR_VARIABLE _e GET "${_index}" annotations "org.opencontainers.image.revision") + if (NOT _rev STREQUAL REVISION) + continue() + endif() + string(JSON _created ERROR_VARIABLE _e GET "${_index}" annotations "org.opencontainers.image.created") + if (NOT _best OR _created STRGREATER _best_created) + set(_best "${_index}") + set(_best_created "${_created}") + endif() + endforeach() + if (NOT _best) + message(FATAL_ERROR "fill-store: no index of ${REPO} has revision ${REVISION}") + endif() + set(${OUT} "${_best}" PARENT_SCOPE) +endfunction() + +# the dependency pairs = in an annotations object +function(dependencies OUT ANNOTATIONS) + set(_pairs) + string(JSON _n LENGTH "${ANNOTATIONS}") + math(EXPR _last "${_n} - 1") + foreach(_i RANGE ${_last}) + string(JSON _key MEMBER "${ANNOTATIONS}" ${_i}) + if (_key MATCHES "^com\\.opendaq\\.dependencies\\.(.+)\\.revision$") + set(_dep "${CMAKE_MATCH_1}") + string(JSON _sha GET "${ANNOTATIONS}" "${_key}") + list(APPEND _pairs "${_dep}=${_sha}") + endif() + endforeach() + list(SORT _pairs) + set(${OUT} "${_pairs}" PARENT_SCOPE) +endfunction() + +set(_total 0) +set(_kept 0) +foreach(_entry IN LISTS LAYERS) + string(REGEX MATCH "^([^=]+)=(.+)$" _ "${_entry}") + set(_layer "${CMAKE_MATCH_1}") + set(_revision "${CMAKE_MATCH_2}") + set(_repo "${REGISTRY}/${_layer}") + message(STATUS "${_layer}@${_revision}") + + find_index(_index "${_repo}" "${_revision}") + string(JSON _index_ann ERROR_VARIABLE _e GET "${_index}" annotations) + string(JSON _count LENGTH "${_index}" manifests) + math(EXPR _last "${_count} - 1") + foreach(_i RANGE ${_last}) + string(JSON _digest GET "${_index}" manifests ${_i} digest) + string(JSON _ann GET "${_index}" manifests ${_i} annotations) + string(JSON _triplet GET "${_ann}" "com.opendaq.package.triplet") + string(JSON _title GET "${_ann}" "org.opencontainers.image.title") + + # the build's identity: the package's annotations, the index's where it has none + set(b_NAME "${_layer}") + string(JSON b_REVISION ERROR_VARIABLE _e GET "${_ann}" "org.opencontainers.image.revision") + if (_e) + string(JSON b_REVISION GET "${_index_ann}" "org.opencontainers.image.revision") + endif() + dependencies(b_DEPENDS_REVISIONS "${_ann}") + if (NOT b_DEPENDS_REVISIONS) + dependencies(b_DEPENDS_REVISIONS "${_index_ann}") + endif() + __FetchLayer_variant_format(b_VARIANT "triplet=${_triplet}") + __FetchLayer_identity_path(b _place) + math(EXPR _total "${_total} + 1") + + if (IS_DIRECTORY "${_place}") + message(STATUS " ${_triplet}: kept") + math(EXPR _kept "${_kept} + 1") + continue() + endif() + + # download once per digest + string(REPLACE ":" "-" _file "${_digest}") + set(_archive "${STORE}/.downloads/${_file}.tar.gz") + if (NOT EXISTS "${_archive}") + set(_tmp "${STORE}/.downloads/.tmp") + file(REMOVE_RECURSE "${_tmp}") + oras(_ pull "${_repo}@${_digest}" -o "${_tmp}") + file(RENAME "${_tmp}/${_title}" "${_archive}") + file(REMOVE_RECURSE "${_tmp}") + endif() + + # unpack; a prefix carried inside the tarball (opt/opendaq) is taken off + set(_unpack "${_place}.tmp") + file(REMOVE_RECURSE "${_unpack}" "${_place}") + file(ARCHIVE_EXTRACT INPUT "${_archive}" DESTINATION "${_unpack}") + if (IS_DIRECTORY "${_unpack}/opt/opendaq") + file(RENAME "${_unpack}/opt/opendaq" "${_place}") + file(REMOVE_RECURSE "${_unpack}") + else() + file(RENAME "${_unpack}" "${_place}") + endif() + + message(STATUS " ${_triplet}: ${_digest}") + endforeach() +endforeach() +message(STATUS "fill-store: ${_total} builds, ${_kept} kept, into ${STORE}") From 86bd0f2c69be42ae213d262ff4b07e55be783a61 Mon Sep 17 00:00:00 2001 From: Aliaksandr Adziareika <8034372+alexadereyko@users.noreply.github.com> Date: Mon, 26 Oct 2026 17:22:45 +0100 Subject: [PATCH 02/44] Build every layer from sources in one tree: post-fetch includes for Boost, the old FetchContent block gone --- CMakeLists.txt | 176 +++----------------------------- README.md | 70 +++++++++++-- cmake/FetchLayer.cmake | 47 +++++++-- cmake/FetchLayer/compile.cmake | 27 ++++- cmake/FetchLayer/instance.cmake | 5 +- cmake/FetchLayer/layer.cmake | 2 + cmake/Layers.cmake | 46 ++++++--- 7 files changed, 178 insertions(+), 195 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b7bee14..0b4f967 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,20 +1,14 @@ set(CMAKE_POLICY_VERSION_MINIMUM 3.5) cmake_minimum_required(VERSION 3.25) -set(REPO_NAME ReleaseRepo) -set(REPO_OPTION_PREFIX RELEASE_REPO) -set(CMAKE_FOLDER "${CMAKE_FOLDER}/${REPO_NAME}") +project(openDAQ-bundle VERSION 1.0.0 LANGUAGES CXX) - -list(APPEND CMAKE_MESSAGE_CONTEXT ${REPO_NAME}) - -project(${REPO_NAME} VERSION "1.0.0" LANGUAGES CXX) - -option(${REPO_OPTION_PREFIX}_ENABLE_TESTS "Enable testing" OFF) -option(${REPO_OPTION_PREFIX}_ENABLE_OPCUA_MODULE "Enable OPC UA module" ON) -option(${REPO_OPTION_PREFIX}_ENABLE_MQTT_MODULE "Enable MQTT module" ON) -option(${REPO_OPTION_PREFIX}_ENABLE_LT_MODULE "Enable LT streaming module" ON) -option(${REPO_OPTION_PREFIX}_ENABLE_ASAM_CMP_MODULE "Enable ASAM-CMP modules" ON) +# The bundle: the stagings named, each from where the policy says — prebuilt from the store, or built from its +# layer's sources (cmake/FetchLayer.cmake). What the bundle is made of is cmake/Layers.cmake; how it is packed, +# cmake/Packing.cmake. +set(BUNDLE_STAGINGS "opendaq;opcua-modules;mqtt-module;lt-streaming-modules;asam-cmp-modules" + CACHE STRING "The stagings to make available, dependencies first") +option(BUNDLE_ENABLE_TESTS "Build the layers' tests, for the layers built from sources" OFF) include(FetchContent) @@ -27,152 +21,12 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(opendaq-cmake-utils) -# The bundle by FetchLayer: the stagings named, from where the policy says; prebuilt only for now, the source -# build below stays until FetchLayer compiles -set(BUNDLE_STAGINGS "" CACHE STRING "The stagings to make available, dependencies first; empty: the source build below") -if (BUNDLE_STAGINGS) - include(cmake/FetchLayer.cmake) - # the identity keys are the annotations staging-push writes - set(FETCHLAYER_IDENTITY_REVISION_KEY "org.opencontainers.image.revision") - set(FETCHLAYER_IDENTITY_DEPENDENCY_KEY "com.opendaq.dependencies..revision") - set(FETCHLAYER_IDENTITY_AXIS_KEY "com.opendaq.package.") - - include(cmake/Layers.cmake) - FetchLayer_MakeAvailable(${BUNDLE_STAGINGS}) - include(cmake/Packing.cmake) - return() -endif() - -FetchContent_Declare( - opendaq - GIT_REPOSITORY https://github.com/openDAQ/openDAQ.git - GIT_TAG main - GIT_PROGRESS ON - GIT_SHALLOW OFF - SOURCE_SUBDIR "INVALID" - GIT_REMOTE_UPDATE_STRATEGY CHECKOUT -) - -set(OPENDAQ_ENABLE_OPCUA OFF CACHE BOOL "") -set(OPENDAQ_ENABLE_OPCUA_INTEGRATION_TESTS OFF CACHE BOOL "") - -set(OPENDAQ_GENERATE_DELPHI_BINDINGS OFF CACHE BOOL "" FORCE) -set(OPENDAQ_GENERATE_PYTHON_BINDINGS ON CACHE BOOL "") -set(OPENDAQ_GENERATE_CSHARP_BINDINGS ON CACHE BOOL "") -set(OPENDAQ_GENERATE_C_BINDINGS ON CACHE BOOL "") - -set(OPENDAQ_ENABLE_TESTS ${${REPO_OPTION_PREFIX}_ENABLE_TESTS} CACHE BOOL "" FORCE) -set(OPENDAQ_ENABLE_TEST_UTILS ${${REPO_OPTION_PREFIX}_ENABLE_TESTS} CACHE BOOL "" FORCE) -set(OPENDAQ_ENABLE_OPTIONAL_TESTS ${${REPO_OPTION_PREFIX}_ENABLE_TESTS} CACHE BOOL "" FORCE) -set(OPENDAQ_ENABLE_COVERAGE OFF CACHE BOOL "" FORCE) -set(OPENDAQ_ENABLE_REGRESSION_TESTS ${${REPO_OPTION_PREFIX}_ENABLE_TESTS} CACHE BOOL "" FORCE) -set(OPENDAQ_ENABLE_UNSTABLE_TEST_LABELS ${${REPO_OPTION_PREFIX}_ENABLE_TESTS} CACHE BOOL "" FORCE) - -set(DAQMODULES_EMPTY_MODULE ON CACHE BOOL "") -set(DAQMODULES_OPENDAQ_CLIENT_MODULE ON CACHE BOOL "") -set(DAQMODULES_OPENDAQ_SERVER_MODULE ON CACHE BOOL "") -set(DAQMODULES_REF_DEVICE_MODULE ON CACHE BOOL "") -set(DAQMODULES_SIMULATOR_DEVICE_MODULE ON CACHE BOOL "") -set(DAQMODULES_REF_FB_MODULE ON CACHE BOOL "") -set(DAQMODULES_AUDIO_DEVICE_MODULE ON CACHE BOOL "") -set(DAQMODULES_BASIC_CSV_RECORDER_MODULE ON CACHE BOOL "") -set(DAQMODULES_LICENSING_MODULE ON CACHE BOOL "") -set(DAQMODULES_PARQUET_RECORDER_MODULE OFF CACHE BOOL "") - -set(OPENDAQ_ENABLE_NATIVE_STREAMING ON CACHE BOOL "") -set(OPENDAQ_ENABLE_WEBSOCKET_STREAMING OFF CACHE BOOL "") - -set(OPENDAQ_DOCUMENTATION_TESTS OFF CACHE BOOL "" FORCE) -set(OPENDAQ_ENABLE_PYTHON_BINDINGS_TESTS OFF CACHE BOOL "" FORCE) -set(APP_ENABLE_WEBPAGE_EXAMPLES OFF CACHE BOOL "") - -FetchContent_MakeAvailable(opendaq) -include("${opendaq_SOURCE_DIR}/external/boost/Boost.cmake" OPTIONAL) - -if (${REPO_OPTION_PREFIX}_ENABLE_OPCUA_MODULE) -FetchContent_Declare( - opcuamodules - GIT_REPOSITORY https://github.com/openDAQ/OpcUaModules.git - GIT_TAG main - GIT_PROGRESS ON - GIT_SHALLOW OFF - SOURCE_SUBDIR "INVALID" - GIT_REMOTE_UPDATE_STRATEGY CHECKOUT -) - -set(DAQMODULES_OPCUA_ENABLE_EXAMPLE_APP OFF CACHE BOOL "") -set(DAQMODULES_OPCUA_ENABLE_TESTS ${${REPO_OPTION_PREFIX}_ENABLE_TESTS} CACHE BOOL "" FORCE) -set(DAQMODULES_OPCUA_ENABLE_CLIENT ON CACHE BOOL "") -set(DAQMODULES_OPCUA_ENABLE_SERVER ON CACHE BOOL "") -set(DAQMODULES_OPCUA_ENABLE_GENERIC_CLIENT ON CACHE BOOL "") - -FetchContent_MakeAvailable(opcuamodules) -include("${opcuamodules_SOURCE_DIR}/external/boost/Boost.cmake" OPTIONAL) -endif() - -if (${REPO_OPTION_PREFIX}_ENABLE_MQTT_MODULE) -FetchContent_Declare( - mqttmodule - GIT_REPOSITORY https://github.com/openDAQ/MQTTStreamingModule.git - GIT_TAG main - GIT_PROGRESS ON - GIT_SHALLOW OFF - SOURCE_SUBDIR "INVALID" - GIT_REMOTE_UPDATE_STRATEGY CHECKOUT -) - -set(DAQMODULES_MQTT_ENABLE_TESTS ${${REPO_OPTION_PREFIX}_ENABLE_TESTS} CACHE BOOL "" FORCE) -set(DAQMODULES_MQTT_ENABLE_EXAMPLE_APP OFF CACHE BOOL "") -set(DAQMODULES_MQTT_ENABLE_SSL OFF CACHE BOOL "") - -FetchContent_MakeAvailable(mqttmodule) -include("${mqttmodule_SOURCE_DIR}/external/boost/Boost.cmake" OPTIONAL) -endif() - - -if (${REPO_OPTION_PREFIX}_ENABLE_LT_MODULE) -FetchContent_Declare( - ltstreamingmodulesmodern - GIT_REPOSITORY https://github.com/openDAQ/LTStreamingModulesModern.git - GIT_TAG main - GIT_PROGRESS ON - GIT_SHALLOW OFF - SOURCE_SUBDIR "INVALID" - GIT_REMOTE_UPDATE_STRATEGY CHECKOUT -) - -set(DAQMODULES_LT_STREAMING_ENABLE_TESTS ${${REPO_OPTION_PREFIX}_ENABLE_TESTS} CACHE BOOL "" FORCE) -set(DAQMODULES_LT_STREAMING_ENABLE_EXAMPLE_APP OFF CACHE BOOL "") -set(DAQMODULES_LT_STREAMING_ENABLE_CLIENT ON CACHE BOOL "") -set(DAQMODULES_LT_STREAMING_ENABLE_SERVER ON CACHE BOOL "") - -FetchContent_MakeAvailable(ltstreamingmodulesmodern) -include("${ltstreamingmodulesmodern_SOURCE_DIR}/external/boost/Boost.cmake" OPTIONAL) -endif() - -if (${REPO_OPTION_PREFIX}_ENABLE_ASAM_CMP_MODULE) -FetchContent_Declare( - asamcmpmodules - GIT_REPOSITORY https://github.com/openDAQ/ASAM-CMP-Modules.git - GIT_TAG main - GIT_PROGRESS ON - GIT_SHALLOW OFF - SOURCE_SUBDIR "INVALID" - GIT_REMOTE_UPDATE_STRATEGY CHECKOUT -) - -set(ASAM_CMP_ENABLE_TESTS ${${REPO_OPTION_PREFIX}_ENABLE_TESTS} CACHE BOOL "" FORCE) -set(ASAM_CMP_ENABLE_EXAMPLE OFF CACHE BOOL "") -set(ASAM_CMP_BUILD_DATA_SINK ON CACHE BOOL "") -set(ASAM_CMP_BUILD_CAPTURE_MODULE ON CACHE BOOL "") - -FetchContent_MakeAvailable(asamcmpmodules) -include("${asamcmpmodules_SOURCE_DIR}/external/boost/Boost.cmake" OPTIONAL) -endif() +include(cmake/FetchLayer.cmake) +# the identity keys are the annotations staging-push writes +set(FETCHLAYER_IDENTITY_REVISION_KEY "org.opencontainers.image.revision") +set(FETCHLAYER_IDENTITY_DEPENDENCY_KEY "com.opendaq.dependencies..revision") +set(FETCHLAYER_IDENTITY_AXIS_KEY "com.opendaq.package.") -# add the subdirectories for the core and modules -add_subdirectory(${opendaq_SOURCE_DIR} ${opendaq_BINARY_DIR}) -add_subdirectory(${opcuamodules_SOURCE_DIR} ${opcuamodules_BINARY_DIR}) -add_subdirectory(${mqttmodule_SOURCE_DIR} ${mqttmodule_BINARY_DIR}) -add_subdirectory(${ltstreamingmodulesmodern_SOURCE_DIR} ${ltstreamingmodulesmodern_BINARY_DIR}) -add_subdirectory(${asamcmpmodules_SOURCE_DIR} ${asamcmpmodules_BINARY_DIR}) +include(cmake/Layers.cmake) +FetchLayer_MakeAvailable(${BUNDLE_STAGINGS}) +include(cmake/Packing.cmake) diff --git a/README.md b/README.md index 93b91f8..5a596a4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,11 @@ A CMake super-project that bundles [openDAQ](https://github.com/openDAQ/openDAQ) - [LTStreamingModulesModern](https://github.com/openDAQ/LTStreamingModulesModern) - [ASAM-CMP-Modules](https://github.com/openDAQ/ASAM-CMP-Modules) -All dependencies are fetched automatically via CMake's `FetchContent`. +Each of these repositories is a **layer** at one revision. What a layer publishes is its **staging**: `opendaq`, +`opcua-modules`, `mqtt-module`, `lt-streaming-modules`, `asam-cmp-modules`. The bundle takes each staging prebuilt, +from a store of stagings, when one fits its revision, its dependencies' revisions and this platform. Otherwise it +builds the layer from its sources in the same tree, fetched with CMake's `FetchContent`. The engine is +`cmake/FetchLayer.cmake`; what the bundle is made of is `cmake/Layers.cmake`. ## Prerequisites @@ -14,6 +18,12 @@ All dependencies are fetched automatically via CMake's `FetchContent`. - Git - **Windows:** Visual Studio 2017 or 2022 (with C++ workload) — used as compiler toolchain only - **Linux:** GCC or Clang, Ninja +- **macOS:** Xcode command line tools, Ninja +- For prebuilt stagings: [oras](https://oras.land) and read access to `ghcr.io/opendaq/dev/staging` + +On an Intel Mac with Homebrew in `/usr/local`, Homebrew's `boost`, `spdlog`, `fmt` and `rapidjson` headers shadow +the ones the build fetches. Unlink them for a build from sources (`brew unlink boost spdlog fmt rapidjson`, then +`brew link …`). ## Configure @@ -21,6 +31,7 @@ Use one of the available CMake presets: | Preset | Platform | Compiler | Arch | Config | |---|---|---|---|---| +| `bundle-native` | this machine | default | this machine | Release, prebuilt stagings from `build/store` | | `x64/msvc-22` | Windows | MSVC 2022 | x64 | Release | | `x86/msvc-22` | Windows | MSVC 2022 | x86 | Release | | `x64/msvc-17` | Windows | MSVC 2017 | x64 | Release | @@ -30,6 +41,9 @@ Use one of the available CMake presets: | `x64/clang/release` | Linux | Clang | x64 | Release | | `x64/clang/debug` | Linux | Clang | x64 | Debug | +`bundle-native` pins every layer to a commit whose stagings are published. The others set no store, so they build +every layer from its `main` branch. + ```bash # Windows cmake --preset="x64/msvc-22" @@ -52,14 +66,56 @@ cmake --build build/x64/gcc/release | Option | Default | Description | |---|---|---| -| `RELEASE_REPO_ENABLE_TESTS` | `OFF` | Build and enable tests | -| `RELEASE_REPO_ENABLE_OPCUA_MODULE` | `ON` | Include OPC UA module | -| `RELEASE_REPO_ENABLE_MQTT_MODULE` | `ON` | Include MQTT streaming module | -| `RELEASE_REPO_ENABLE_LT_MODULE` | `ON` | Include LT streaming module | -| `RELEASE_REPO_ENABLE_ASAM_CMP_MODULE` | `ON` | Include ASAM-CMP modules | +| `BUNDLE_STAGINGS` | all five | The stagings to make available, dependencies first: `opendaq;opcua-modules;mqtt-module;lt-streaming-modules;asam-cmp-modules` | +| `BUNDLE_REVISION_` | `main` | The revision of a layer (a repository, its name upper-cased): a full sha, a tag or a branch, e.g. `BUNDLE_REVISION_OPCUAMODULES` | +| `FETCHLAYER_BUILD` | `MISSING` | `NEVER`: prebuilt stagings only; `MISSING`: prebuilt where there is one, else from sources; `ALWAYS`: from sources. `FETCHLAYER_BUILD_` for one layer | +| `FETCHLAYER_STORE` | — | The store of prebuilt stagings, `//idx-//`; without one, everything is built from sources | +| `FETCHCONTENT_SOURCE_DIR_` | — | A layer from a local checkout, always built from it | +| `BUNDLE_AXIS_TRIPLET` | this build's | The triplets to make available; others than this build's come prebuilt, as files only | +| `BUNDLE_ENABLE_TESTS` | `OFF` | Build the tests of the layers built from sources | Pass options at configure time: ```bash -cmake --preset="x64/gcc/release" -DRELEASE_REPO_ENABLE_TESTS=ON +cmake --preset="x64/gcc/release" -DBUNDLE_ENABLE_TESTS=ON +cmake --preset="x64/gcc/release" -DBUNDLE_STAGINGS="opendaq;opcua-modules" -DBUNDLE_REVISION_OPCUAMODULES=v4.0.1 +cmake --preset bundle-native -DFETCHLAYER_BUILD_OPCUAMODULES=ALWAYS # OPC UA from sources, the rest prebuilt +``` + +## Prebuilt stagings + +The store is filled from the registry once, for the revisions `bundle-native` pins (see `CMakePresets.json`): + +```bash +cmake -DSTORE=build/store -DREGISTRY=ghcr.io/opendaq/dev/staging \ + [-DORAS_REGISTRY_CONFIG=$HOME/.oras/config.json] \ + "-DLAYERS=opendaq=;opcua-modules=;mqtt-module=;lt-streaming-modules=;asam-cmp-modules=" \ + -P tools/fill-store.cmake +``` + +It takes every platform of each staging; a build already in the store is kept. Configuring from a filled store +needs no network, apart from the first fetch of `opendaq-cmake-utils`. + +## Package + +With prebuilt stagings in a store, `bundle-native` configures without building, and the package presets pack: + +```bash +cmake --preset bundle-native +cmake --build --preset bundle-native +cpack --preset core # the core: runtime, C++ API, C binding +cpack --preset modules # the modules, without the core +``` + +The packages land in `build/bundle-native/packages`, named `opendaq---.tar.gz`. + +## Tests + +```bash +cmake -P tests/identity.cmake # the identity hash against its check vector +cmake -P tests/axis.cmake # axes and variants +cmake -S tests/fetchlayer-axes -B build/fetchlayer-axes # variants by two axes, the layer's decision +cmake -S tests/fetchlayer-git -B build/fetchlayer-git # the git origin (network) +cmake -S tests/fetchlayer -B build/fetchlayer -G Ninja \ + -DFETCHLAYER_STORE=$PWD/build/store -DFETCHLAYER_TRIPLET= # on the store, with a smoke test ``` diff --git a/cmake/FetchLayer.cmake b/cmake/FetchLayer.cmake index 367a926..7ef20bf 100644 --- a/cmake/FetchLayer.cmake +++ b/cmake/FetchLayer.cmake @@ -4,7 +4,7 @@ ## Stagings are made available, for one or more triplets, from where the policy says — prebuilt (System, later ## OCI) or built from their layer's sources (Git). ## -## FetchLayer_Declare( REVISION […]) +## FetchLayer_Declare( REVISION […] [POST_FETCH_INCLUDES