This commit is contained in:
2026-08-08 22:39:18 +08:00
parent d1cff37a2a
commit 464f3a166a
22 changed files with 510 additions and 54 deletions
@@ -206,3 +206,10 @@ Before handoff, verify:
- CMake paths are local to the owning CMake file.
- Install/export/package contents still match public headers and repo-local Codex guidance.
- Only verification steps actually executed are reported as passing.
## Dependency and release workflow
Repository builds use pinned bundled nlohmann/json, magic_enum, and cpp-httplib headers, but installed adapter components use external CMake package targets. Never restore installation of bundled dependency headers under `${CMAKE_INSTALL_INCLUDEDIR}`. When changing a dependency version, update the bundled source, `AdminiveConfig.cmake.in`, `THIRD_PARTY_NOTICES.md`, the matching license text, install consumer, and package test together.
The fast Drogon test uses a fake transport contract. Before release on a machine with real Drogon installed, run `python scripts/verify.py --real-drogon`; it must compile the real adapter and an installed-package consumer. Do not report that gate as passed when it was skipped.
Adminive/Structive project-owned code has no outbound license declared in this tree. Do not generate a project license unless the owner explicitly chooses one.
+6
View File
@@ -87,3 +87,9 @@ When reviewing a change, check in this order:
5. Managed synchronization and transaction behavior.
6. Adapter parity, especially httplib versus Drogon.
7. Independent public-header compilation, install consumer, package contents, frontend build, and E2E coverage.
## Dependency and release policy
- Source-tree builds may use the pinned nlohmann/json, magic_enum, and cpp-httplib copies under `backend/service/third_party/include`; installed Adminive adapter targets must resolve those dependencies through their external CMake packages instead of installing bundled headers into the consumer's generic include namespace.
- Keep `THIRD_PARTY_NOTICES.md`, `third_party/licenses/`, `RELEASE.md`, install-consumer checks, and package ZIP checks synchronized with dependency version changes.
- The repository does not declare an outbound license for Adminive/Structive project-owned code. Do not invent or infer one.
- Release verification against Drogon must include the opt-in real dependency gate: `python scripts/verify.py --real-drogon`. Fake Drogon tests remain fast contract tests, not a substitute for that release gate.
+11
View File
@@ -1,6 +1,11 @@
cmake_minimum_required(VERSION 3.20)
project(Adminive VERSION 1.0.0 LANGUAGES CXX)
include(CTest)
option(ADMINIVE_ENABLE_REAL_DROGON_TESTS "Build and run release gates against installed Drogon" OFF)
if(ADMINIVE_ENABLE_REAL_DROGON_TESTS)
find_package(nlohmann_json 3.12.0 CONFIG REQUIRED)
find_package(Drogon CONFIG REQUIRED)
endif()
include("${CMAKE_CURRENT_LIST_DIR}/cmake/AdminiveVerification.cmake")
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/third_party/Structive")
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/backend")
@@ -15,6 +20,8 @@ set(adminive_package_includes
"/frontend/"
"/install_consumer/"
"/README.md"
"/RELEASE.md"
"/THIRD_PARTY_NOTICES.md"
"/scripts/"
"/third_party/"
)
@@ -40,6 +47,10 @@ add_project_zip_target(package_zip "${CMAKE_CURRENT_LIST_DIR}/Adminive.zip" "${C
if(BUILD_TESTING)
add_test(NAME Adminive_Install_Consumer_Test COMMAND "${CMAKE_COMMAND}" "-DADMINIVE_BINARY_DIR=${CMAKE_BINARY_DIR}" "-DADMINIVE_CONFIG=$<CONFIG>" -P "${CMAKE_CURRENT_LIST_DIR}/cmake/RunInstallConsumer.cmake")
set_tests_properties(Adminive_Install_Consumer_Test PROPERTIES LABELS "install;package")
if(ADMINIVE_ENABLE_REAL_DROGON_TESTS)
add_test(NAME Adminive_Real_Drogon_Install_Consumer_Test COMMAND "${CMAKE_COMMAND}" "-DADMINIVE_BINARY_DIR=${CMAKE_BINARY_DIR}" "-DADMINIVE_CONFIG=$<CONFIG>" "-DDrogon_DIR=${Drogon_DIR}" "-Dnlohmann_json_DIR=${nlohmann_json_DIR}" -P "${CMAKE_CURRENT_LIST_DIR}/cmake/RunRealDrogonInstallConsumer.cmake")
set_tests_properties(Adminive_Real_Drogon_Install_Consumer_Test PROPERTIES LABELS "install;package;drogon;real-dependency;release")
endif()
add_test(NAME Adminive_Package_Zip_Test COMMAND "${CMAKE_COMMAND}" "-DADMINIVE_BINARY_DIR=${CMAKE_BINARY_DIR}" "-DADMINIVE_CONFIG=$<CONFIG>" -P "${CMAKE_CURRENT_LIST_DIR}/cmake/RunPackageZipTest.cmake")
set_tests_properties(Adminive_Package_Zip_Test PROPERTIES LABELS "package;archive")
adminive_register_frontend_tests("${CMAKE_BINARY_DIR}" "$<CONFIG>")
+15 -1
View File
@@ -825,7 +825,7 @@ ctest --test-dir build --output-on-failure
测试使用 `backend/test_support/adminive_test.hpp``ADMINIVE_CHECK`,不会像标准 `assert()` 一样在 Release `NDEBUG` 下被移除。旧的 `Synchronized_Value`/`Resource_Lock_Scope` 测试实现已经删除;仍有意义的嵌套同步、无锁字段、读写能力和多线程序列化用例由当前 `Managed_Value` 测试覆盖,不恢复旧 API。
CTest 使用 label 区分 `unit``protocol``http``install``package``header``concurrency``managed` 等测试。全部 Adminive Core 公共头和 5 个 Service Adapter 公共头都注册独立 include 编译测试,避免 umbrella header 掩盖缺失依赖。`Adminive_Install_Consumer_Test` 会先安装到构建目录内的固定测试 prefix,再从独立 `install_consumer` 工程只通过 `find_package(Adminive)` 同时构建 `Adminive::Core``Adminive::Default`/Httplib consumer。`Adminive_Package_Zip_Test` 会实际生成源码 ZIP,验证根目录精确文件匹配、无残留嵌套工程且已删除的旧同步实现不会重新进入发布包。
CTest 使用 label 区分 `unit``protocol``http``install``package``header``concurrency``managed` 等测试。全部 Adminive Core 公共头和 5 个 Service Adapter 公共头都注册独立 include 编译测试,避免 umbrella header 掩盖缺失依赖。`Adminive_Install_Consumer_Test` 会先安装到构建目录内的固定测试 prefix,再从独立 `install_consumer` 工程只通过 `find_package(Adminive)` 同时构建 `Adminive::Core``Adminive::Default`/Httplib consumer。安装包不再把仓库内置的 nlohmann/json、magic_enum、cpp-httplib 复制到公共 include 命名空间;测试会用独立依赖 package fixture 验证导出目标确实通过 `find_dependency` 解析外部依赖,并检查安装 prefix 中不存在这些 bundled header。`Adminive_Package_Zip_Test` 会实际生成源码 ZIP,验证根目录精确文件匹配、release/license metadata、无残留嵌套工程且已删除的旧同步实现不会重新进入发布包。
Clang/GNU sanitizer
@@ -846,6 +846,14 @@ MSVC 配置 UBSan 或 TSan 会在 CMake 配置阶段明确失败,不允许出
python .\scripts\verify.py
```
正式发布环境安装了真实 Drogon 与 nlohmann_json CMake package 时,再执行:
```powershell
python .\scripts\verify.py --real-drogon
```
这个选项会在 Release 构建中启用真实 Drogon adapter target 和真实 installed-package consumer;默认 fake Drogon contract test 不冒充这一 Gate。
Gate 使用固定 `verification/debug``verification/release``verification/asan-ubsan``verification/tsan` 路径。Clang/GNU 运行 Debug/Release 全构建和 CTest、ASan+UBSan Core/Service 矩阵、TSan concurrency、前端依赖/单测/production build、Playwright Chromium E2EWindows MSVC 只运行真实 ASan,并明确输出 UBSan/TSan SKIP,额外运行 `msedge` channel。Sanitizer 矩阵覆盖 Core Adapter、Advanced Adapter、Safety、Managed、View Schema、Drogon 和真实 Httplib,不把耗时的示例 Gallery 重复做 sanitizer 构建。前端 E2E 覆盖导航、同一 Manifest 五种布局、display/create/edit、Collection CRUD/分页/排序/搜索/筛选/状态/重排/错误、422 nested validation、Sensitive 输出过滤、Object Adapter transaction counter、Transaction rollback、状态轮询序列、协议边界和缓存头。需要把前端测试注册进 CTest 时配置 `ADMINIVE_ENABLE_FRONTEND_TESTS=ON`
安装并通过 CMake 包使用:
@@ -861,6 +869,8 @@ find_package(Adminive CONFIG REQUIRED COMPONENTS Drogon)
target_link_libraries(drogon_app PRIVATE Adminive::Drogon)
```
安装后的 Adapter component 使用外部 CMake package`Nlohmann` 解析 `nlohmann_json::nlohmann_json``MagicEnum` 解析 `magic_enum::magic_enum``Httplib` 解析 `httplib::httplib``BoostPfr` 解析 `Boost::headers``Drogon` 解析 `Drogon::Drogon`。仓库内置的前三个 header 只服务源码树 build/test,不安装到消费者的通用 include 目录。完整规则见 `RELEASE.md`
MSVC 消费者默认接收 `/utf-8``/Zc:__cplusplus``/permissive-` 只用于 Adminive 自身目标;确实需要传播时配置 `ADMINIVE_PROPAGATE_MSVC_STRICT_MODE=ON`
访问:
@@ -886,3 +896,7 @@ add_project_zip_target(
adminive_package_excludes
)
```
## Release and third-party policy
See `RELEASE.md` for source-tree versus installed-package dependency rules and the real Drogon release gate. Redistributed dependency notices and license texts are listed in `THIRD_PARTY_NOTICES.md`. The repository does not currently declare an outbound license for Adminive/Structive project-owned code.
+47
View File
@@ -0,0 +1,47 @@
# Adminive Release Policy
## Dependency model
Adminive intentionally uses two dependency modes:
- Source-tree build/test mode: nlohmann/json 3.12.0, magic_enum 0.9.8, and cpp-httplib 0.48.0 use pinned bundled headers under `backend/service/third_party/include`.
- Installed-package mode: adapter components use external CMake packages. The bundled headers are not installed into the consumer's generic include namespace.
Installed component dependencies are:
| Adminive component | External dependency |
|---|---|
| `Core`, `Http`, `Service` | none beyond the exported Adminive/Structive headers |
| `Nlohmann` | `nlohmann_json` >= 3.12.0 |
| `MagicEnum` | `magic_enum` >= 0.9.8 |
| `Httplib` | `httplib` >= 0.48.0 |
| `BoostPfr` | Boost headers |
| `Drogon` | Drogon |
| `Default` | nlohmann_json, magic_enum, cpp-httplib |
This split prevents Adminive installation from shadowing a consumer's own `<nlohmann/...>`, `<magic_enum/...>`, or `<httplib.h>` headers while keeping repository builds reproducible.
## License metadata
`THIRD_PARTY_NOTICES.md` and `third_party/licenses/` describe redistributed third-party code. The source tree does not currently declare an outbound license for Adminive or Structive project-owned code. Adding or changing that project license is a separate owner decision and must not be inferred from third-party licenses.
## Drogon release gate
Normal tests use the lightweight fake Drogon contract fixture. A release machine with real Drogon and nlohmann_json CMake packages installed must additionally run:
```text
python scripts/verify.py --real-drogon
```
`ADMINIVE_ENABLE_REAL_DROGON_TESTS=ON` builds a real-Drogon adapter compile/run target and an installed-package consumer. This gate exists specifically to catch changes in real Drogon route/callback/constraint APIs that the fake fixture cannot detect.
## Release checklist
1. Run focused tests for changed Core/adapter/protocol code.
2. Run Debug and Release CTest suites.
3. Run install-consumer and package ZIP tests.
4. Run sanitizer gates supported by the compiler.
5. Run `python scripts/verify.py --real-drogon` on a release environment with real Drogon packages.
6. If frontend code changed, run frontend unit, production build, Chromium E2E, and Edge E2E on Windows.
7. Verify the installed prefix contains Adminive headers and release/license metadata but not bundled nlohmann/json, magic_enum, or cpp-httplib headers.
8. Generate the source archive without build output and with China Standard Time timestamps.
+29
View File
@@ -0,0 +1,29 @@
# Adminive Third-Party Notices
This file records third-party software used by Adminive. It does not declare or grant a license for Adminive or Structive project-owned code; no outbound project license is declared by this source tree.
## Bundled source-tree dependencies
The source tree carries pinned copies for local builds and tests only. Installed Adminive CMake packages do not copy these headers into the consumer's generic include namespace.
| Dependency | Bundled version | License | Bundled path | License text |
|---|---:|---|---|---|
| nlohmann/json | 3.12.0 | MIT | `backend/service/third_party/include/nlohmann/` | `third_party/licenses/nlohmann-json-LICENSE.MIT` |
| magic_enum | 0.9.8 | MIT | `backend/service/third_party/include/magic_enum/` | `third_party/licenses/magic-enum-LICENSE.MIT` |
| cpp-httplib | 0.48.0 | MIT | `backend/service/third_party/include/httplib.h` | `third_party/licenses/cpp-httplib-LICENSE.MIT` |
## External package dependencies
Installed adapter components resolve their third-party dependencies with CMake package configuration instead of installing private copies under public include names:
- `Adminive::Nlohmann` -> `nlohmann_json::nlohmann_json`
- `Adminive::MagicEnum` -> `magic_enum::magic_enum`
- `Adminive::Httplib` -> `httplib::httplib`
- `Adminive::BoostPfr` -> `Boost::headers`
- `Adminive::Drogon` -> `Drogon::Drogon`
Boost.PFR is distributed under the Boost Software License 1.0 as part of Boost. Drogon is distributed under the MIT License. Their source code is not bundled into the Adminive source tree by this project, so their license files remain with the externally supplied packages.
## Release rule
When changing a bundled dependency version, update the pinned source, this notice, the matching license text, the installed package dependency requirement, and install/package tests together. Do not add a bundled dependency directory to `${CMAKE_INSTALL_INCLUDEDIR}`.
+7
View File
@@ -11,5 +11,12 @@ configure_package_config_file("${CMAKE_CURRENT_LIST_DIR}/../cmake/AdminiveConfig
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/AdminiveConfigVersion.cmake" VERSION "${PROJECT_VERSION}" COMPATIBILITY SameMajorVersion)
install(EXPORT AdminiveCoreTargets FILE AdminiveCoreTargets.cmake NAMESPACE Adminive:: DESTINATION "${adminive_install_cmake_dir}")
install(EXPORT AdminiveServiceTargets FILE AdminiveServiceTargets.cmake NAMESPACE Adminive:: DESTINATION "${adminive_install_cmake_dir}")
install(EXPORT AdminiveNlohmannTargets FILE AdminiveNlohmannTargets.cmake NAMESPACE Adminive:: DESTINATION "${adminive_install_cmake_dir}")
install(EXPORT AdminiveMagicEnumTargets FILE AdminiveMagicEnumTargets.cmake NAMESPACE Adminive:: DESTINATION "${adminive_install_cmake_dir}")
install(EXPORT AdminiveBoostPfrTargets FILE AdminiveBoostPfrTargets.cmake NAMESPACE Adminive:: DESTINATION "${adminive_install_cmake_dir}")
install(EXPORT AdminiveHttplibTargets FILE AdminiveHttplibTargets.cmake NAMESPACE Adminive:: DESTINATION "${adminive_install_cmake_dir}")
install(EXPORT AdminiveDefaultTargets FILE AdminiveDefaultTargets.cmake NAMESPACE Adminive:: DESTINATION "${adminive_install_cmake_dir}")
install(EXPORT AdminiveDrogonTargets FILE AdminiveDrogonTargets.cmake NAMESPACE Adminive:: DESTINATION "${adminive_install_cmake_dir}")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/AdminiveConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/AdminiveConfigVersion.cmake" DESTINATION "${adminive_install_cmake_dir}")
install(FILES "${CMAKE_CURRENT_LIST_DIR}/../THIRD_PARTY_NOTICES.md" "${CMAKE_CURRENT_LIST_DIR}/../RELEASE.md" DESTINATION "${CMAKE_INSTALL_DATADIR}/Adminive")
install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/../third_party/licenses/" DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/Adminive")
+4 -2
View File
@@ -996,9 +996,9 @@ Config Store persistence
### 23.7 Transport 与安装黑盒测试
Service 单元测试不能替代 transport 黑盒测试。Httplib adapter test 必须启动真实 localhost server,再通过 client 请求 descriptor/view/data/amis、Resource Context、Collection CRUD/query/reorder/status 和错误状态。Drogon 保持独立 adapter contract test
Service 单元测试不能替代 transport 黑盒测试。Httplib adapter test 必须启动真实 localhost server,再通过 client 请求 descriptor/view/data/amis、Resource Context、Collection CRUD/query/reorder/status 和错误状态。Drogon 保持快速 fake contract test,并提供 `ADMINIVE_ENABLE_REAL_DROGON_TESTS=ON` 的发布 Gate 编译真实 Drogon route/callback/constraint API 和 installed-package consumer。transport 的 `context_factory` 属于最外层边界,`std::exception` 与未知异常都必须在这里转换成结构化 500,不能逃出框架 callback。Collection 的客户端 query/JSON/reorder 输入错误显式映射为 400,字段/对象 validation 为 422status reader 或其他内部 callback 抛出的未分类异常必须是 500,不能因为共用 `safe_response` 被误报成客户端错误
安装测试必须先执行 `cmake --install` 到构建目录内的固定 prefix,再从独立 consumer 工程只通过 `find_package(Adminive)` 配置和运行。测试 consumer 不允许使用源码 include path,否则无法发现 export/install 错误。
安装测试必须先执行 `cmake --install` 到构建目录内的固定 prefix,再从独立 consumer 工程只通过 `find_package(Adminive)` 配置和运行。Adminive 源码树可以使用 pinned bundled nlohmann/json、magic_enum、cpp-httplib 便于可复现 build/test,但安装后的 Adapter target 必须通过外部 CMake package target 解析这些依赖,不能把 bundled header 安装到 `${CMAKE_INSTALL_INCLUDEDIR}` 污染消费者命名空间。install consumer 用独立 package fixture 验证导出 dependency wiring,同时确认公共 install prefix 不包含 bundled dependency header。Boost.PFR 和 Drogon 始终由外部 package 提供。测试 consumer 不允许直接使用 Adminive 源码 include path,否则无法发现 export/install 错误。
### 23.8 Verification Gate
@@ -1018,6 +1018,8 @@ frontend production build
Playwright Chromium E2E
Playwright Edge E2E on Windows
real HTTP integration
real Drogon compile + installed consumer release gate
third-party notice/license/package-content verification
```
CTest label 用于按 `unit/protocol/http/install/package/header/concurrency/frontend/e2e` 分层定位失败。Clang/GNU 的 ASan+UBSan 对 Core Adapter、Advanced Adapter、Safety、Managed、View Schema、Drogon 与真实 Httplib 运行 sanitizer 矩阵,TSan 单独覆盖并发 Managed 路径;MSVC 只运行真实 ASan,配置不支持的 UBSan/TSan 必须直接失败而不能假通过。公共头必须逐个独立 include 编译,安装 consumer 同时验证 Core 与 Default/Httplib,源码 ZIP 必须实际生成并检查精确根文件、旧实现残留和嵌套工程。Sanitizer 是测试配置,不改变 Core API。frontend E2E 验证同一 Manifest 五种布局、Form modes、Collection CRUD/query/reorder/status、422 nested validation、Sensitive 输出过滤、Object Adapter transaction counter、transaction rollback、状态序列、协议错误和缓存策略。Gate 使用确定的目录和端口,不依赖随机构建路径。
+43 -17
View File
@@ -117,6 +117,10 @@ public:
}
Http_Response<Json> list_response(Collection_Query query = {}) const noexcept {
return safe_response([&] {
const std::string error = query_error(query);
if(!error.empty()) {
return make_http_error<Json>(400, error);
}
std::scoped_lock lock(mutex_);
std::vector<const Entry*> ordered_entries;
ordered_entries.reserve(entries_.size());
@@ -152,7 +156,12 @@ public:
}
Http_Response<Json> create_response(std::string_view body) noexcept {
return safe_response([&] {
Json input = parse_json<Json>(body);
Json input;
try {
input = parse_json<Json>(body);
} catch(const std::exception& error) {
return make_http_error<Json>(400, error.what());
}
Json_Adapter<Json>::erase(input, "id");
T value{};
const auto result = apply_frontend_create<Json>(value, input);
@@ -166,7 +175,12 @@ public:
}
Http_Response<Json> update_response(std::uint64_t id, std::string_view body) noexcept {
return safe_response([&] {
Json patch = parse_json<Json>(body);
Json patch;
try {
patch = parse_json<Json>(body);
} catch(const std::exception& error) {
return make_http_error<Json>(400, error.what());
}
Json_Adapter<Json>::erase(patch, "id");
std::scoped_lock lock(mutex_);
const auto iterator = find_entry(id);
@@ -193,8 +207,13 @@ public:
}
Http_Response<Json> reorder_response(std::string_view body) noexcept {
return safe_response([&] {
const Json input = parse_json<Json>(body);
const auto ids = read_order_ids(Json_Adapter<Json>::at(input, "ids"));
std::vector<std::uint64_t> ids;
try {
const Json input = parse_json<Json>(body);
ids = read_order_ids(Json_Adapter<Json>::at(input, "ids"));
} catch(const std::exception& error) {
return make_http_error<Json>(400, error.what());
}
std::scoped_lock lock(mutex_);
reorder(ids);
return make_http_success<Json>(json_object<Json>(), "reordered");
@@ -216,7 +235,7 @@ private:
} catch(const Field_Validation_Error& error) {
return make_http_error<Json>(422, object_commit_error<T>(error));
} catch(const std::exception& error) {
return make_http_error<Json>(400, error.what());
return make_http_error<Json>(500, error.what());
} catch(...) {
return make_http_error<Json>(500, "unknown server error");
}
@@ -247,14 +266,30 @@ private:
});
return iterator == view.columns.end() ? nullptr : &*iterator;
}
static bool is_sortable_field(const std::string& name) {
static bool is_sortable_field(const Table_View& view, const std::string& name) {
if(name == "id") {
return true;
}
const auto view = describe_table_view<T>();
const auto* column = find_table_column(view, name);
return column && column->sortable;
}
static std::string query_error(const Collection_Query& query) {
const auto view = describe_table_view<T>();
for(const auto& [name, value] : query.fields) {
static_cast<void>(value);
const auto* column = find_table_column(view, name);
if(!column || (!column->searchable && !column->filterable)) {
return "table query field is not searchable or filterable: " + name;
}
}
if(!query.order_by.empty() && !is_sortable_field(view, query.order_by)) {
return "table query field is not sortable: " + query.order_by;
}
if(!query.order_by.empty() && !query.order_dir.empty() && query.order_dir != "asc" && query.order_dir != "desc") {
return "table order direction must be asc or desc";
}
return {};
}
static std::string query_text(const Json& value) {
if(Json_Adapter<Json>::is_string(value)) {
return json_get<Json, std::string>(value);
@@ -291,9 +326,6 @@ private:
const auto view = describe_table_view<T>();
for(const auto& [name, value] : query.fields) {
const auto* column = find_table_column(view, name);
if(!column || (!column->searchable && !column->filterable)) {
throw std::invalid_argument("table query field is not searchable or filterable: " + name);
}
entries.erase(std::remove_if(entries.begin(), entries.end(), [&](const Entry* entry) {
const Json encoded = to_frontend_json<Json>(entry->value);
if(!Json_Adapter<Json>::contains(encoded, name)) {
@@ -331,15 +363,9 @@ private:
if(order_by.empty()) {
return;
}
if(!is_sortable_field(order_by)) {
if(!query.order_by.empty()) {
throw std::invalid_argument("table query field is not sortable: " + order_by);
}
if(!is_sortable_field(view, order_by)) {
return;
}
if(order_dir != "asc" && order_dir != "desc") {
throw std::invalid_argument("table order direction must be asc or desc");
}
const bool descending = order_dir == "desc";
std::stable_sort(entries.begin(), entries.end(), [&](const Entry* left, const Entry* right) {
int comparison{};
+17 -6
View File
@@ -5,16 +5,18 @@ file(TO_CMAKE_PATH "${adminive_frontend_dist_dir}" adminive_frontend_dist_dir)
add_library(Adminive_Service INTERFACE)
set_target_properties(Adminive_Service PROPERTIES EXPORT_NAME Service)
add_library(Adminive::Service ALIAS Adminive_Service)
target_include_directories(Adminive_Service INTERFACE "$<BUILD_INTERFACE:${adminive_service_include_dir}>" "$<BUILD_INTERFACE:${adminive_third_party_include_dir}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
target_include_directories(Adminive_Service INTERFACE "$<BUILD_INTERFACE:${adminive_service_include_dir}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
target_link_libraries(Adminive_Service INTERFACE Adminive::Core)
add_library(Adminive_Bundled_Service_Dependencies INTERFACE)
target_include_directories(Adminive_Bundled_Service_Dependencies INTERFACE "${adminive_third_party_include_dir}")
add_library(Adminive_Nlohmann INTERFACE)
set_target_properties(Adminive_Nlohmann PROPERTIES EXPORT_NAME Nlohmann)
add_library(Adminive::Nlohmann ALIAS Adminive_Nlohmann)
target_link_libraries(Adminive_Nlohmann INTERFACE Adminive::Service)
target_link_libraries(Adminive_Nlohmann INTERFACE Adminive::Service "$<BUILD_INTERFACE:Adminive_Bundled_Service_Dependencies>" "$<INSTALL_INTERFACE:nlohmann_json::nlohmann_json>")
add_library(Adminive_MagicEnum INTERFACE)
set_target_properties(Adminive_MagicEnum PROPERTIES EXPORT_NAME MagicEnum)
add_library(Adminive::MagicEnum ALIAS Adminive_MagicEnum)
target_link_libraries(Adminive_MagicEnum INTERFACE Adminive::Service)
target_link_libraries(Adminive_MagicEnum INTERFACE Adminive::Service "$<BUILD_INTERFACE:Adminive_Bundled_Service_Dependencies>" "$<INSTALL_INTERFACE:magic_enum::magic_enum>")
add_library(Adminive_BoostPfr INTERFACE)
set_target_properties(Adminive_BoostPfr PROPERTIES EXPORT_NAME BoostPfr)
add_library(Adminive::BoostPfr ALIAS Adminive_BoostPfr)
@@ -22,7 +24,7 @@ target_link_libraries(Adminive_BoostPfr INTERFACE Adminive::Service "$<INSTALL_I
add_library(Adminive_Httplib INTERFACE)
set_target_properties(Adminive_Httplib PROPERTIES EXPORT_NAME Httplib)
add_library(Adminive::Httplib ALIAS Adminive_Httplib)
target_link_libraries(Adminive_Httplib INTERFACE Adminive::Service)
target_link_libraries(Adminive_Httplib INTERFACE Adminive::Service "$<BUILD_INTERFACE:Adminive_Bundled_Service_Dependencies>" "$<INSTALL_INTERFACE:httplib::httplib>")
if(WIN32)
target_compile_definitions(Adminive_Httplib INTERFACE WINVER=0x0A00 _WIN32_WINNT=0x0A00)
target_link_libraries(Adminive_Httplib INTERFACE ws2_32)
@@ -80,6 +82,11 @@ if(BUILD_TESTING)
target_include_directories(Adminive_Drogon_Adapter_Test BEFORE PRIVATE "${CMAKE_CURRENT_LIST_DIR}/tests/fake_drogon")
target_link_libraries(Adminive_Drogon_Adapter_Test PRIVATE Adminive::Default)
adminive_register_test(Adminive_Drogon_Adapter_Test "unit;http;drogon")
if(ADMINIVE_ENABLE_REAL_DROGON_TESTS)
add_executable(Adminive_Real_Drogon_Adapter_Test "${CMAKE_CURRENT_LIST_DIR}/tests/real_drogon_adapter_test.cpp")
target_link_libraries(Adminive_Real_Drogon_Adapter_Test PRIVATE Adminive::Drogon Adminive::Nlohmann)
adminive_register_test(Adminive_Real_Drogon_Adapter_Test "integration;http;drogon;real-dependency;release")
endif()
add_executable(Adminive_Httplib_Adapter_Test "${CMAKE_CURRENT_LIST_DIR}/tests/httplib_adapter_test.cpp")
target_link_libraries(Adminive_Httplib_Adapter_Test PRIVATE Adminive::Default)
adminive_register_test(Adminive_Httplib_Adapter_Test "integration;http;httplib")
@@ -110,7 +117,11 @@ if(BUILD_TESTING)
target_compile_options(Adminive_View_Schema_Test PRIVATE /permissive-)
endif()
endif()
install(TARGETS Adminive_Service Adminive_Nlohmann Adminive_MagicEnum Adminive_BoostPfr Adminive_Httplib Adminive_Default EXPORT AdminiveServiceTargets)
install(TARGETS Adminive_Service EXPORT AdminiveServiceTargets)
install(TARGETS Adminive_Nlohmann EXPORT AdminiveNlohmannTargets)
install(TARGETS Adminive_MagicEnum EXPORT AdminiveMagicEnumTargets)
install(TARGETS Adminive_BoostPfr EXPORT AdminiveBoostPfrTargets)
install(TARGETS Adminive_Httplib EXPORT AdminiveHttplibTargets)
install(TARGETS Adminive_Default EXPORT AdminiveDefaultTargets)
install(TARGETS Adminive_Drogon EXPORT AdminiveDrogonTargets)
install(DIRECTORY "${adminive_service_include_dir}/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
install(DIRECTORY "${adminive_third_party_include_dir}/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
@@ -29,6 +29,18 @@ struct Drogon_Bind_Options {
inline Request_Context make_drogon_request_context(const drogon::HttpRequestPtr& request, const Drogon_Request_Context_Factory& factory) {
return factory ? factory(request) : Request_Context{};
}
template <Json_Type Json>
bool resolve_drogon_request_context(const drogon::HttpRequestPtr& request, const Drogon_Request_Context_Factory& factory, Request_Context& context, std::function<void(const drogon::HttpResponsePtr&)>& callback) noexcept {
try {
context = make_drogon_request_context(request, factory);
return true;
} catch(const std::exception& error) {
callback(make_drogon_response(make_http_error<Json>(500, error.what())));
} catch(...) {
callback(make_drogon_response(make_http_error<Json>(500, "unknown server error")));
}
return false;
}
inline std::vector<drogon::internal::HttpConstraint> make_drogon_constraints(drogon::HttpMethod method, const std::vector<std::string>& filters) {
std::vector<drogon::internal::HttpConstraint> result;
result.reserve(filters.size() + 1);
@@ -119,10 +131,7 @@ public:
}, get_constraints);
app.registerHandler(service->path() + "/data", [service, options](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
Request_Context context;
try {
context = make_drogon_request_context(request, options.context_factory);
} catch(const std::exception& error) {
callback(make_drogon_response(make_http_error<Json>(500, error.what())));
if(!resolve_drogon_request_context<Json>(request, options.context_factory, context, callback)) {
return;
}
schedule_drogon_request<Json>(options.executor, std::move(callback), [service, body = std::string(request->getBody()), context] {
@@ -266,10 +275,7 @@ public:
}, constraints);
app.registerHandler(state->path + "/data", [state, options](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
Request_Context context;
try {
context = make_drogon_request_context(request, options.context_factory);
} catch(const std::exception& error) {
callback(make_drogon_response(make_http_error<Json>(500, error.what())));
if(!resolve_drogon_request_context<Json>(request, options.context_factory, context, callback)) {
return;
}
schedule_drogon_request<Json>(options.executor, std::move(callback), [state, context] {
+47 -3
View File
@@ -6,12 +6,18 @@
#include <utility>
#include <vector>
namespace drogon_test {
struct Config {
std::string name{"default"};
};
struct Status {
std::string user;
};
struct Config {
std::string name{"default"};
Status status() {
if(name == "status-error") {
throw std::runtime_error("item status failed");
}
return Status{name};
}
};
}
namespace adminive {
template <>
@@ -76,6 +82,27 @@ int main() {
});
ADMINIVE_CHECK(response->status == drogon::k200OK);
ADMINIVE_CHECK(Json::parse(response->body).at("data").at("kind") == "form");
adminive::Drogon_Bind_Options nonstd_options = options;
nonstd_options.context_factory = [](const drogon::HttpRequestPtr&) -> adminive::Request_Context {
throw 7;
};
adminive::Drogon_Resource<drogon_test::Config, Json> nonstd_resource(config, "/context-error");
nonstd_resource.bind(app, nonstd_options);
adminive::Drogon_Status_Resource<drogon_test::Status, Json> nonstd_status("/status-context-error", [](const adminive::Request_Context&) {
return drogon_test::Status{};
});
nonstd_status.bind(app, nonstd_options);
response.reset();
app.handle("/context-error/data", drogon::Post, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
ADMINIVE_CHECK(response->status == drogon::k500InternalServerError);
response.reset();
app.handle("/status-context-error/data", drogon::Get, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
ADMINIVE_CHECK(response->status == drogon::k500InternalServerError);
ADMINIVE_CHECK(tasks.empty());
request->body = R"({"name":"updated"})";
request->user = "wyc";
response.reset();
@@ -114,6 +141,7 @@ int main() {
});
ADMINIVE_CHECK(response->status == drogon::k200OK);
adminive::Drogon_Collection_Resource<drogon_test::Config, Json> collection("/configs", {drogon_test::Config{"first"}});
collection.register_status<&drogon_test::Config::status>(25);
collection.bind(app, options);
response.reset();
app.handle("/configs/view", drogon::Get, request, [&response](const drogon::HttpResponsePtr& value) {
@@ -174,6 +202,22 @@ int main() {
tasks.erase(tasks.begin());
ADMINIVE_CHECK(response->status == drogon::k200OK);
ADMINIVE_CHECK(Json::parse(response->body).at("data").at("name") == "changed");
request->body = R"({"name":"status-error"})";
response.reset();
app.handle("/configs/2", drogon::Patch, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
tasks.front()();
tasks.erase(tasks.begin());
ADMINIVE_CHECK(response->status == drogon::k200OK);
response.reset();
app.handle("/configs/2/status", drogon::Get, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
ADMINIVE_CHECK(!response);
tasks.front()();
tasks.erase(tasks.begin());
ADMINIVE_CHECK(response->status == drogon::k500InternalServerError);
response.reset();
app.handle("/configs/1", drogon::Delete, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
@@ -23,6 +23,9 @@ struct Row {
bool enabled{true};
int score{};
Item_Status status() {
if(name == "status-error") {
throw std::runtime_error("item status failed");
}
return {};
}
};
@@ -103,6 +106,9 @@ int main() {
if(user == "context-error") {
throw std::runtime_error("context failed");
}
if(user == "context-unknown") {
throw 7;
}
adminive::Request_Context context;
context.user = user;
context.request_id = request.get_header_value("X-Request-Id");
@@ -132,7 +138,9 @@ int main() {
ADMINIVE_CHECK(committed_request_id == "request-42");
ADMINIVE_CHECK(committed_remote == "127.0.0.1");
httplib::Headers context_error_headers{{"X-User", "context-error"}};
httplib::Headers context_unknown_headers{{"X-User", "context-unknown"}};
check_status(client.Post("/config/data", context_error_headers, R"({"name":"not-applied"})", "application/json"), 500);
check_status(client.Post("/config/data", context_unknown_headers, R"({"name":"not-applied"})", "application/json"), 500);
ADMINIVE_CHECK(config.name == "updated");
check_status(client.Get("/status/descriptor"), 200);
check_status(client.Get("/status/amis"), 200);
@@ -140,6 +148,7 @@ int main() {
httplib::Headers status_error_headers{{"X-User", "status-error"}};
check_status(client.Get("/status/data", status_error_headers), 500);
check_status(client.Get("/status/data", context_error_headers), 500);
check_status(client.Get("/status/data", context_unknown_headers), 500);
ADMINIVE_CHECK(body_json(client.Get("/rows/descriptor")).at("data").at("name") == "httplib_row");
ADMINIVE_CHECK(body_json(client.Get("/rows/view")).at("data").at("kind") == "table");
ADMINIVE_CHECK(body_json(client.Get("/rows?perPage=2&page=1&orderBy=score&orderDir=desc")).at("data").at("items").at(0).at("name") == "beta");
@@ -157,6 +166,8 @@ int main() {
ADMINIVE_CHECK(body_json(client.Get(("/rows/" + std::to_string(created_id)).c_str())).at("data").at("score") == 81);
check_status(client.Patch(("/rows/" + std::to_string(created_id)).c_str(), R"({"name":"delta-patched"})", "application/json"), 200);
ADMINIVE_CHECK(body_json(client.Get(("/rows/" + std::to_string(created_id) + "/status").c_str())).at("data").at("state").at("value") == "ready");
check_status(client.Patch(("/rows/" + std::to_string(created_id)).c_str(), R"({"name":"status-error"})", "application/json"), 200);
check_status(client.Get(("/rows/" + std::to_string(created_id) + "/status").c_str()), 500);
check_status(client.Post("/rows/order", R"({"ids":[3,2,1]})", "application/json"), 200);
ADMINIVE_CHECK(body_json(client.Get("/rows?orderBy=id&orderDir=asc")).at("data").at("total") == 4);
check_status(client.Delete(("/rows/" + std::to_string(created_id)).c_str()), 200);
@@ -0,0 +1,55 @@
#include "adminive/adapters/drogon.hpp"
#include "adminive/adapters/nlohmann_json.hpp"
#include "adminive_test.hpp"
#include <string>
namespace real_drogon_test {
struct Status {
std::string state{"ready"};
};
struct Config {
std::string name{"real"};
Status status() {
return {};
}
};
}
namespace adminive {
template <>
struct Type_Descriptor<real_drogon_test::Status> {
static auto get() {
using T = real_drogon_test::Status;
return object<T>("real_drogon_status", ADMINIVE_FIELD(T, state));
}
};
template <>
struct Type_Descriptor<real_drogon_test::Config> {
static auto get() {
using T = real_drogon_test::Config;
return object<T>("real_drogon_config", ADMINIVE_FIELD(T, name).editable().creatable());
}
};
template <>
struct Type_View_Descriptor<real_drogon_test::Config> {
static Table_View table() {
using T = real_drogon_test::Config;
return table_view<T>(column<&T::name>("Name").sort().search());
}
};
}
int main() {
using Json = nlohmann::json;
auto& app = drogon::app();
real_drogon_test::Config config;
adminive::Drogon_Resource<real_drogon_test::Config, Json> resource(config, "/real/config");
resource.bind(app);
adminive::Drogon_Status_Resource<real_drogon_test::Status, Json> status("/real/status", [](const adminive::Request_Context&) {
return real_drogon_test::Status{};
});
status.bind(app);
adminive::Drogon_Collection_Resource<real_drogon_test::Config, Json> collection("/real/configs", {config});
collection.register_status<&real_drogon_test::Config::status>();
collection.bind(app);
ADMINIVE_CHECK(resource.view_schema().at("kind") == "form");
ADMINIVE_CHECK(collection.view_schema().at("kind") == "table");
return 0;
}
@@ -217,6 +217,12 @@ int main() {
query.order_dir = "sideways";
response = collection.list_response(query);
ADMINIVE_CHECK(response.status == 400);
response = collection.create_response("{");
ADMINIVE_CHECK(response.status == 400);
response = collection.update_response(1, "{");
ADMINIVE_CHECK(response.status == 400);
response = collection.reorder_response(R"({"ids":["invalid"]})");
ADMINIVE_CHECK(response.status == 400);
bool invalid_manifest{};
try {
adminive::to_composition_manifest_json<Json>(frontend_composition, {adminive::Composition_Slot_Contract{"left", "amis_schema", {}, {}, {}, {}}}, {{"left", Json{{"type", "tpl"}}}});
+69 -13
View File
@@ -10,31 +10,87 @@ if(NOT TARGET Adminive::Http)
set_property(TARGET Adminive::Http PROPERTY INTERFACE_LINK_LIBRARIES Adminive::Core)
endif()
set(_adminive_service_loaded FALSE)
set(_adminive_nlohmann_loaded FALSE)
set(_adminive_magic_enum_loaded FALSE)
set(_adminive_boost_pfr_loaded FALSE)
set(_adminive_httplib_loaded FALSE)
macro(_adminive_load_service)
if(NOT _adminive_service_loaded)
include("${CMAKE_CURRENT_LIST_DIR}/AdminiveServiceTargets.cmake")
set(_adminive_service_loaded TRUE)
endif()
endmacro()
macro(_adminive_load_nlohmann)
if(NOT _adminive_nlohmann_loaded)
find_dependency(nlohmann_json 3.12.0 CONFIG)
_adminive_load_service()
include("${CMAKE_CURRENT_LIST_DIR}/AdminiveNlohmannTargets.cmake")
set(_adminive_nlohmann_loaded TRUE)
endif()
endmacro()
macro(_adminive_load_magic_enum)
if(NOT _adminive_magic_enum_loaded)
find_dependency(magic_enum 0.9.8 CONFIG)
_adminive_load_service()
include("${CMAKE_CURRENT_LIST_DIR}/AdminiveMagicEnumTargets.cmake")
set(_adminive_magic_enum_loaded TRUE)
endif()
endmacro()
macro(_adminive_load_boost_pfr)
if(NOT _adminive_boost_pfr_loaded)
find_dependency(Boost CONFIG)
_adminive_load_service()
include("${CMAKE_CURRENT_LIST_DIR}/AdminiveBoostPfrTargets.cmake")
set(_adminive_boost_pfr_loaded TRUE)
endif()
endmacro()
macro(_adminive_load_httplib)
if(NOT _adminive_httplib_loaded)
find_dependency(httplib 0.48.0 CONFIG)
_adminive_load_service()
include("${CMAKE_CURRENT_LIST_DIR}/AdminiveHttplibTargets.cmake")
set(_adminive_httplib_loaded TRUE)
endif()
endmacro()
foreach(component IN LISTS Adminive_FIND_COMPONENTS)
if(component STREQUAL "Core" OR component STREQUAL "Http")
set(Adminive_${component}_FOUND TRUE)
elseif(component STREQUAL "Service" OR component STREQUAL "Nlohmann" OR component STREQUAL "MagicEnum" OR component STREQUAL "BoostPfr" OR component STREQUAL "Httplib" OR component STREQUAL "Default")
if(component STREQUAL "BoostPfr")
find_dependency(Boost CONFIG)
endif()
if(NOT _adminive_service_loaded)
include("${CMAKE_CURRENT_LIST_DIR}/AdminiveServiceTargets.cmake")
set(_adminive_service_loaded TRUE)
endif()
set(Adminive_${component}_FOUND TRUE)
elseif(component STREQUAL "Service")
_adminive_load_service()
set(Adminive_Service_FOUND TRUE)
elseif(component STREQUAL "Nlohmann")
_adminive_load_nlohmann()
set(Adminive_Nlohmann_FOUND TRUE)
elseif(component STREQUAL "MagicEnum")
_adminive_load_magic_enum()
set(Adminive_MagicEnum_FOUND TRUE)
elseif(component STREQUAL "BoostPfr")
_adminive_load_boost_pfr()
set(Adminive_BoostPfr_FOUND TRUE)
elseif(component STREQUAL "Httplib")
_adminive_load_httplib()
set(Adminive_Httplib_FOUND TRUE)
elseif(component STREQUAL "Default")
_adminive_load_nlohmann()
_adminive_load_magic_enum()
_adminive_load_httplib()
include("${CMAKE_CURRENT_LIST_DIR}/AdminiveDefaultTargets.cmake")
set(Adminive_Default_FOUND TRUE)
elseif(component STREQUAL "Drogon")
if(NOT _adminive_service_loaded)
include("${CMAKE_CURRENT_LIST_DIR}/AdminiveServiceTargets.cmake")
set(_adminive_service_loaded TRUE)
endif()
find_dependency(Drogon CONFIG)
if(TARGET drogon AND NOT TARGET Drogon::Drogon)
add_library(Drogon::Drogon ALIAS drogon)
endif()
_adminive_load_service()
include("${CMAKE_CURRENT_LIST_DIR}/AdminiveDrogonTargets.cmake")
set(Adminive_Drogon_FOUND TRUE)
else()
set(Adminive_${component}_FOUND FALSE)
endif()
endforeach()
unset(_adminive_service_loaded)
unset(_adminive_nlohmann_loaded)
unset(_adminive_magic_enum_loaded)
unset(_adminive_boost_pfr_loaded)
unset(_adminive_httplib_loaded)
check_required_components(Adminive)
+22 -1
View File
@@ -8,13 +8,34 @@ get_filename_component(adminive_root "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
set(adminive_consumer_source "${adminive_root}/install_consumer")
set(adminive_test_root "${ADMINIVE_BINARY_DIR}/install_consumer_test")
set(adminive_prefix "${adminive_test_root}/prefix")
set(adminive_dependency_prefix "${adminive_test_root}/dependencies")
set(adminive_consumer_binary "${adminive_test_root}/build")
set(adminive_vendor_include "${adminive_root}/backend/service/third_party/include")
file(REMOVE_RECURSE "${adminive_test_root}")
execute_process(COMMAND "${CMAKE_COMMAND}" --install "${ADMINIVE_BINARY_DIR}" --prefix "${adminive_prefix}" --config "${ADMINIVE_CONFIG}" RESULT_VARIABLE install_result)
if(NOT install_result EQUAL 0)
message(FATAL_ERROR "Adminive install failed: ${install_result}")
endif()
execute_process(COMMAND "${CMAKE_COMMAND}" -S "${adminive_consumer_source}" -B "${adminive_consumer_binary}" "-DCMAKE_PREFIX_PATH=${adminive_prefix}" "-DCMAKE_BUILD_TYPE=${ADMINIVE_CONFIG}" RESULT_VARIABLE configure_result)
foreach(path IN ITEMS "${adminive_prefix}/include/nlohmann" "${adminive_prefix}/include/magic_enum" "${adminive_prefix}/include/httplib.h")
if(EXISTS "${path}")
message(FATAL_ERROR "Installed Adminive package leaked bundled dependency into the public include namespace: ${path}")
endif()
endforeach()
foreach(path IN ITEMS "${adminive_prefix}/share/Adminive/THIRD_PARTY_NOTICES.md" "${adminive_prefix}/share/Adminive/RELEASE.md" "${adminive_prefix}/share/licenses/Adminive/nlohmann-json-LICENSE.MIT" "${adminive_prefix}/share/licenses/Adminive/magic-enum-LICENSE.MIT" "${adminive_prefix}/share/licenses/Adminive/cpp-httplib-LICENSE.MIT")
if(NOT EXISTS "${path}")
message(FATAL_ERROR "Installed Adminive package is missing release/license metadata: ${path}")
endif()
endforeach()
function(adminive_write_fake_dependency package version target)
set(package_dir "${adminive_dependency_prefix}/lib/cmake/${package}")
file(MAKE_DIRECTORY "${package_dir}")
file(WRITE "${package_dir}/${package}Config.cmake" "if(NOT TARGET ${target})\n add_library(${target} INTERFACE IMPORTED)\n set_property(TARGET ${target} PROPERTY INTERFACE_INCLUDE_DIRECTORIES \"${adminive_vendor_include}\")\nendif()\n")
file(WRITE "${package_dir}/${package}ConfigVersion.cmake" "set(PACKAGE_VERSION \"${version}\")\nif(PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION)\n set(PACKAGE_VERSION_COMPATIBLE FALSE)\nelse()\n set(PACKAGE_VERSION_COMPATIBLE TRUE)\n if(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION)\n set(PACKAGE_VERSION_EXACT TRUE)\n endif()\nendif()\n")
endfunction()
adminive_write_fake_dependency(nlohmann_json 3.12.0 nlohmann_json::nlohmann_json)
adminive_write_fake_dependency(magic_enum 0.9.8 magic_enum::magic_enum)
adminive_write_fake_dependency(httplib 0.48.0 httplib::httplib)
execute_process(COMMAND "${CMAKE_COMMAND}" -S "${adminive_consumer_source}" -B "${adminive_consumer_binary}" "-DCMAKE_PREFIX_PATH=${adminive_prefix};${adminive_dependency_prefix}" "-DCMAKE_BUILD_TYPE=${ADMINIVE_CONFIG}" RESULT_VARIABLE configure_result)
if(NOT configure_result EQUAL 0)
message(FATAL_ERROR "Install consumer configure failed: ${configure_result}")
endif()
+24
View File
@@ -23,6 +23,13 @@ set(root_agents_count 0)
set(adminive_skill_count 0)
set(structive_agents_count 0)
set(structive_skill_count 0)
set(release_policy_count 0)
set(third_party_notices_count 0)
set(nlohmann_license_count 0)
set(magic_enum_license_count 0)
set(httplib_license_count 0)
set(structive_release_count 0)
set(structive_notices_count 0)
foreach(entry IN LISTS archive_entries)
if(entry MATCHES "^Adminive/")
message(FATAL_ERROR "package_zip contains nested stale project entry: ${entry}")
@@ -41,6 +48,20 @@ foreach(entry IN LISTS archive_entries)
math(EXPR structive_agents_count "${structive_agents_count} + 1")
elseif(entry STREQUAL "third_party/Structive/.agents/skills/structive-development/SKILL.md")
math(EXPR structive_skill_count "${structive_skill_count} + 1")
elseif(entry STREQUAL "RELEASE.md")
math(EXPR release_policy_count "${release_policy_count} + 1")
elseif(entry STREQUAL "THIRD_PARTY_NOTICES.md")
math(EXPR third_party_notices_count "${third_party_notices_count} + 1")
elseif(entry STREQUAL "third_party/licenses/nlohmann-json-LICENSE.MIT")
math(EXPR nlohmann_license_count "${nlohmann_license_count} + 1")
elseif(entry STREQUAL "third_party/licenses/magic-enum-LICENSE.MIT")
math(EXPR magic_enum_license_count "${magic_enum_license_count} + 1")
elseif(entry STREQUAL "third_party/licenses/cpp-httplib-LICENSE.MIT")
math(EXPR httplib_license_count "${httplib_license_count} + 1")
elseif(entry STREQUAL "third_party/Structive/RELEASE.md")
math(EXPR structive_release_count "${structive_release_count} + 1")
elseif(entry STREQUAL "third_party/Structive/THIRD_PARTY_NOTICES.md")
math(EXPR structive_notices_count "${structive_notices_count} + 1")
elseif(entry STREQUAL "backend/library/include/adminive/synchronized.hpp" OR entry STREQUAL "backend/service/tests/synchronized_test.cpp")
message(FATAL_ERROR "package_zip contains removed synchronized implementation: ${entry}")
endif()
@@ -51,3 +72,6 @@ endif()
if(NOT root_agents_count EQUAL 1 OR NOT adminive_skill_count EQUAL 1 OR NOT structive_agents_count EQUAL 1 OR NOT structive_skill_count EQUAL 1)
message(FATAL_ERROR "package_zip is missing Codex repository guidance")
endif()
if(NOT release_policy_count EQUAL 1 OR NOT third_party_notices_count EQUAL 1 OR NOT nlohmann_license_count EQUAL 1 OR NOT magic_enum_license_count EQUAL 1 OR NOT httplib_license_count EQUAL 1 OR NOT structive_release_count EQUAL 1 OR NOT structive_notices_count EQUAL 1)
message(FATAL_ERROR "package_zip is missing release or third-party license metadata")
endif()
+39
View File
@@ -0,0 +1,39 @@
if(NOT DEFINED ADMINIVE_BINARY_DIR)
message(FATAL_ERROR "ADMINIVE_BINARY_DIR is required")
endif()
if(NOT DEFINED ADMINIVE_CONFIG OR ADMINIVE_CONFIG STREQUAL "")
set(ADMINIVE_CONFIG Release)
endif()
if(NOT DEFINED Drogon_DIR OR Drogon_DIR STREQUAL "")
message(FATAL_ERROR "Drogon_DIR is required")
endif()
if(NOT DEFINED nlohmann_json_DIR OR nlohmann_json_DIR STREQUAL "")
message(FATAL_ERROR "nlohmann_json_DIR is required")
endif()
get_filename_component(adminive_root "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
set(adminive_source "${adminive_root}/install_consumer/real_drogon")
set(adminive_test_root "${ADMINIVE_BINARY_DIR}/real_drogon_install_consumer_test")
set(adminive_prefix "${adminive_test_root}/prefix")
set(adminive_binary "${adminive_test_root}/build")
file(REMOVE_RECURSE "${adminive_test_root}")
execute_process(COMMAND "${CMAKE_COMMAND}" --install "${ADMINIVE_BINARY_DIR}" --prefix "${adminive_prefix}" --config "${ADMINIVE_CONFIG}" RESULT_VARIABLE install_result)
if(NOT install_result EQUAL 0)
message(FATAL_ERROR "Adminive install failed: ${install_result}")
endif()
execute_process(COMMAND "${CMAKE_COMMAND}" -S "${adminive_source}" -B "${adminive_binary}" "-DCMAKE_PREFIX_PATH=${adminive_prefix}" "-DDrogon_DIR=${Drogon_DIR}" "-Dnlohmann_json_DIR=${nlohmann_json_DIR}" "-DCMAKE_BUILD_TYPE=${ADMINIVE_CONFIG}" RESULT_VARIABLE configure_result)
if(NOT configure_result EQUAL 0)
message(FATAL_ERROR "Real Drogon install consumer configure failed: ${configure_result}")
endif()
execute_process(COMMAND "${CMAKE_COMMAND}" --build "${adminive_binary}" --config "${ADMINIVE_CONFIG}" RESULT_VARIABLE build_result)
if(NOT build_result EQUAL 0)
message(FATAL_ERROR "Real Drogon install consumer build failed: ${build_result}")
endif()
if(WIN32)
set(adminive_consumer "${adminive_binary}/${ADMINIVE_CONFIG}/real_drogon_consumer.exe")
else()
set(adminive_consumer "${adminive_binary}/real_drogon_consumer")
endif()
execute_process(COMMAND "${adminive_consumer}" RESULT_VARIABLE run_result)
if(NOT run_result EQUAL 0)
message(FATAL_ERROR "Real Drogon install consumer run failed: ${run_result}")
endif()
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.20)
project(AdminiveRealDrogonInstallConsumer LANGUAGES CXX)
find_package(Adminive CONFIG REQUIRED COMPONENTS Core Nlohmann Drogon)
add_executable(real_drogon_consumer main.cpp)
target_link_libraries(real_drogon_consumer PRIVATE Adminive::Drogon Adminive::Nlohmann)
target_compile_features(real_drogon_consumer PRIVATE cxx_std_20)
+24
View File
@@ -0,0 +1,24 @@
#include <adminive/adapters/drogon.hpp>
#include <adminive/adapters/nlohmann_json.hpp>
#include <string>
namespace real_drogon_install_consumer {
struct Config {
std::string name{"installed"};
};
}
namespace adminive {
template <>
struct Type_Descriptor<real_drogon_install_consumer::Config> {
static auto get() {
using T = real_drogon_install_consumer::Config;
return object<T>("installed_real_drogon", ADMINIVE_FIELD(T, name).editable());
}
};
}
int main() {
using Json = nlohmann::json;
real_drogon_install_consumer::Config config;
adminive::Drogon_Resource<real_drogon_install_consumer::Config, Json> resource(config, "/installed-real-drogon");
resource.bind(drogon::app());
return resource.view_schema().at("kind") == "form" ? 0 : 1;
}
+7 -3
View File
@@ -87,7 +87,7 @@ def run_e2e(build_dir: Path, configuration: str, edge: bool) -> None:
output = process.stdout.read().decode(errors="replace")
if output:
print(output, end="")
def full_verification() -> None:
def full_verification(real_drogon: bool) -> None:
if VERIFICATION.exists():
shutil.rmtree(VERIFICATION)
debug_dir = VERIFICATION / "debug"
@@ -97,7 +97,8 @@ def full_verification() -> None:
configure(debug_dir, "Debug")
build(debug_dir, "Debug")
ctest(debug_dir, "Debug")
configure(release_dir, "Release")
release_extra = ["-DADMINIVE_ENABLE_REAL_DROGON_TESTS=ON"] if real_drogon else None
configure(release_dir, "Release", release_extra)
build(release_dir, "Release")
ctest(release_dir, "Release")
if os.name == "nt":
@@ -118,12 +119,15 @@ def full_verification() -> None:
run_e2e(release_dir, "Release", False)
if os.name == "nt":
run_e2e(release_dir, "Release", True)
if not real_drogon:
print("SKIP: real Drogon compile/install gate; run with --real-drogon on a machine with Drogon and nlohmann_json CMake packages installed", flush=True)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--e2e-only", action="store_true")
parser.add_argument("--edge", action="store_true")
parser.add_argument("--binary-dir", type=Path)
parser.add_argument("--configuration", default="Release")
parser.add_argument("--real-drogon", action="store_true")
args = parser.parse_args()
if args.edge and os.name != "nt":
raise RuntimeError("Edge E2E requires Windows with Microsoft Edge installed")
@@ -132,7 +136,7 @@ def main() -> int:
raise RuntimeError("--binary-dir is required with --e2e-only")
run_e2e(args.binary_dir.resolve(), args.configuration or "Release", args.edge)
return 0
full_verification()
full_verification(args.real_drogon)
return 0
if __name__ == "__main__":
sys.exit(main())