build: arborescence bin/<plateforme>/<type> et build unifié (ADR 0010)
- platform/layout : racine sequa-root.json, chemins des composants, arguments et chemins en UTF-8.
- Règles install() : bin/windows-x64/{debug,release,asan}/ (core, render, studio + ui, engines).
- Le Core trouve sequa-render et les adaptateurs depuis la racine (--sequa-root, passé par
Studio host à sa copie fantôme) : plus de render-unavailable depuis le panneau Rendu.
- L'adaptateur Godot cherche Godot et son projet à côté de lui (surcharge par --adapter-options) ;
template_release pour le type release. Godot hébergé dans les presets debug et release.
- scripts/build-all.ps1 : Godot, C++, interface, arrêt des processus du dossier visé, install ;
check.ps1 l'appelle (modification de l'ADR 0004).
- docs/installation/ : compiler, lancer, installer et désinstaller par plateforme.
This commit is contained in:
1 parent
28b1b44294
commit
da0d31385b
51 files changed
+1468
-197
No files matched your search
@@ -1,5 +1,7 @@
|
||||
# Build
|
||||
build/
|
||||
/bin/
|
||||
/installers/
|
||||
out/
|
||||
cmake-build-*/
|
||||
CMakeUserPresets.json
|
||||
|
||||
@@ -28,12 +28,14 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||
include(SequaCompilerOptions)
|
||||
include(SequaDependencies)
|
||||
include(SequaInstall) # bin/<plateforme>/<type>/ (ADR 0010)
|
||||
|
||||
if(SEQUA_BUILD_TESTS)
|
||||
enable_testing()
|
||||
endif()
|
||||
|
||||
add_subdirectory(platform/process) # lancement de processus enfants (Studio host, Core)
|
||||
add_subdirectory(platform/layout) # arborescence des binaires, racine sequa-root.json (ADR 0010)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(3d)
|
||||
add_subdirectory(engines/godot/adapter) # Godot lui-même n'est pas compilé ici (SCons, scripts/build-godot.ps1)
|
||||
@@ -43,6 +45,9 @@ if(SEQUA_BUILD_SERVER)
|
||||
add_subdirectory(studio/host) # dépend d'IXWebSocket et nlohmann/json
|
||||
endif()
|
||||
|
||||
# Interface construite par npm run build (studio/ui/dist), absente tant qu'elle n'est pas construite.
|
||||
install(DIRECTORY "${PROJECT_SOURCE_DIR}/studio/ui/dist/" DESTINATION studio/ui OPTIONAL)
|
||||
|
||||
if(SEQUA_BUILD_PROTOTYPES AND EXISTS "${PROJECT_SOURCE_DIR}/prototypes/CMakeLists.txt")
|
||||
add_subdirectory(prototypes)
|
||||
endif()
|
||||
+6
-4
@@ -23,25 +23,26 @@
|
||||
"name": "windows-msvc-debug",
|
||||
"displayName": "Windows MSVC Debug",
|
||||
"inherits": "windows-base",
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "SEQUA_BIN_TYPE": "debug", "SEQUA_BUILD_GODOT_RUNTIME": "ON" }
|
||||
},
|
||||
{
|
||||
"name": "windows-msvc-release",
|
||||
"displayName": "Windows MSVC Release",
|
||||
"inherits": "windows-base",
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" }
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo", "SEQUA_BIN_TYPE": "release", "SEQUA_BUILD_GODOT_RUNTIME": "ON" }
|
||||
},
|
||||
{
|
||||
"name": "windows-msvc-asan",
|
||||
"displayName": "Windows MSVC AddressSanitizer",
|
||||
"inherits": "windows-base",
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "SEQUA_SANITIZERS": "address" }
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "SEQUA_BIN_TYPE": "asan", "SEQUA_SANITIZERS": "address" }
|
||||
},
|
||||
{
|
||||
"name": "windows-msvc-prototypes",
|
||||
"displayName": "Windows MSVC Release + prototypes",
|
||||
"inherits": "windows-msvc-release",
|
||||
"cacheVariables": { "SEQUA_BUILD_PROTOTYPES": "ON", "SEQUA_BUILD_GODOT_RUNTIME": "ON" }
|
||||
"installDir": "${sourceDir}/build/${presetName}/install",
|
||||
"cacheVariables": { "SEQUA_BUILD_PROTOTYPES": "ON" }
|
||||
},
|
||||
{
|
||||
"name": "linux-base",
|
||||
@@ -62,6 +63,7 @@
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"SEQUA_BIN_TYPE": "asan",
|
||||
"SEQUA_SANITIZERS": "address;undefined"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ Documentation de référence : [docs/SEQUA_Engine_resume_complet_v2.md](docs/SEQ
|
||||
| [3d/](3d) | SEQUA 3D : interface vers les moteurs 3D (adaptateurs, capacités, synchronisation) | C++20 |
|
||||
| [render/](render) | `sequa-render` : processus de rendu lancé par le Core, charge une bibliothèque d'adaptateur ([ADR 0009](docs/adr/0009-processus-de-rendu-lance-par-le-core.md)) | C++20 |
|
||||
| [engines/](engines) | Adaptateurs des moteurs 3D : `godot/adapter` + `godot/vendor/godot-src` (fork de Godot 4.7.2-stable) et `godot/vendor/godot-cpp` (10.0.0-stable), sous-modules ; `debug/adapter` (rendu 2D sans GPU, en images ou vidéo) | C++20 |
|
||||
| [platform/](platform) | Code propre à chaque système, partagé (lancement de processus enfants liés au parent) | C++20 |
|
||||
| [platform/](platform) | Code propre à chaque système, partagé : lancement de processus enfants liés au parent (`process/`), arborescence des binaires et racine `sequa-root.json` (`layout/`) | C++20 |
|
||||
| [schema/](schema) | Contrat de données et protocole Studio↔Core | Protobuf / FlatBuffers |
|
||||
| [docs/](docs) | Documentation, décisions d'architecture (`docs/adr/`), spécifications des protocoles (`docs/specs/`), tutoriels (`docs/tutoriels/`) | Markdown |
|
||||
| [artwork/](artwork) | Identité graphique et sources visuelles | PNG |
|
||||
@@ -38,14 +38,15 @@ Pas encore de rendu neuronal ni de carte de confiance. Seul Windows a été cons
|
||||
|
||||
## Construire et vérifier
|
||||
|
||||
Il n'y a pas d'intégration continue sur serveur : les vérifications se lancent localement.
|
||||
Il n'y a pas d'intégration continue sur serveur : les vérifications se lancent localement. Voir aussi [docs/installation/](docs/installation/) pour les pages par plateforme (prérequis, compiler, lancer, installer, désinstaller).
|
||||
|
||||
| Plateforme | Commande | Prérequis |
|
||||
|---|---|---|
|
||||
| Windows | `.\scripts\build.ps1 [-Preset <nom>] [-Clean] [-NoTests]` | Visual Studio 2022 avec outils C++ (MSVC, CMake et Ninja fournis) |
|
||||
| Windows (build complet) | `.\scripts\build-all.ps1 [-Type debug\|release\|asan\|all] [-NoTests] [-Godot] [-SkipGodot] [-SkipUi] [-NoStop] [-Clean]` | Visual Studio 2022 avec outils C++ (MSVC, CMake, Ninja fournis), Python 3, Node.js 24, sous-modules Godot, GPU Vulkan |
|
||||
| Windows (un preset CMake seul) | `.\scripts\build.ps1 [-Preset <nom>] [-Clean] [-NoTests]` | idem |
|
||||
| Windows, avant commit | `.\scripts\check.ps1` | idem + Node.js pour Studio |
|
||||
| Linux / macOS | `./scripts/build.sh [<preset>]` | CMake ≥ 3.25, Ninja, GCC ou Clang |
|
||||
| Lancer SEQUA Studio (Core + interface) | `.\build\windows-msvc-debug\studio\host\app\sequa-studio.exe [--dev] [--watch]` (voir [studio/host](studio/host/README.md)) | build + `npm run build` dans `studio/ui` |
|
||||
| Lancer SEQUA Studio (Core + interface) | `.\bin\windows-x64\debug\studio\sequa-studio.exe [--dev] [--watch]` | build complet |
|
||||
| Interface seule (développement) | `.\scripts\studio.ps1 [-Core ws://hôte:port] [-MockCore]` ou `./scripts/studio.sh` | Node.js 24 |
|
||||
| Windows, cuDNN pour l'inférence GPU | `.\scripts\install-cudnn.ps1 [-Version <x.y.z>] [-CudaMajor 12\|13] [-DryRun]` | CUDA Toolkit installé + GPU NVIDIA ; télécharge cuDNN (~1,3 Go), demande d'accepter la licence NVIDIA et définit `CUDNN_PATH` (utilisateur, sans droits admin) |
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# Arborescence installée (ADR 0010) : bin/<plateforme>/<type>/ en développement, même
|
||||
# contenu dans le dossier d'installation. `cmake --install <build>` la remplit.
|
||||
|
||||
# Plateforme : système et processeur visés (windows-x64, linux-arm64…).
|
||||
if(WIN32)
|
||||
set(_sequa_os "windows")
|
||||
elseif(APPLE)
|
||||
set(_sequa_os "macos")
|
||||
else()
|
||||
string(TOLOWER "${CMAKE_SYSTEM_NAME}" _sequa_os)
|
||||
endif()
|
||||
if(MSVC)
|
||||
set(_sequa_cpu "${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}")
|
||||
else()
|
||||
set(_sequa_cpu "${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
string(TOLOWER "${_sequa_cpu}" _sequa_cpu)
|
||||
if(_sequa_cpu MATCHES "^(amd64|x86_64|x64)$")
|
||||
set(_sequa_cpu "x64")
|
||||
elseif(_sequa_cpu MATCHES "^(arm64|aarch64)$")
|
||||
set(_sequa_cpu "arm64")
|
||||
endif()
|
||||
set(SEQUA_PLATFORM "${_sequa_os}-${_sequa_cpu}" CACHE STRING "Plateforme de bin/<plateforme>/<type>")
|
||||
|
||||
# Type : fixé par le preset (debug, release, asan) ; à défaut, d'après CMAKE_BUILD_TYPE.
|
||||
if(CMAKE_BUILD_TYPE MATCHES "^(Release|RelWithDebInfo|MinSizeRel)$")
|
||||
set(_sequa_default_type "release")
|
||||
else()
|
||||
set(_sequa_default_type "debug")
|
||||
endif()
|
||||
set(SEQUA_BIN_TYPE "${_sequa_default_type}" CACHE STRING "Type de bin/<plateforme>/<type>")
|
||||
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${PROJECT_SOURCE_DIR}/bin/${SEQUA_PLATFORM}/${SEQUA_BIN_TYPE}"
|
||||
CACHE PATH "Racine de l'arborescence installée" FORCE)
|
||||
endif()
|
||||
message(STATUS "SEQUA : installation dans ${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
# Repère de la racine (sequa::platform::find_root).
|
||||
configure_file("${CMAKE_CURRENT_LIST_DIR}/sequa-root.json.in" "${PROJECT_BINARY_DIR}/sequa-root.json" @ONLY)
|
||||
install(FILES "${PROJECT_BINARY_DIR}/sequa-root.json" DESTINATION .)
|
||||
|
||||
# Bibliothèques d'exécution qu'un exécutable lancé hors de l'invite de Visual Studio ne
|
||||
# trouverait pas : ASan de MSVC, à côté de chaque exécutable.
|
||||
set(SEQUA_RUNTIME_FILES "")
|
||||
if(MSVC AND SEQUA_SANITIZERS MATCHES "address")
|
||||
get_filename_component(_sequa_cl_dir "${CMAKE_CXX_COMPILER}" DIRECTORY)
|
||||
file(GLOB SEQUA_RUNTIME_FILES "${_sequa_cl_dir}/clang_rt.asan_dynamic-*.dll")
|
||||
if(NOT SEQUA_RUNTIME_FILES)
|
||||
message(WARNING "Bibliothèque ASan introuvable dans ${_sequa_cl_dir} : bin/ ne se lancera que depuis l'invite de Visual Studio")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Installe un exécutable dans <destination>/ avec les bibliothèques d'exécution.
|
||||
function(sequa_install_executable target destination)
|
||||
install(TARGETS ${target} RUNTIME DESTINATION ${destination})
|
||||
if(SEQUA_RUNTIME_FILES)
|
||||
install(FILES ${SEQUA_RUNTIME_FILES} DESTINATION ${destination})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Installe une bibliothèque d'adaptateur (MODULE) dans engines/<moteur>/.
|
||||
function(sequa_install_adapter target engine)
|
||||
install(TARGETS ${target} LIBRARY DESTINATION engines/${engine} RUNTIME DESTINATION engines/${engine})
|
||||
endfunction()
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"layout": 1,
|
||||
"sequaVersion": "@PROJECT_VERSION@",
|
||||
"platform": "@SEQUA_PLATFORM@",
|
||||
"type": "@SEQUA_BIN_TYPE@"
|
||||
}
|
||||
+3
-2
@@ -38,7 +38,7 @@ if(SEQUA_BUILD_SERVER)
|
||||
add_library(sequa::core_server ALIAS sequa_core_server)
|
||||
target_link_libraries(sequa_core_server
|
||||
PUBLIC sequa::core sequa::3d
|
||||
PRIVATE sequa::core_render sequa::3d_wire sequa::process nlohmann_json::nlohmann_json ixwebsocket::ixwebsocket)
|
||||
PRIVATE sequa::core_render sequa::3d_wire sequa::process sequa::layout nlohmann_json::nlohmann_json ixwebsocket::ixwebsocket)
|
||||
if(WIN32)
|
||||
target_link_libraries(sequa_core_server PRIVATE ws2_32) # vérification exclusive du port
|
||||
endif()
|
||||
@@ -48,8 +48,9 @@ if(SEQUA_BUILD_SERVER)
|
||||
set_target_properties(sequa_core_app PROPERTIES OUTPUT_NAME sequa-core)
|
||||
# L'application utilise le superviseur du rendu, interne à sequa_core_server.
|
||||
target_include_directories(sequa_core_app PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/server")
|
||||
target_link_libraries(sequa_core_app PRIVATE sequa::core_server sequa::core_render sequa::process)
|
||||
target_link_libraries(sequa_core_app PRIVATE sequa::core_server sequa::core_render sequa::process sequa::layout)
|
||||
sequa_target_defaults(sequa_core_app)
|
||||
sequa_install_executable(sequa_core_app core)
|
||||
endif()
|
||||
|
||||
# --- Tests ------------------------------------------------------------------------------
|
||||
|
||||
+3
-2
@@ -44,10 +44,11 @@ Protocole **0.2**, JSON provisoire, spécifié dans [docs/specs/protocole-studio
|
||||
- `CoreRuntime` (implémente `CoreServices`) : bail réversible (`--linked`, attach/detach), demande d'arrêt, `instanceId`, compteurs.
|
||||
- `LiveSession` : session en direct à 60 Hz (fil dédié), entrée maintenue, export (enregistrement + empreintes par tick), import par rejeu puis poursuite dans le même enregistrement.
|
||||
- `CoreServer` : transport IXWebSocket, jeton (`?token=`) et contrôle de l'`Origin` à l'ouverture, écoute locale par défaut, refus explicite si le port est déjà pris.
|
||||
- Rendu piloté par Studio (protocole 0.3, §9 ; ADR 0010) : `--render <godot|debug>` lance `sequa-render` depuis l'arborescence `bin/<plateforme>/<type>/` (repérée via `sequa-root.json`) ; option `--sequa-root <dossier>` pour forcer la racine.
|
||||
|
||||
```powershell
|
||||
.\build\windows-msvc-debug\core\sequa-core.exe --help
|
||||
.\build\windows-msvc-debug\core\sequa-core.exe --port 9100 --token <hex> --linked
|
||||
.\bin\windows-x64\debug\core\sequa-core.exe --help
|
||||
.\bin\windows-x64\debug\core\sequa-core.exe --port 9100 --token <hex> --linked
|
||||
```
|
||||
|
||||
En usage normal, c'est Studio host (`sequa-studio`) qui lance et supervise le Core.
|
||||
|
||||
+30
-29
@@ -8,6 +8,8 @@
|
||||
#include "sequa/core/server/server.hpp"
|
||||
#include "sequa/core/version.hpp"
|
||||
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#include <charconv>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
@@ -21,6 +23,7 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@@ -59,6 +62,7 @@ void print_usage()
|
||||
" --render-fps <n> cadence imposée au rendu (debug : 10 conseillé)\n"
|
||||
" --render-options <json> réglages propres à l'adaptateur\n"
|
||||
" --render-log <fichier> journal du rendu (défaut : sortie du Core)\n"
|
||||
" --sequa-root <dossier> racine de l'arborescence (défaut : trouvée depuis l'exécutable)\n"
|
||||
" --help\n"
|
||||
"Codes de sortie : 0 arrêt normal, 1 démarrage impossible, 2 arguments invalides, 3 bail expiré.\n");
|
||||
}
|
||||
@@ -74,19 +78,6 @@ std::optional<T> parse_number(std::string_view text, int base = 10)
|
||||
return value;
|
||||
}
|
||||
|
||||
std::filesystem::path current_executable()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
std::wstring buffer(32768, L'\0');
|
||||
const DWORD size = GetModuleFileNameW(nullptr, buffer.data(), static_cast<DWORD>(buffer.size()));
|
||||
buffer.resize(size);
|
||||
return std::filesystem::path(buffer);
|
||||
#else
|
||||
std::error_code error;
|
||||
return std::filesystem::read_symlink("/proc/self/exe", error);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv)
|
||||
@@ -97,17 +88,20 @@ int main(int argc, char** argv)
|
||||
SetConsoleOutputCP(CP_UTF8); // journaux en UTF-8 (accents, JSON reçu)
|
||||
#endif
|
||||
|
||||
// Arguments en UTF-8 (Windows : CommandLineToArgvW ; POSIX : argv tel quel).
|
||||
const std::vector<std::string> args = sequa::platform::utf8_arguments(argc, argv);
|
||||
|
||||
server::ServerOptions options;
|
||||
server::CoreRuntimeOptions runtime_options;
|
||||
std::string render_adapter; // vide : pas de rendu
|
||||
server::RenderStartRequest render_request; // paramètres de render/start
|
||||
runtime_options.core_version = std::string(version_string());
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string_view arg = argv[i];
|
||||
const bool has_value = i + 1 < argc;
|
||||
for (std::size_t i = 0; i < args.size(); ++i) {
|
||||
const std::string_view arg = args[i];
|
||||
const bool has_value = i + 1 < args.size();
|
||||
const auto invalid = [&](const char* what) {
|
||||
std::fprintf(stderr, "%s invalide : %s\n", what, argv[i]);
|
||||
std::fprintf(stderr, "%s invalide : %s\n", what, arg.data());
|
||||
return exit_bad_arguments;
|
||||
};
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
@@ -117,52 +111,54 @@ int main(int argc, char** argv)
|
||||
if (arg == "--linked") {
|
||||
runtime_options.linked = true;
|
||||
} else if (arg == "--host" && has_value) {
|
||||
options.host = argv[++i];
|
||||
options.host = args[++i];
|
||||
} else if (arg == "--port" && has_value) {
|
||||
const auto port = parse_number<int>(argv[++i]);
|
||||
const auto port = parse_number<int>(args[++i]);
|
||||
if (!port || *port <= 0 || *port > 65535) {
|
||||
return invalid("Port");
|
||||
}
|
||||
options.port = *port;
|
||||
} else if (arg == "--token" && has_value) {
|
||||
options.token = argv[++i];
|
||||
options.token = args[++i];
|
||||
if (options.token.empty()) {
|
||||
return invalid("Jeton");
|
||||
}
|
||||
} else if (arg == "--allowed-origin" && has_value) {
|
||||
options.allowed_origins.emplace_back(argv[++i]);
|
||||
options.allowed_origins.emplace_back(args[++i]);
|
||||
} else if (arg == "--lease-grace-ms" && has_value) {
|
||||
const auto grace = parse_number<long long>(argv[++i]);
|
||||
const auto grace = parse_number<long long>(args[++i]);
|
||||
if (!grace || *grace < 0) {
|
||||
return invalid("Délai de grâce");
|
||||
}
|
||||
runtime_options.lease_grace = std::chrono::milliseconds(*grace);
|
||||
} else if (arg == "--seed" && has_value) {
|
||||
const std::string_view text = argv[++i];
|
||||
const std::string_view text = args[++i];
|
||||
const auto seed = text.size() <= 16 ? parse_number<std::uint64_t>(text, 16) : std::nullopt;
|
||||
if (!seed) {
|
||||
return invalid("Graine");
|
||||
}
|
||||
runtime_options.seed = *seed;
|
||||
} else if (arg == "--render" && has_value) {
|
||||
render_adapter = argv[++i];
|
||||
render_adapter = args[++i];
|
||||
if (render_adapter != "godot" && render_adapter != "debug") {
|
||||
return invalid("Adaptateur de rendu");
|
||||
}
|
||||
} else if (arg == "--render-headless") {
|
||||
render_request.headless = true;
|
||||
} else if (arg == "--render-fps" && has_value) {
|
||||
const auto fps = parse_number<std::uint32_t>(argv[++i]);
|
||||
const auto fps = parse_number<std::uint32_t>(args[++i]);
|
||||
if (!fps || *fps > 240) {
|
||||
return invalid("Cadence de rendu");
|
||||
}
|
||||
render_request.fps = *fps;
|
||||
} else if (arg == "--render-options" && has_value) {
|
||||
render_request.adapter_options = argv[++i];
|
||||
render_request.adapter_options = args[++i];
|
||||
} else if (arg == "--render-log" && has_value) {
|
||||
runtime_options.render_log = argv[++i];
|
||||
runtime_options.render_log = sequa::platform::path_from_utf8(args[++i]);
|
||||
} else if (arg == "--sequa-root" && has_value) {
|
||||
runtime_options.sequa_root = sequa::platform::path_from_utf8(args[++i]);
|
||||
} else {
|
||||
std::fprintf(stderr, "Argument inconnu : %s\n", argv[i]);
|
||||
std::fprintf(stderr, "Argument inconnu : %s\n", arg.data());
|
||||
print_usage();
|
||||
return exit_bad_arguments;
|
||||
}
|
||||
@@ -176,7 +172,12 @@ int main(int argc, char** argv)
|
||||
};
|
||||
|
||||
// Remplir les options du runtime AVANT de construire le CoreRuntime.
|
||||
runtime_options.core_executable = current_executable();
|
||||
if (runtime_options.sequa_root.empty()) {
|
||||
const auto exe = sequa::platform::current_executable();
|
||||
if (!exe.empty()) {
|
||||
runtime_options.sequa_root = sequa::platform::find_root(exe.parent_path()).value_or(std::filesystem::path{});
|
||||
}
|
||||
}
|
||||
const std::string host = options.host == "0.0.0.0" ? std::string("127.0.0.1") : options.host;
|
||||
std::string url = "ws://" + host + ":" + std::to_string(options.port) + "/";
|
||||
if (!options.token.empty()) {
|
||||
|
||||
@@ -27,8 +27,8 @@ struct CoreRuntimeOptions {
|
||||
bool real_time = true; // faux : session sans fil (tests), voir LiveSession
|
||||
std::chrono::milliseconds shutdown_delay{200}; // laisse partir la réponse à core/shutdown
|
||||
|
||||
// --- Options pour le rendu piloté par Studio (protocole 0.3, §9 ; ADR 0009) ---
|
||||
std::filesystem::path core_executable; // pour find_render_executable / find_adapter_library
|
||||
// --- Options pour le rendu piloté par Studio (protocole 0.3, §9 ; ADR 0010) ---
|
||||
std::filesystem::path sequa_root; // racine de l'arborescence (sequa-root.json) : sequa-render et adaptateurs (ADR 0010)
|
||||
std::string render_core_url; // URL WebSocket du Core avec jeton
|
||||
std::filesystem::path render_log; // journal du rendu (vide = sortie héritée)
|
||||
std::function<void(std::string_view)> log; // fonction de journalisation
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
|
||||
#include "render_hub.hpp"
|
||||
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace sequa::core::server {
|
||||
namespace {
|
||||
@@ -17,50 +18,42 @@ constexpr auto restart_delay = std::chrono::seconds(1);
|
||||
constexpr auto restart_window = std::chrono::minutes(1);
|
||||
constexpr std::size_t max_restarts_per_window = 3;
|
||||
|
||||
#ifdef _WIN32
|
||||
constexpr const char* executable_suffix = ".exe";
|
||||
constexpr const char* library_suffix = ".dll";
|
||||
constexpr const char* library_prefix = "";
|
||||
#elif defined(__APPLE__)
|
||||
constexpr const char* executable_suffix = "";
|
||||
constexpr const char* library_suffix = ".dylib";
|
||||
constexpr const char* library_prefix = "lib";
|
||||
#else
|
||||
constexpr const char* executable_suffix = "";
|
||||
constexpr const char* library_suffix = ".so";
|
||||
constexpr const char* library_prefix = "lib";
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
std::optional<std::filesystem::path> first_existing(const std::vector<std::filesystem::path>& candidates)
|
||||
std::optional<std::filesystem::path> find_render_executable(const std::filesystem::path& sequa_root)
|
||||
{
|
||||
for (const auto& candidate : candidates) {
|
||||
std::error_code error;
|
||||
if (std::filesystem::is_regular_file(candidate, error)) {
|
||||
return candidate;
|
||||
}
|
||||
if (sequa_root.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
sequa::platform::Layout layout(sequa_root);
|
||||
const auto path = layout.render_executable();
|
||||
std::error_code error;
|
||||
if (std::filesystem::is_regular_file(path, error)) {
|
||||
return path;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<std::filesystem::path> find_render_executable(const std::filesystem::path& core_exe)
|
||||
{
|
||||
const std::filesystem::path dir = core_exe.parent_path();
|
||||
const std::string name = std::string("sequa-render") + executable_suffix;
|
||||
return first_existing({dir / name, dir.parent_path() / "render" / name});
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> find_adapter_library(const std::filesystem::path& core_exe,
|
||||
std::optional<std::filesystem::path> find_adapter_library(const std::filesystem::path& sequa_root,
|
||||
std::string_view adapter)
|
||||
{
|
||||
if (adapter != "godot" && adapter != "debug") {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::filesystem::path dir = core_exe.parent_path();
|
||||
const std::string name =
|
||||
std::string(library_prefix) + "sequa_" + std::string(adapter) + "_adapter" + library_suffix;
|
||||
return first_existing({dir / name, dir.parent_path() / "engines" / std::string(adapter) / "adapter" / name});
|
||||
if (sequa_root.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
sequa::platform::Layout layout(sequa_root);
|
||||
const auto opt = layout.adapter_library(adapter);
|
||||
if (!opt) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto path = *opt;
|
||||
std::error_code error;
|
||||
if (std::filesystem::is_regular_file(path, error)) {
|
||||
return path;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
RenderSupervisor::RenderSupervisor(RenderLaunch launch, std::string core_url, std::function<void(std::string_view)> log)
|
||||
@@ -78,7 +71,7 @@ RenderSupervisor::~RenderSupervisor()
|
||||
bool RenderSupervisor::launch()
|
||||
{
|
||||
std::vector<std::string> args = {"--core", core_url_,
|
||||
"--adapter", launch_.adapter.string(),
|
||||
"--adapter", sequa::platform::path_to_utf8(launch_.adapter),
|
||||
"--adapter-options", launch_.adapter_options,
|
||||
"--width", std::to_string(launch_.width),
|
||||
"--height", std::to_string(launch_.height)};
|
||||
@@ -94,11 +87,11 @@ bool RenderSupervisor::launch()
|
||||
options.kill_with_parent = true; // jamais de rendu orphelin (VRAM)
|
||||
process_ = platform::ChildProcess::launch(launch_.executable, args, options);
|
||||
if (!process_) {
|
||||
log_("Rendu : lancement impossible de " + launch_.executable.string());
|
||||
log_("Rendu : lancement impossible de " + sequa::platform::path_to_utf8(launch_.executable));
|
||||
state_ = State::stopped;
|
||||
return false;
|
||||
}
|
||||
log_("Rendu lancé (pid " + std::to_string(process_->pid()) + ", " + launch_.adapter.filename().string() + ")");
|
||||
log_("Rendu lancé (pid " + std::to_string(process_->pid()) + ", " + sequa::platform::path_to_utf8(launch_.adapter.filename()) + ")");
|
||||
state_ = State::running;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ private:
|
||||
std::chrono::steady_clock::time_point restart_at_;
|
||||
};
|
||||
|
||||
// Bibliothèque d'adaptateur « godot » ou « debug » et sequa-render, cherchés à côté de
|
||||
// l'exécutable du Core puis dans l'arborescence de build (build/<preset>/…).
|
||||
[[nodiscard]] std::optional<std::filesystem::path> find_render_executable(const std::filesystem::path& core_exe);
|
||||
[[nodiscard]] std::optional<std::filesystem::path> find_adapter_library(const std::filesystem::path& core_exe,
|
||||
// Bibliothèque d'adaptateur « godot » ou « debug » et sequa-render, cherchés dans
|
||||
// l'arborescence SEQUA (ADR 0010) à partir de la racine (sequa-root.json).
|
||||
[[nodiscard]] std::optional<std::filesystem::path> find_render_executable(const std::filesystem::path& sequa_root);
|
||||
[[nodiscard]] std::optional<std::filesystem::path> find_adapter_library(const std::filesystem::path& sequa_root,
|
||||
std::string_view adapter);
|
||||
|
||||
} // namespace sequa::core::server
|
||||
@@ -252,7 +252,7 @@ std::optional<SessionImportData> CoreRuntime::session_import(std::span<const std
|
||||
return session_.import_session(recording, resume);
|
||||
}
|
||||
|
||||
// --- Rendu piloté par Studio (protocole 0.3, §9) ---
|
||||
// --- Rendu piloté par Studio (protocole 0.3, §9 ; ADR 0010) ---
|
||||
|
||||
RenderStartResult CoreRuntime::render_start(const RenderStartRequest& request)
|
||||
{
|
||||
@@ -270,13 +270,14 @@ RenderStartResult CoreRuntime::render_start(const RenderStartRequest& request)
|
||||
return RenderStartResult{std::nullopt, "render-unavailable", "Adaptateur inconnu : " + request.adapter};
|
||||
}
|
||||
|
||||
// Trouver l'exécutable et la bibliothèque d'adaptateur.
|
||||
if (options_.core_executable.empty()) {
|
||||
return RenderStartResult{std::nullopt, "render-unavailable", "Chemin de l'exécutable Core non fourni."};
|
||||
// Trouver l'exécutable et la bibliothèque d'adaptateur à partir de la racine SEQUA.
|
||||
if (options_.sequa_root.empty()) {
|
||||
return RenderStartResult{std::nullopt, "render-unavailable",
|
||||
"Racine SEQUA introuvable (sequa-root.json) : lancer depuis bin/<plateforme>/<type>/ ou passer --sequa-root."};
|
||||
}
|
||||
|
||||
const auto executable = find_render_executable(options_.core_executable);
|
||||
const auto adapter_lib = find_adapter_library(options_.core_executable, request.adapter);
|
||||
const auto executable = find_render_executable(options_.sequa_root);
|
||||
const auto adapter_lib = find_adapter_library(options_.sequa_root, request.adapter);
|
||||
|
||||
if (!executable || !adapter_lib) {
|
||||
std::string msg = "Rendu introuvable : ";
|
||||
|
||||
@@ -22,8 +22,8 @@ CoreRuntimeOptions fast(bool linked)
|
||||
options.attach_deadline = 100ms;
|
||||
options.shutdown_delay = 20ms;
|
||||
options.core_version = "test";
|
||||
// Options de rendu pour les tests : core_executable dans un dossier temporaire vide.
|
||||
options.core_executable = std::filesystem::temp_directory_path() / "sequa_test_empty" / "sequa-core";
|
||||
// Options de rendu pour les tests : racine dans un dossier temporaire vide (pas de sequa-render).
|
||||
options.sequa_root = std::filesystem::temp_directory_path() / "sequa_test_empty";
|
||||
options.render_core_url = "ws://127.0.0.1:9100/?token=test";
|
||||
options.render_log = "";
|
||||
options.log = [](std::string_view) {};
|
||||
@@ -129,10 +129,10 @@ TEST_CASE("Runtime : comptage des clients")
|
||||
|
||||
// --- Tests du rendu (protocole 0.3) ---
|
||||
|
||||
TEST_CASE("Runtime : render_start avec core_executable vide -> render-unavailable")
|
||||
TEST_CASE("Runtime : render_start avec sequa_root vide -> render-unavailable")
|
||||
{
|
||||
CoreRuntimeOptions opts = fast(false);
|
||||
opts.core_executable = ""; // invalide
|
||||
opts.sequa_root = ""; // racine inconnue
|
||||
CoreRuntime runtime(opts);
|
||||
|
||||
RenderStartRequest req;
|
||||
|
||||
@@ -81,7 +81,7 @@ Une installation pour toute la machine (`%ProgramFiles%`, `/opt/sequa`) garde la
|
||||
- Une bibliothèque commune (`platform/layout`) connaît la disposition relative : à partir de la racine, `core/`, `render/`, `studio/ui/`, `engines/<nom>/`.
|
||||
- Racine : `--sequa-root <dossier>` si fourni, sinon premier dossier parent de l'exécutable contenant `sequa-root.json`.
|
||||
- Studio host passe `--sequa-root` au Core lancé depuis sa copie fantôme ; le Core en déduit `sequa-render` et l'adaptateur.
|
||||
- L'adaptateur Godot reçoit le chemin de la bibliothèque Godot et du projet par `--adapter-options` (JSON), rempli par le Core depuis la disposition : `SEQUA_GODOT_DEFAULT_LIBRARY` et `SEQUA_GODOT_DEFAULT_PROJECT` disparaissent.
|
||||
- L'adaptateur Godot cherche la bibliothèque Godot et le projet à côté de lui-même (`engines/godot/`), ce qui garde le Core ignorant du moteur (§7.0.1) ; `--adapter-options` (JSON : `godotLibrary`, `project`) peut les imposer. `SEQUA_GODOT_DEFAULT_LIBRARY` et `SEQUA_GODOT_DEFAULT_PROJECT` disparaissent.
|
||||
- Le repli actuel sur `build/<preset>/…` et `studio/ui/dist` est retiré : on lance depuis `bin/`.
|
||||
|
||||
### Build unifié
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Installation de SEQUA
|
||||
|
||||
Ce dossier décrit comment compiler, lancer, installer et désinstaller SEQUA sur chaque plateforme.
|
||||
|
||||
## Arborescence des binaires (ADR 0010)
|
||||
|
||||
En développement comme une fois installé, les exécutables et leurs ressources sont organisés ainsi :
|
||||
|
||||
```text
|
||||
bin/
|
||||
└── <plateforme>/ # windows-x64, linux-x64, macos-arm64, ...
|
||||
└── <type>/ # debug, release, asan
|
||||
├── sequa-root.json # repère de la racine (version disposition, plateforme, type, version SEQUA)
|
||||
├── core/ # sequa-core
|
||||
├── render/ # sequa-render
|
||||
├── studio/ # sequa-studio
|
||||
│ └── ui/ # interface construite (copie de studio/ui/dist)
|
||||
└── engines/
|
||||
├── debug/ # sequa_debug_adapter
|
||||
└── godot/ # sequa_godot_adapter, bibliothèque Godot (LibGodot), project/
|
||||
```
|
||||
|
||||
- `<plateforme>` = système et processeur (ex. `windows-x64`).
|
||||
- `<type>` = preset de construction : `debug` (template_debug Godot), `release` (template_release Godot), `asan` (sans Godot, adaptateur debug seulement).
|
||||
- Le contenu de `bin/<plateforme>/<type>/` est **l'image d'installation** : il se déplace tel quel.
|
||||
- La racine est trouvée automatiquement par `sequa-root.json` ; on peut la forcer avec `--sequa-root <dossier>`.
|
||||
|
||||
## Pages par plateforme
|
||||
|
||||
| Plateforme | Page | État |
|
||||
|---|---|---|
|
||||
| Windows x64 | [windows-x64.md](windows-x64.md) | Construit et testé |
|
||||
| Linux x64 | [linux-x64.md](linux-x64.md) | **Non vérifié** (ADR 0004) |
|
||||
| macOS arm64 | [macos-arm64.md](macos-arm64.md) | **Non vérifié** (ADR 0004) |
|
||||
|
||||
Chaque page contient : Prérequis, Compiler (mode développement), Lancer, Installer, Désinstaller.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Installation sur Linux x64
|
||||
|
||||
> **NON VÉRIFIÉ** : cette page décrit la procédure prévue. Les presets Linux n'ont pas encore tourné (ADR 0004). L'hébergement de Godot est Windows-only pour l'instant (adaptateur de débogage seulement).
|
||||
|
||||
## Prérequis
|
||||
|
||||
- **CMake ≥ 3.25**, **Ninja**, **GCC** ou **Clang**.
|
||||
- **Python 3** dans le `PATH` (pour SCons si Godot devient disponible).
|
||||
- **Node.js 24** dans le `PATH`.
|
||||
- **Sous-modules Git** pour l'adaptateur Godot (quand il sera pris en charge) :
|
||||
```bash
|
||||
git submodule update --init engines/godot/vendor/godot-src engines/godot/vendor/godot-cpp
|
||||
```
|
||||
- GPU Vulkan pour Godot (futur).
|
||||
|
||||
## Compiler (mode développement)
|
||||
|
||||
Pas encore de `build-all.sh`. Étapes manuelles :
|
||||
|
||||
```bash
|
||||
# Interface (une fois)
|
||||
(cd studio/ui && npm ci && npm run build)
|
||||
|
||||
# Preset CMake (linux-gcc-debug par défaut)
|
||||
./scripts/build.sh linux-gcc-debug
|
||||
|
||||
# Installation dans l'arborescence bin/
|
||||
cmake --install build/linux-gcc-debug --prefix bin/linux-x64/debug
|
||||
```
|
||||
|
||||
Variables d'environnement utiles :
|
||||
- `CLEAN=1` : supprime `build/<preset>` avant de configurer.
|
||||
- `NO_TESTS=1` : compile sans lancer les tests.
|
||||
|
||||
Le script `build.sh` détecte la plateforme et choisit le preset par défaut (`linux-gcc-debug` sur Linux, `macos-clang-debug` sur macOS).
|
||||
|
||||
## Lancer
|
||||
|
||||
Depuis l'image de développement dans `bin/` :
|
||||
|
||||
```bash
|
||||
# Studio complet (Core + interface)
|
||||
bin/linux-x64/debug/studio/sequa-studio [--dev] [--watch]
|
||||
|
||||
# Core seul (trouve sa racine via sequa-root.json ; --sequa-root <dossier> pour forcer)
|
||||
bin/linux-x64/debug/core/sequa-core --render debug --render-headless --render-fps 10
|
||||
```
|
||||
|
||||
L'adaptateur Godot n'est pas disponible ; seul l'adaptateur de débogage (`engines/debug/`) fonctionne.
|
||||
|
||||
## Installer (par utilisateur, sans droits administrateur)
|
||||
|
||||
Emplacement prévu :
|
||||
- Programme : `~/.local/opt/sequa/` (copie de `bin/linux-x64/release/`)
|
||||
- Lien : `~/.local/bin/sequa-studio` → `~/.local/opt/sequa/studio/sequa-studio`
|
||||
- Données d'exécution : `$XDG_RUNTIME_DIR/sequa` (fichiers d'exécution, logs, copies fantômes du Core)
|
||||
- État persistant : `~/.local/state/sequa/`
|
||||
|
||||
## Désinstaller
|
||||
|
||||
Supprimer les dossiers créés à l'installation :
|
||||
```bash
|
||||
rm -rf ~/.local/opt/sequa
|
||||
rm -f ~/.local/bin/sequa-studio
|
||||
rm -rf "$XDG_RUNTIME_DIR/sequa"
|
||||
rm -rf ~/.local/state/sequa
|
||||
```
|
||||
@@ -0,0 +1,63 @@
|
||||
# Installation sur macOS arm64
|
||||
|
||||
> **NON VÉRIFIÉ** : cette page décrit la procédure prévue. Les presets macOS n'ont pas encore tourné (ADR 0004). L'hébergement de Godot est Windows-only pour l'instant (adaptateur de débogage seulement). Le paquet `.app` est reporté (ADR 0007 : Godot Metal reporté).
|
||||
|
||||
## Prérequis
|
||||
|
||||
- **CMake ≥ 3.25**, **Ninja**, **Clang** (Xcode Command Line Tools).
|
||||
- **Python 3** dans le `PATH` (pour SCons si Godot devient disponible).
|
||||
- **Node.js 24** dans le `PATH`.
|
||||
- **Sous-modules Git** pour l'adaptateur Godot (quand il sera pris en charge) :
|
||||
```bash
|
||||
git submodule update --init engines/godot/vendor/godot-src engines/godot/vendor/godot-cpp
|
||||
```
|
||||
- GPU Metal pour Godot (futur).
|
||||
|
||||
## Compiler (mode développement)
|
||||
|
||||
Pas encore de `build-all.sh`. Étapes manuelles :
|
||||
|
||||
```bash
|
||||
# Interface (une fois)
|
||||
(cd studio/ui && npm ci && npm run build)
|
||||
|
||||
# Preset CMake (macos-clang-debug par défaut)
|
||||
./scripts/build.sh macos-clang-debug
|
||||
|
||||
# Installation dans l'arborescence bin/
|
||||
cmake --install build/macos-clang-debug --prefix bin/macos-arm64/debug
|
||||
```
|
||||
|
||||
Variables d'environnement utiles :
|
||||
- `CLEAN=1` : supprime `build/<preset>` avant de configurer.
|
||||
- `NO_TESTS=1` : compile sans lancer les tests.
|
||||
|
||||
Le script `build.sh` détecte la plateforme et choisit le preset par défaut (`linux-gcc-debug` sur Linux, `macos-clang-debug` sur macOS).
|
||||
|
||||
## Lancer
|
||||
|
||||
Depuis l'image de développement dans `bin/` :
|
||||
|
||||
```bash
|
||||
# Studio complet (Core + interface)
|
||||
bin/macos-arm64/debug/studio/sequa-studio [--dev] [--watch]
|
||||
|
||||
# Core seul (trouve sa racine via sequa-root.json ; --sequa-root <dossier> pour forcer)
|
||||
bin/macos-arm64/debug/core/sequa-core --render debug --render-headless --render-fps 10
|
||||
```
|
||||
|
||||
L'adaptateur Godot n'est pas disponible ; seul l'adaptateur de débogage (`engines/debug/`) fonctionne.
|
||||
|
||||
## Installer (par utilisateur, sans droits administrateur)
|
||||
|
||||
Emplacement prévu (paquet `.app` reporté) :
|
||||
- Programme : copie de `bin/macos-arm64/release/` (emplacement à définir, `.app` plus tard)
|
||||
- Données : `~/Library/Application Support/SEQUA/` (fichiers d'exécution, logs, copies fantômes du Core, état persistant)
|
||||
|
||||
## Désinstaller
|
||||
|
||||
Supprimer les dossiers créés à l'installation :
|
||||
```bash
|
||||
rm -rf ~/Library/Application\ Support/SEQUA
|
||||
# Programme : supprimer le dossier ou le paquet .app une fois défini
|
||||
```
|
||||
@@ -0,0 +1,75 @@
|
||||
# Installation sur Windows x64
|
||||
|
||||
## Prérequis
|
||||
|
||||
- **Visual Studio 2022** avec la charge de travail « Développement Desktop en C++ » (MSVC, CMake, Ninja fournis).
|
||||
- **Python 3** dans le `PATH` (SCons est installé dans `engines/godot/.venv` par `build-godot.ps1`).
|
||||
- **Node.js 24** dans le `PATH`.
|
||||
- **Sous-modules Git** pour l'adaptateur Godot :
|
||||
```powershell
|
||||
git submodule update --init engines/godot/vendor/godot-src engines/godot/vendor/godot-cpp
|
||||
```
|
||||
- **GPU Vulkan** pour l'exécution de Godot.
|
||||
|
||||
Optionnel (pour l'inférence GPU, prototype `gpu_interop`) :
|
||||
- **CUDA Toolkit** : `.\scripts\install-cuda.ps1` (télécharge ~2,7 Go, installeur interactif, nécessite UAC).
|
||||
- **cuDNN** : `.\scripts\install-cudnn.ps1` (télécharge ~1,3 Go, accepte la licence NVIDIA, définit la variable utilisateur `CUDNN_PATH`, installe dans `%LOCALAPPDATA%\SEQUA\sdk\cudnn\`).
|
||||
|
||||
## Compiler (mode développement)
|
||||
|
||||
Une seule commande construit tout (Godot, C++, interface, installation dans `bin\`) :
|
||||
|
||||
```powershell
|
||||
.\scripts\build-all.ps1 [-Type debug|release|asan|all] [-NoTests] [-Godot] [-SkipGodot] [-SkipUi] [-NoStop] [-Clean]
|
||||
```
|
||||
|
||||
- `-Type` : `debug` (défaut), `release`, `asan`, ou `all` (les trois).
|
||||
- `-NoTests` : compile sans lancer les tests.
|
||||
- `-Godot` : force la reconstruction de Godot (après modification du fork).
|
||||
- `-SkipGodot` : ne construit jamais Godot (échoue à la configuration s'il manque).
|
||||
- `-SkipUi` : ne construit pas l'interface (installe `studio\ui\dist` s'il existe).
|
||||
- `-NoStop` : échoue au lieu d'arrêter les processus SEQUA qui tournent depuis le dossier visé.
|
||||
- `-Clean` : supprime `build\<preset>` et `bin\windows-x64\<type>` avant de construire.
|
||||
|
||||
Le script :
|
||||
1. Construit Godot (LibGodot) via `build-godot.ps1` (`template_debug` pour `debug`, `template_release` pour `release`, rien pour `asan`) — 10 à 20 min la première fois par variante.
|
||||
2. Lance le preset CMake via `build.ps1` (compilation et tests).
|
||||
3. Construit l'interface (`npm ci` si `node_modules` manque, puis `npm run build`).
|
||||
4. **Arrête les processus SEQUA** lancés depuis `bin\windows-x64\<type>\` (Studio host, Core, rendu) — Windows refuse de remplacer un fichier en cours d'utilisation. Le Core, qui tourne depuis une copie fantôme, est reconnu à son option `--sequa-root`.
|
||||
5. Copie les produits vers `bin\windows-x64\<type>\` via `cmake --install` (adaptateurs, bibliothèque et projet Godot, interface, `sequa-root.json`).
|
||||
|
||||
Exemples :
|
||||
```powershell
|
||||
.\scripts\build-all.ps1
|
||||
.\scripts\build-all.ps1 -Type all -NoTests
|
||||
```
|
||||
|
||||
## Lancer
|
||||
|
||||
Depuis l'image de développement dans `bin\` :
|
||||
|
||||
```powershell
|
||||
# Studio complet (Core + interface)
|
||||
.\bin\windows-x64\debug\studio\sequa-studio.exe [--dev] [--watch]
|
||||
|
||||
# Core seul (trouve sa racine via sequa-root.json ; --sequa-root <dossier> pour forcer)
|
||||
.\bin\windows-x64\debug\core\sequa-core.exe --render debug --render-headless --render-fps 10
|
||||
```
|
||||
|
||||
## Installer (par utilisateur, sans droits administrateur)
|
||||
|
||||
Aucun installeur n'existe encore. Copier le contenu de `bin\windows-x64\release\` vers `%LOCALAPPDATA%\Programs\SEQUA\` et lancer `studio\sequa-studio.exe` depuis là.
|
||||
|
||||
Sur une autre machine sans Visual Studio : le **Microsoft Visual C++ Redistributable 2015-2022 x64** est requis. Les types `debug` et `asan` ne tournent que sur une machine de développement.
|
||||
|
||||
## Désinstaller
|
||||
|
||||
1. Fermer Studio.
|
||||
2. Supprimer le programme installé : `%LOCALAPPDATA%\Programs\SEQUA\` (copie installée) ou `bin\windows-x64\<type>\` (développement).
|
||||
3. Données utilisateur (peuvent être supprimées) : `%LOCALAPPDATA%\SEQUA\` contenant `run\` (fichiers d'exécution, logs, copies fantômes du Core), `downloads\`, `sdk\`.
|
||||
4. Variable d'environnement utilisateur `CUDNN_PATH` si cuDNN a été installé par le script :
|
||||
```powershell
|
||||
[Environment]::SetEnvironmentVariable("CUDNN_PATH", $null, "User")
|
||||
```
|
||||
5. Restes de développement seulement : `build\`, `engines\godot\.venv\`, `studio\ui\node_modules\`, binaires Godot dans `engines\godot\vendor\godot-src\bin\`.
|
||||
6. CUDA Toolkit : désinstaller via « Applications installées » de Windows.
|
||||
@@ -87,10 +87,10 @@ Le Core n'envoie aucun message spontané en 0.2.
|
||||
|
||||
| Option | Effet |
|
||||
|---|---|
|
||||
| `--core-exe <chemin>` | Exécutable source du Core (défaut : `sequa-core[.exe]` à côté de `sequa-studio`, sinon dans `build/<preset>/core/`) |
|
||||
| `--core-exe <chemin>` | Exécutable source du Core (défaut : `core/sequa-core[.exe]` depuis la racine `sequa-root.json`) |
|
||||
| `--core-port <n>` | 9100 |
|
||||
| `--ui-port <n>` | 9180 |
|
||||
| `--ui-dir <chemin>` | Dossier de l'interface construite (défaut : `studio/ui/dist` trouvé en remontant depuis l'exécutable) |
|
||||
| `--ui-dir <chemin>` | Dossier de l'interface construite (défaut : `studio/ui` depuis la racine `sequa-root.json`) |
|
||||
| `--dev [url]` | Ne sert pas l'interface ; ouvre le navigateur sur le serveur Vite (défaut `http://localhost:5173`) |
|
||||
| `--detach-core` | Lance le Core puis lève le bail (`core/detach`) : il survivra au host |
|
||||
| `--no-browser` | N'ouvre pas le navigateur |
|
||||
|
||||
@@ -215,12 +215,11 @@ Le faux Core répond à `render/*` avec un rendu **SIMULÉ** : tout le panneau s
|
||||
### Avec le vrai Core et Godot
|
||||
|
||||
```powershell
|
||||
# Une fois : Godot en bibliothèque, puis le preset qui contient Godot, le rendu et les adaptateurs
|
||||
.\scripts\build-godot.ps1
|
||||
.\scripts\build.ps1 -Preset windows-msvc-prototypes -BuildDir build\windows-msvc-prototypes
|
||||
# Une fois : build complet (Godot, C++, interface, installation dans bin\)
|
||||
.\scripts\build-all.ps1
|
||||
|
||||
# Terminal 1 : le Core seul (sans jeton : poste local uniquement)
|
||||
.\build\windows-msvc-prototypes\core\sequa-core.exe
|
||||
# Terminal 1 : le Core seul (trouve sa racine via sequa-root.json)
|
||||
.\bin\windows-x64\debug\core\sequa-core.exe
|
||||
# Terminal 2 : Studio
|
||||
cd studio/ui; npm run dev # http://localhost:5173/?core=ws://127.0.0.1:9100
|
||||
```
|
||||
@@ -252,7 +251,7 @@ Limites connues, utiles pour lire le panneau :
|
||||
|
||||
```powershell
|
||||
.\scripts\build.ps1 -Preset windows-msvc-debug # tout, avec les tests
|
||||
.\scripts\build.ps1 -Preset windows-msvc-debug -TestRegex "Canal de rendu|render"
|
||||
.\scripts\build.ps1 -Preset windows-msvc-debug -BuildDir build\windows-msvc-debug -TestRegex "Canal de rendu|render"
|
||||
```
|
||||
|
||||
## 8. Ta liste de contrôle pour un nouveau panneau
|
||||
|
||||
@@ -21,6 +21,7 @@ if(TARGET nlohmann_json::nlohmann_json)
|
||||
set_target_properties(sequa_debug_adapter_module PROPERTIES OUTPUT_NAME sequa_debug_adapter PREFIX "")
|
||||
target_link_libraries(sequa_debug_adapter_module PRIVATE sequa::debug_adapter nlohmann_json::nlohmann_json)
|
||||
sequa_target_defaults(sequa_debug_adapter_module)
|
||||
sequa_install_adapter(sequa_debug_adapter_module debug)
|
||||
endif()
|
||||
|
||||
if(SEQUA_BUILD_TESTS)
|
||||
|
||||
@@ -15,8 +15,15 @@ sequa_target_defaults(sequa_godot_adapter)
|
||||
|
||||
if(SEQUA_BUILD_GODOT_RUNTIME)
|
||||
set(_godot_src "${PROJECT_SOURCE_DIR}/engines/godot/vendor/godot-src")
|
||||
set(SEQUA_GODOT_LIBRARY "${_godot_src}/bin/godot.windows.template_debug.x86_64.dll"
|
||||
# Variante de Godot (ADR 0010) : template_debug pour Debug, template_release sinon.
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(_godot_target "template_debug")
|
||||
else()
|
||||
set(_godot_target "template_release")
|
||||
endif()
|
||||
set(SEQUA_GODOT_LIBRARY "${_godot_src}/bin/godot.windows.${_godot_target}.x86_64.dll"
|
||||
CACHE FILEPATH "Godot construit en bibliothèque (LibGodot)")
|
||||
get_filename_component(_godot_library_name "${SEQUA_GODOT_LIBRARY}" NAME)
|
||||
if(NOT WIN32)
|
||||
message(FATAL_ERROR "SEQUA_BUILD_GODOT_RUNTIME : Windows seulement pour l'instant")
|
||||
endif()
|
||||
@@ -37,7 +44,7 @@ if(SEQUA_BUILD_GODOT_RUNTIME)
|
||||
set(GODOTCPP_API_VERSION "4.7" CACHE STRING "" FORCE)
|
||||
message(STATUS "godot-cpp : API officielle 4.7 (extraire celle du fork : scripts/build-godot.ps1 -Target editor)")
|
||||
endif()
|
||||
set(GODOTCPP_TARGET "template_debug" CACHE STRING "" FORCE)
|
||||
set(GODOTCPP_TARGET "${_godot_target}" CACHE STRING "" FORCE)
|
||||
# Même bibliothèque d'exécution que le reste de SEQUA (/MD, /MDd en Debug) : godot-cpp
|
||||
# choisirait sinon /MT pour tout le projet.
|
||||
set(GODOTCPP_USE_STATIC_CPP OFF CACHE BOOL "" FORCE)
|
||||
@@ -49,22 +56,32 @@ if(SEQUA_BUILD_GODOT_RUNTIME)
|
||||
set_target_properties(godot-cpp PROPERTIES PDB_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/godot-cpp/bin")
|
||||
|
||||
target_sources(sequa_godot_adapter PRIVATE src/godot_host.cpp src/godot_scene.cpp)
|
||||
target_link_libraries(sequa_godot_adapter PRIVATE godot::cpp)
|
||||
# PROVISOIRE : chemins fixés à la construction, le processus de rendu les fournira (ADR 0008).
|
||||
target_compile_definitions(sequa_godot_adapter PRIVATE
|
||||
SEQUA_GODOT_RUNTIME=1
|
||||
SEQUA_GODOT_DEFAULT_LIBRARY="${SEQUA_GODOT_LIBRARY}"
|
||||
SEQUA_GODOT_DEFAULT_PROJECT="${CMAKE_CURRENT_SOURCE_DIR}/project")
|
||||
target_link_libraries(sequa_godot_adapter PRIVATE godot::cpp sequa::layout)
|
||||
target_compile_definitions(sequa_godot_adapter PRIVATE SEQUA_GODOT_RUNTIME=1)
|
||||
|
||||
# Bibliothèque chargée par sequa-render (ADR 0009) : point d'entrée C versionné.
|
||||
add_library(sequa_godot_adapter_module MODULE src/entry_point.cpp)
|
||||
set_target_properties(sequa_godot_adapter_module PROPERTIES OUTPUT_NAME sequa_godot_adapter PREFIX "")
|
||||
target_link_libraries(sequa_godot_adapter_module PRIVATE sequa::godot_adapter)
|
||||
target_link_libraries(sequa_godot_adapter_module PRIVATE sequa::godot_adapter sequa::layout
|
||||
nlohmann_json::nlohmann_json)
|
||||
# Nom seul : la bibliothèque Godot est cherchée à côté de l'adaptateur (ADR 0010).
|
||||
target_compile_definitions(sequa_godot_adapter_module PRIVATE
|
||||
SEQUA_GODOT_LIBRARY_NAME="${_godot_library_name}")
|
||||
sequa_target_defaults(sequa_godot_adapter_module)
|
||||
|
||||
# Démonstration de l'étape 3 : scène SIMULÉE poussée depuis SEQUA vers Godot.
|
||||
# engines/godot/ : adaptateur, bibliothèque Godot et projet chargé par Godot.
|
||||
sequa_install_adapter(sequa_godot_adapter_module godot)
|
||||
install(FILES "${SEQUA_GODOT_LIBRARY}" DESTINATION engines/godot)
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/project/" DESTINATION engines/godot/project
|
||||
PATTERN ".godot" EXCLUDE PATTERN ".gitignore" EXCLUDE)
|
||||
|
||||
# Démonstration de l'étape 3 : scène SIMULÉE poussée depuis SEQUA vers Godot. Outil de
|
||||
# développement, non installé : il désigne Godot et le projet dans le dépôt.
|
||||
add_executable(sequa_godot_demo demo/main.cpp)
|
||||
target_link_libraries(sequa_godot_demo PRIVATE sequa::godot_adapter)
|
||||
target_compile_definitions(sequa_godot_demo PRIVATE
|
||||
SEQUA_GODOT_DEMO_LIBRARY="${SEQUA_GODOT_LIBRARY}"
|
||||
SEQUA_GODOT_DEMO_PROJECT="${CMAKE_CURRENT_SOURCE_DIR}/project")
|
||||
sequa_target_defaults(sequa_godot_demo)
|
||||
if(SEQUA_BUILD_TESTS)
|
||||
add_test(NAME sequa_godot_demo COMMAND sequa_godot_demo --frames 300)
|
||||
|
||||
@@ -19,6 +19,31 @@ Implémentation de `sequa::three_d::I3DEngineAdapter` (voir [3d/](../../../3d))
|
||||
| `src/godot_scene.*` | Nœuds Godot qui reflètent la scène SEQUA (deltas déjà validés par `SceneState`) |
|
||||
| `include/sequa/godot/conversions.hpp` | Conversions pures (rotation, couleur sRGB, formes), testées sans moteur |
|
||||
|
||||
### Recherche de la bibliothèque et du projet Godot (ADR 0010)
|
||||
|
||||
L'adaptateur est installé dans `engines/godot/` à côté de la bibliothèque Godot (LibGodot) et du dossier `project/` chargé par Godot.
|
||||
|
||||
- **Par défaut** : l'adaptateur cherche la bibliothèque (`SEQUA_GODOT_LIBRARY_NAME`, ex. `godot.windows.template_release.x86_64.dll`) et le projet `project/` dans le même dossier que lui (`engines/godot/`).
|
||||
- **Surcharge via `--adapter-options`** (JSON, chemins en UTF-8) :
|
||||
```json
|
||||
{ "godotLibrary": "C:/chemin/vers/godot.dll", "project": "C:/chemin/vers/projet" }
|
||||
```
|
||||
Les deux clés sont facultatives ; si absentes, les valeurs par défaut (côté de l'adaptateur) sont utilisées.
|
||||
|
||||
### Variantes de construction Godot (template_debug / template_release)
|
||||
|
||||
`scripts/build-godot.ps1` construit Godot en bibliothèque partagée (LibGodot) avec deux cibles :
|
||||
- **template_debug** : utilisée automatiquement en build Debug (`CMAKE_BUILD_TYPE=Debug`). Contient les assertions et symboles de débogage.
|
||||
- **template_release** : utilisée en build Release/RelWithDebInfo/MinSizeRel. Optimisée, sans assertions.
|
||||
|
||||
Le script est invoqué ainsi :
|
||||
```powershell
|
||||
.\scripts\build-godot.ps1 # template_debug (par défaut)
|
||||
.\scripts\build-godot.ps1 -Target template_release
|
||||
```
|
||||
|
||||
CMake sélectionne la variante selon `CMAKE_BUILD_TYPE` (voir `CMakeLists.txt` lignes 19-23). Le nom de fichier de la bibliothèque (`SEQUA_GODOT_LIBRARY_NAME`) est passé en définition de compilation au module `sequa_godot_adapter_module` uniquement.
|
||||
|
||||
### Décisions de l'étape 3 (2026-11-07)
|
||||
|
||||
- **Frontière binaire reportée** (question ouverte de l'ADR 0008) : l'adaptateur reste une bibliothèque liée statiquement au programme qui l'utilise. Le passage en bibliothèque chargée, avec point d'entrée C versionné, se fera avec le processus de rendu (étape 4), en même temps que le choix du transport Core ↔ rendu.
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
@@ -73,7 +74,9 @@ int main(int argc, char** argv)
|
||||
}
|
||||
}
|
||||
|
||||
godot::GodotEngineAdapter adapter;
|
||||
godot::GodotEngineAdapter adapter(godot::GodotPaths{
|
||||
std::filesystem::path(SEQUA_GODOT_DEMO_LIBRARY),
|
||||
std::filesystem::path(SEQUA_GODOT_DEMO_PROJECT)});
|
||||
three_d::EngineConfig config;
|
||||
config.window_title = "SEQUA - démonstration adaptateur Godot (scène SIMULÉE)";
|
||||
if (auto error = adapter.start(config)) {
|
||||
|
||||
@@ -3,10 +3,17 @@
|
||||
|
||||
#include "sequa/3d/adapter.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
namespace sequa::godot {
|
||||
|
||||
// Chemins de la bibliothèque Godot (LibGodot) et du projet Godot chargé par le moteur.
|
||||
struct GodotPaths {
|
||||
std::filesystem::path library; // chemin vers la DLL/so de LibGodot
|
||||
std::filesystem::path project; // dossier du projet Godot (contenant project.godot)
|
||||
};
|
||||
|
||||
// Version de Godot visée (sous-module engines/godot/vendor/godot-src).
|
||||
inline constexpr const char* engine_version = "4.7.2-stable";
|
||||
|
||||
@@ -28,7 +35,10 @@ inline constexpr const char* engine_version = "4.7.2-stable";
|
||||
// PROVISOIRE : render_frame() ne lit encore aucun tampon (étape 5).
|
||||
class GodotEngineAdapter final : public three_d::I3DEngineAdapter {
|
||||
public:
|
||||
// Constructeur par défaut : chemins vides (utilisé par les tests).
|
||||
GodotEngineAdapter();
|
||||
// Constructeur avec chemins explicites de la bibliothèque et du projet Godot.
|
||||
explicit GodotEngineAdapter(GodotPaths paths);
|
||||
~GodotEngineAdapter() override;
|
||||
|
||||
[[nodiscard]] three_d::EngineCapabilities capabilities() const override { return godot_capabilities(); }
|
||||
@@ -41,8 +51,9 @@ public:
|
||||
[[nodiscard]] three_d::FrameOutcome render_frame(const three_d::FrameRequest& request) override;
|
||||
|
||||
private:
|
||||
struct Runtime; // moteur hébergé et nœuds de la scène (vide sans SEQUA_BUILD_GODOT_RUNTIME)
|
||||
struct Runtime; // moteur hébergé et nœuds de la scène (vide sans SEQUA_GODOT_RUNTIME)
|
||||
|
||||
GodotPaths paths_{};
|
||||
three_d::SceneState scene_;
|
||||
std::unique_ptr<Runtime> runtime_;
|
||||
bool running_ = false;
|
||||
|
||||
@@ -1,27 +1,69 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Point d'entrée de la bibliothèque d'adaptateur Godot (ADR 0009).
|
||||
//
|
||||
// Réglages (--adapter-options de sequa-render, tous facultatifs) :
|
||||
// { "godotLibrary": "<chemin vers LibGodot>", "project": "<dossier du projet Godot>" }
|
||||
// Les chemins sont en UTF-8. Par défaut : bibliothèque et projet à côté de l'adaptateur
|
||||
// (engines/godot/ d'après ADR 0010).
|
||||
|
||||
#include "sequa/3d/adapter_library.hpp"
|
||||
#include "sequa/godot/godot_adapter.hpp"
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
SEQUA_ADAPTER_EXPORT sequa::three_d::I3DEngineAdapter* sequa_create_engine_adapter(std::uint32_t interface_version,
|
||||
const char* options_json,
|
||||
char* error, std::size_t error_size)
|
||||
const char* options_json,
|
||||
char* error, std::size_t error_size)
|
||||
{
|
||||
using sequa::three_d::write_adapter_error;
|
||||
if (interface_version != sequa::three_d::adapter_interface_version) {
|
||||
write_adapter_error(error, error_size, "adaptateur Godot : version d'interface différente");
|
||||
return nullptr;
|
||||
}
|
||||
// Aucun réglage propre à Godot pour l'instant : seuls l'absence d'options et « {} » sont acceptés.
|
||||
if (options_json != nullptr && options_json[0] != '\0' && std::strcmp(options_json, "{}") != 0) {
|
||||
write_adapter_error(error, error_size, "adaptateur Godot : aucun réglage accepté");
|
||||
|
||||
// Valeurs par défaut : bibliothèque et projet à côté de l'adaptateur.
|
||||
const std::filesystem::path adapter_dir =
|
||||
sequa::platform::module_path(reinterpret_cast<const void*>(&sequa_create_engine_adapter)).parent_path();
|
||||
std::filesystem::path library_path = adapter_dir / SEQUA_GODOT_LIBRARY_NAME;
|
||||
std::filesystem::path project_path = adapter_dir / "project";
|
||||
|
||||
// Analyse des options JSON si fournies.
|
||||
const std::string text = options_json != nullptr && options_json[0] != '\0' ? options_json : "{}";
|
||||
const nlohmann::json options = nlohmann::json::parse(text, nullptr, false);
|
||||
if (options.is_discarded() || !options.is_object()) {
|
||||
write_adapter_error(error, error_size, "adaptateur Godot : réglages JSON invalides");
|
||||
return nullptr;
|
||||
}
|
||||
return new (std::nothrow) sequa::godot::GodotEngineAdapter();
|
||||
|
||||
// Clés autorisées : "godotLibrary" et "project" (chaînes UTF-8).
|
||||
for (const auto& [key, value] : options.items()) {
|
||||
if (key == "godotLibrary") {
|
||||
if (!value.is_string()) {
|
||||
write_adapter_error(error, error_size, "adaptateur Godot : \"godotLibrary\" doit être une chaîne");
|
||||
return nullptr;
|
||||
}
|
||||
library_path = sequa::platform::path_from_utf8(value.get<std::string>());
|
||||
} else if (key == "project") {
|
||||
if (!value.is_string()) {
|
||||
write_adapter_error(error, error_size, "adaptateur Godot : \"project\" doit être une chaîne");
|
||||
return nullptr;
|
||||
}
|
||||
project_path = sequa::platform::path_from_utf8(value.get<std::string>());
|
||||
} else {
|
||||
write_adapter_error(error, error_size,
|
||||
("adaptateur Godot : clé inconnue \"" + key + "\" dans les réglages").c_str());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
sequa::godot::GodotPaths paths{std::move(library_path), std::move(project_path)};
|
||||
return new (std::nothrow) sequa::godot::GodotEngineAdapter(std::move(paths));
|
||||
}
|
||||
|
||||
SEQUA_ADAPTER_EXPORT void sequa_destroy_engine_adapter(sequa::three_d::I3DEngineAdapter* adapter)
|
||||
|
||||
@@ -44,6 +44,8 @@ bool runtime_available() noexcept
|
||||
|
||||
GodotEngineAdapter::GodotEngineAdapter() = default;
|
||||
|
||||
GodotEngineAdapter::GodotEngineAdapter(GodotPaths paths) : paths_(std::move(paths)) {}
|
||||
|
||||
GodotEngineAdapter::~GodotEngineAdapter()
|
||||
{
|
||||
stop();
|
||||
@@ -55,7 +57,7 @@ std::optional<Error> GodotEngineAdapter::start(const three_d::EngineConfig& conf
|
||||
return Error{Error::Code::already_started, "adaptateur Godot déjà démarré"};
|
||||
}
|
||||
#if SEQUA_GODOT_RUNTIME
|
||||
GodotHost::StartOutcome outcome = GodotHost::start(config);
|
||||
GodotHost::StartOutcome outcome = GodotHost::start(config, paths_);
|
||||
if (auto* error = std::get_if<Error>(&outcome)) {
|
||||
return std::move(*error);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
#include "godot_host.hpp"
|
||||
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#include <godot_cpp/classes/engine.hpp>
|
||||
#include <godot_cpp/classes/godot_instance.hpp>
|
||||
#include <godot_cpp/classes/main_loop.hpp>
|
||||
@@ -50,12 +52,14 @@ GDExtensionBool host_init(GDExtensionInterfaceGetProcAddress get_proc_address, G
|
||||
return init.init();
|
||||
}
|
||||
|
||||
void* load_library(const char* path)
|
||||
void* load_library(const std::filesystem::path& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return LoadLibraryA(path);
|
||||
// Chemin large pour prendre en charge les chemins Unicode.
|
||||
return LoadLibraryExW(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
|
||||
#else
|
||||
return dlopen(path, RTLD_NOW | RTLD_LOCAL);
|
||||
// POSIX : le chemin est déjà en UTF-8 natif.
|
||||
return dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -114,7 +118,7 @@ LibraryKeeper g_library_keeper __attribute__((init_priority(101)));
|
||||
|
||||
} // namespace
|
||||
|
||||
GodotHost::StartOutcome GodotHost::start(const three_d::EngineConfig& config)
|
||||
GodotHost::StartOutcome GodotHost::start(const three_d::EngineConfig& config, const GodotPaths& paths)
|
||||
{
|
||||
if (config.headless) {
|
||||
// --headless de Godot coupe le rendu ; le vrai headless viendra du fork (ADR 0007).
|
||||
@@ -127,12 +131,15 @@ GodotHost::StartOutcome GodotHost::start(const three_d::EngineConfig& config)
|
||||
return Error{Error::Code::engine_failure,
|
||||
"Godot ne redémarre pas dans le même processus (LibGodot) : relancer le processus de rendu"};
|
||||
}
|
||||
if (paths.library.empty() || paths.project.empty()) {
|
||||
return Error{Error::Code::engine_failure,
|
||||
"Godot : chemins de la bibliothèque et du projet non fournis"};
|
||||
}
|
||||
|
||||
// PROVISOIRE : chemins fixés à la construction ; le processus de rendu les fournira (ADR 0008).
|
||||
void* library = load_library(SEQUA_GODOT_DEFAULT_LIBRARY);
|
||||
void* library = load_library(paths.library);
|
||||
if (library == nullptr) {
|
||||
return Error{Error::Code::engine_failure,
|
||||
std::string("Godot : impossible de charger ") + SEQUA_GODOT_DEFAULT_LIBRARY};
|
||||
"Godot : impossible de charger " + sequa::platform::path_to_utf8(paths.library)};
|
||||
}
|
||||
auto* create = reinterpret_cast<CreateInstanceFn>(load_symbol(library, "libgodot_create_godot_instance"));
|
||||
if (create == nullptr || load_symbol(library, "libgodot_destroy_godot_instance") == nullptr) {
|
||||
@@ -140,9 +147,11 @@ GodotHost::StartOutcome GodotHost::start(const three_d::EngineConfig& config)
|
||||
return Error{Error::Code::engine_failure, "Godot : bibliothèque construite sans library_type=shared_library"};
|
||||
}
|
||||
|
||||
// Argument --path encodé en UTF-8 (Godot attend de l'UTF-8).
|
||||
std::string project_utf8 = sequa::platform::path_to_utf8(paths.project);
|
||||
std::vector<std::string> args = {"sequa",
|
||||
"--path",
|
||||
SEQUA_GODOT_DEFAULT_PROJECT,
|
||||
project_utf8,
|
||||
"--rendering-driver",
|
||||
"vulkan",
|
||||
"--resolution",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "sequa/3d/adapter.hpp"
|
||||
#include "sequa/godot/godot_adapter.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
@@ -20,7 +21,7 @@ namespace sequa::godot {
|
||||
class GodotHost {
|
||||
public:
|
||||
using StartOutcome = std::variant<std::unique_ptr<GodotHost>, three_d::Error>;
|
||||
[[nodiscard]] static StartOutcome start(const three_d::EngineConfig& config);
|
||||
[[nodiscard]] static StartOutcome start(const three_d::EngineConfig& config, const GodotPaths& paths);
|
||||
|
||||
GodotHost(const GodotHost&) = delete;
|
||||
GodotHost& operator=(const GodotHost&) = delete;
|
||||
|
||||
@@ -42,3 +42,20 @@ TEST_CASE("GodotEngineAdapter : rendu sans fenetre refuse")
|
||||
error->code == three_d::Error::Code::not_implemented));
|
||||
CHECK_FALSE(adapter.running());
|
||||
}
|
||||
|
||||
TEST_CASE("GodotEngineAdapter : constructeur par defaut avec chemins vides echoue au start si runtime disponible")
|
||||
{
|
||||
if (!godot::runtime_available()) {
|
||||
return; // sans runtime, start() renvoie not_implemented
|
||||
}
|
||||
godot::GodotEngineAdapter adapter; // chemins vides
|
||||
three_d::EngineConfig config;
|
||||
config.width = 800;
|
||||
config.height = 600;
|
||||
config.window_title = "test";
|
||||
const auto error = adapter.start(config);
|
||||
REQUIRE(error.has_value());
|
||||
CHECK(error->code == three_d::Error::Code::engine_failure);
|
||||
CHECK(error->message.find("non fournis") != std::string::npos);
|
||||
CHECK_FALSE(adapter.running());
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# Bibliothèque de disposition de l'arborescence SEQUA (ADR 0010).
|
||||
# Fournit : recherche de la racine (sequa-root.json), chemins relatifs
|
||||
# (core/, render/, studio/, engines/<moteur>/), noms d'exécutables et
|
||||
# de modules selon la plateforme. Utilisée par le Core, le Studio host
|
||||
# et les adaptateurs.
|
||||
|
||||
add_library(sequa_layout
|
||||
src/layout.cpp)
|
||||
add_library(sequa::layout ALIAS sequa_layout)
|
||||
|
||||
target_include_directories(sequa_layout
|
||||
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_compile_features(sequa_layout PUBLIC cxx_std_20)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(sequa_layout PRIVATE shell32) # CommandLineToArgvW
|
||||
elseif(NOT APPLE)
|
||||
# dladdr pour module_path.
|
||||
target_link_libraries(sequa_layout PUBLIC ${CMAKE_DL_LIBS})
|
||||
endif()
|
||||
|
||||
sequa_target_defaults(sequa_layout)
|
||||
|
||||
if(SEQUA_BUILD_TESTS)
|
||||
add_executable(sequa_layout_tests
|
||||
tests/main.cpp
|
||||
tests/test_layout.cpp)
|
||||
target_link_libraries(sequa_layout_tests PRIVATE sequa::layout doctest::doctest)
|
||||
sequa_target_defaults(sequa_layout_tests)
|
||||
doctest_discover_tests(sequa_layout_tests)
|
||||
endif()
|
||||
@@ -0,0 +1,44 @@
|
||||
# Plateforme : disposition de l'arborescence
|
||||
|
||||
Brique commune qui connaît la structure de l'installation SEQUA (ADR 0010).
|
||||
Elle permet à chaque composant (Studio host, Core, rendu, adaptateurs) de
|
||||
retrouver les autres binaires et ressources à partir d'un unique repère :
|
||||
le fichier `sequa-root.json` à la racine.
|
||||
|
||||
## Arborescence attendue
|
||||
|
||||
```text
|
||||
<root>/sequa-root.json
|
||||
<root>/core/sequa-core(.exe)
|
||||
<root>/render/sequa-render(.exe)
|
||||
<root>/studio/sequa-studio(.exe)
|
||||
<root>/studio/ui/ (interface web, index.html)
|
||||
<root>/engines/<engine>/sequa_<engine>_adapter(.dll|.so)
|
||||
```
|
||||
|
||||
## API (`sequa::platform`)
|
||||
|
||||
| Pièce | Rôle |
|
||||
|---|---|
|
||||
| `root_marker` | Nom du fichier repère (`"sequa-root.json"`). |
|
||||
| `current_executable()` | Chemin absolu de l'exécutable en cours (Windows / Linux / macOS). |
|
||||
| `module_path(address)` | Chemin du module (exe ou bibliothèque partagée) contenant `address` (dladdr / GetModuleHandleEx). |
|
||||
| `find_root(start)` | Remonte depuis `start` jusqu'à la racine du FS ; premier dossier contenant `sequa-root.json`. |
|
||||
| `executable_name(base)` | Ajoute `.exe` sous Windows, sinon inchangé. |
|
||||
| `module_name(base)` | Ajoute `.dll` sous Windows, `.so` ailleurs (macOS compris, suffixe CMake MODULE). |
|
||||
| `is_valid_engine_name(engine)` | Non vide, seulement `[a-z0-9_]`. |
|
||||
| `utf8_arguments(argc, argv)` | Arguments en UTF-8 sans le nom du programme (Windows : relus par `CommandLineToArgvW`, argv étant dans la page de code locale). |
|
||||
| `path_from_utf8(text)`, `path_to_utf8(path)` | Conversions chemin ↔ UTF-8 ; à utiliser pour tout chemin passé en argument à un processus enfant ou reçu de lui. |
|
||||
| `Layout(root)` | Façade orientée objet : `core_executable()`, `render_executable()`, `studio_executable()`, `ui_dir()`, `engine_dir(engine)`, `adapter_library(engine)` (nullopt si nom invalide). |
|
||||
|
||||
## Utilisation
|
||||
|
||||
- **Studio host** : trouve la racine via `find_root(current_executable())`, construit un `Layout`, passe `--sequa-root <root>` au Core lancé (copie fantôme, ADR 0006).
|
||||
- **Core** : reçoit `--sequa-root`, en déduit `sequa-render` et l'adaptateur moteur via `Layout`.
|
||||
- **Adaptateurs** : `module_path` sur un symbole interne → dossier de l'adaptateur → `engines/<moteur>/` pour la bibliothèque Godot et le projet (ADR 0010 §7.0.1).
|
||||
|
||||
Aucune dépendance externe hors STL. Tests avec doctest (MIT).
|
||||
|
||||
## Référence
|
||||
|
||||
[ADR 0010 : Arborescence des binaires (développement et installation), build unifié](../../docs/adr/0010-arborescence-des-binaires-et-build-unifie.md)
|
||||
@@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace sequa::platform {
|
||||
|
||||
// Fichier repère à la racine de l'arborescence SEQUA (ADR 0010).
|
||||
inline constexpr const char* root_marker = "sequa-root.json";
|
||||
|
||||
// Chemin absolu de l'exécutable courant.
|
||||
// Windows : GetModuleFileNameW avec tampon grandissant.
|
||||
// Linux : readlink("/proc/self/exe").
|
||||
// macOS : _NSGetExecutablePath.
|
||||
// Chemin vide en cas d'échec.
|
||||
[[nodiscard]] std::filesystem::path current_executable();
|
||||
|
||||
// Chemin du module (exe ou bibliothèque partagée) qui contient `address`.
|
||||
// Windows : GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT) + GetModuleFileNameW.
|
||||
// POSIX : dladdr.
|
||||
// Chemin vide en cas d'échec.
|
||||
[[nodiscard]] std::filesystem::path module_path(const void* address);
|
||||
|
||||
// Premier dossier qui contient root_marker (fichier régulier), en partant de
|
||||
// `start` (inclus) et en remontant jusqu'à la racine du système de fichiers.
|
||||
[[nodiscard]] std::optional<std::filesystem::path> find_root(const std::filesystem::path& start);
|
||||
|
||||
// "sequa-core" -> "sequa-core.exe" sous Windows, inchangé ailleurs.
|
||||
[[nodiscard]] std::string executable_name(std::string_view base);
|
||||
|
||||
// Bibliothèque chargée (CMake MODULE avec PREFIX "") :
|
||||
// "x" -> "x.dll" sous Windows, "x.so" ailleurs (macOS compris : suffixe des modules CMake).
|
||||
[[nodiscard]] std::string module_name(std::string_view base);
|
||||
|
||||
// Vrai si `engine` est non vide et ne contient que [a-z0-9_].
|
||||
[[nodiscard]] bool is_valid_engine_name(std::string_view engine) noexcept;
|
||||
|
||||
// Arguments de la ligne de commande en UTF-8, sans le nom du programme. Sous Windows,
|
||||
// argv est dans la page de code locale : ils sont relus par CommandLineToArgvW. Ailleurs,
|
||||
// argv est repris tel quel (UTF-8 par convention).
|
||||
[[nodiscard]] std::vector<std::string> utf8_arguments(int argc, char** argv);
|
||||
|
||||
// Conversions entre chemin et UTF-8 (les arguments des processus enfants sont en UTF-8,
|
||||
// voir sequa::platform::ChildProcess). std::filesystem::path(std::string) supposerait la
|
||||
// page de code locale sous Windows.
|
||||
[[nodiscard]] std::filesystem::path path_from_utf8(std::string_view text);
|
||||
[[nodiscard]] std::string path_to_utf8(const std::filesystem::path& path);
|
||||
|
||||
class Layout {
|
||||
public:
|
||||
explicit Layout(std::filesystem::path root);
|
||||
|
||||
[[nodiscard]] const std::filesystem::path& root() const noexcept;
|
||||
|
||||
[[nodiscard]] std::filesystem::path core_executable() const; // root/core/sequa-core[.exe]
|
||||
[[nodiscard]] std::filesystem::path render_executable() const; // root/render/sequa-render[.exe]
|
||||
[[nodiscard]] std::filesystem::path studio_executable() const; // root/studio/sequa-studio[.exe]
|
||||
[[nodiscard]] std::filesystem::path ui_dir() const; // root/studio/ui
|
||||
|
||||
[[nodiscard]] std::filesystem::path engine_dir(std::string_view engine) const; // root/engines/<engine>
|
||||
|
||||
// root/engines/<engine>/sequa_<engine>_adapter + suffixe module ;
|
||||
// nullopt si nom invalide.
|
||||
[[nodiscard]] std::optional<std::filesystem::path> adapter_library(std::string_view engine) const;
|
||||
|
||||
private:
|
||||
std::filesystem::path root_;
|
||||
};
|
||||
|
||||
} // namespace sequa::platform
|
||||
@@ -0,0 +1,238 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
#if defined(_WIN32)
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# ifndef NOMINMAX
|
||||
# define NOMINMAX
|
||||
# endif
|
||||
# include <windows.h>
|
||||
# include <shellapi.h>
|
||||
#else
|
||||
# include <dlfcn.h>
|
||||
# include <unistd.h>
|
||||
# if defined(__APPLE__)
|
||||
# include <mach-o/dyld.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace sequa::platform {
|
||||
|
||||
namespace {
|
||||
|
||||
// Vérifie si un chemin est un fichier régulier (sans lever d'exception).
|
||||
[[nodiscard]] bool is_regular_file_noexcept(const std::filesystem::path& p) noexcept {
|
||||
std::error_code ec;
|
||||
const auto status = std::filesystem::status(p, ec);
|
||||
return !ec && std::filesystem::is_regular_file(status);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::filesystem::path current_executable() {
|
||||
#if defined(_WIN32)
|
||||
std::wstring buffer;
|
||||
buffer.resize(256);
|
||||
while (true) {
|
||||
const DWORD size = static_cast<DWORD>(buffer.size());
|
||||
const DWORD copied = ::GetModuleFileNameW(nullptr, buffer.data(), size);
|
||||
if (copied == 0) {
|
||||
return {};
|
||||
}
|
||||
if (copied < size) {
|
||||
buffer.resize(copied);
|
||||
break;
|
||||
}
|
||||
buffer.resize(buffer.size() * 2);
|
||||
}
|
||||
return std::filesystem::path(buffer).make_preferred();
|
||||
#elif defined(__APPLE__)
|
||||
std::uint32_t size = 1024;
|
||||
std::string buffer(size, '\0');
|
||||
while (true) {
|
||||
const int result = _NSGetExecutablePath(buffer.data(), &size);
|
||||
if (result == 0) {
|
||||
buffer.resize(std::strlen(buffer.c_str()));
|
||||
break;
|
||||
}
|
||||
buffer.resize(size);
|
||||
}
|
||||
// Résoudre les liens symboliques éventuels
|
||||
std::error_code ec;
|
||||
const auto resolved = std::filesystem::canonical(buffer, ec);
|
||||
return ec ? std::filesystem::path(buffer).make_preferred() : resolved.make_preferred();
|
||||
#else
|
||||
// Linux : /proc/self/exe
|
||||
std::error_code ec;
|
||||
const auto path = std::filesystem::read_symlink("/proc/self/exe", ec);
|
||||
return ec ? std::filesystem::path{} : path.make_preferred();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path module_path(const void* address) {
|
||||
#if defined(_WIN32)
|
||||
HMODULE module = nullptr;
|
||||
const BOOL ok = ::GetModuleHandleExW(
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
reinterpret_cast<LPCWSTR>(address),
|
||||
&module);
|
||||
if (!ok || module == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring buffer;
|
||||
buffer.resize(256);
|
||||
while (true) {
|
||||
const DWORD size = static_cast<DWORD>(buffer.size());
|
||||
const DWORD copied = ::GetModuleFileNameW(module, buffer.data(), size);
|
||||
if (copied == 0) {
|
||||
return {};
|
||||
}
|
||||
if (copied < size) {
|
||||
buffer.resize(copied);
|
||||
break;
|
||||
}
|
||||
buffer.resize(buffer.size() * 2);
|
||||
}
|
||||
return std::filesystem::path(buffer).make_preferred();
|
||||
#else
|
||||
Dl_info info{};
|
||||
if (dladdr(address, &info) == 0 || info.dli_fname == nullptr) {
|
||||
return {};
|
||||
}
|
||||
std::error_code ec;
|
||||
const auto path = std::filesystem::canonical(info.dli_fname, ec);
|
||||
return ec ? std::filesystem::path(info.dli_fname).make_preferred() : path.make_preferred();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> find_root(const std::filesystem::path& start) {
|
||||
std::error_code ec;
|
||||
auto current = std::filesystem::absolute(start, ec);
|
||||
if (ec) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const auto marker_path = current / root_marker;
|
||||
if (is_regular_file_noexcept(marker_path)) {
|
||||
return current;
|
||||
}
|
||||
|
||||
const auto parent = current.parent_path();
|
||||
if (parent == current) {
|
||||
// Atteint la racine du système de fichiers
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string executable_name(std::string_view base) {
|
||||
#if defined(_WIN32)
|
||||
return std::string(base) + ".exe";
|
||||
#else
|
||||
return std::string(base);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string module_name(std::string_view base) {
|
||||
#if defined(_WIN32)
|
||||
return std::string(base) + ".dll";
|
||||
#else
|
||||
return std::string(base) + ".so";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_valid_engine_name(std::string_view engine) noexcept {
|
||||
if (engine.empty()) {
|
||||
return false;
|
||||
}
|
||||
return std::ranges::all_of(engine, [](char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_';
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<std::string> utf8_arguments(int argc, char** argv)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
std::vector<std::string> result;
|
||||
int count = 0;
|
||||
LPWSTR* wide = ::CommandLineToArgvW(::GetCommandLineW(), &count);
|
||||
if (wide == nullptr) {
|
||||
return result;
|
||||
}
|
||||
for (int i = 1; i < count; ++i) {
|
||||
result.push_back(path_to_utf8(std::filesystem::path(wide[i])));
|
||||
}
|
||||
::LocalFree(static_cast<HLOCAL>(wide));
|
||||
return result;
|
||||
#else
|
||||
std::vector<std::string> result;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
result.emplace_back(argv[i]);
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path path_from_utf8(std::string_view text)
|
||||
{
|
||||
const std::u8string utf8(text.begin(), text.end());
|
||||
return std::filesystem::path(utf8);
|
||||
}
|
||||
|
||||
std::string path_to_utf8(const std::filesystem::path& path)
|
||||
{
|
||||
const std::u8string utf8 = path.u8string();
|
||||
return std::string(utf8.begin(), utf8.end());
|
||||
}
|
||||
|
||||
Layout::Layout(std::filesystem::path root) : root_(std::move(root)) {}
|
||||
|
||||
const std::filesystem::path& Layout::root() const noexcept {
|
||||
return root_;
|
||||
}
|
||||
|
||||
std::filesystem::path Layout::core_executable() const {
|
||||
return root_ / "core" / executable_name("sequa-core");
|
||||
}
|
||||
|
||||
std::filesystem::path Layout::render_executable() const {
|
||||
return root_ / "render" / executable_name("sequa-render");
|
||||
}
|
||||
|
||||
std::filesystem::path Layout::studio_executable() const {
|
||||
return root_ / "studio" / executable_name("sequa-studio");
|
||||
}
|
||||
|
||||
std::filesystem::path Layout::ui_dir() const {
|
||||
return root_ / "studio" / "ui";
|
||||
}
|
||||
|
||||
std::filesystem::path Layout::engine_dir(std::string_view engine) const {
|
||||
return root_ / "engines" / engine;
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> Layout::adapter_library(std::string_view engine) const {
|
||||
if (!is_valid_engine_name(engine)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::string adapter_base = "sequa_" + std::string(engine) + "_adapter";
|
||||
return engine_dir(engine) / module_name(adapter_base);
|
||||
}
|
||||
|
||||
} // namespace sequa::platform
|
||||
@@ -0,0 +1,3 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest/doctest.h>
|
||||
@@ -0,0 +1,228 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
namespace sequa::platform::testing {
|
||||
|
||||
// Dossier temporaire propre à un test, supprimé à la fin.
|
||||
class TempDir {
|
||||
public:
|
||||
TempDir()
|
||||
: path_(std::filesystem::temp_directory_path()
|
||||
/ ("sequa-layout-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + "-"
|
||||
+ std::to_string(++counter_)))
|
||||
{
|
||||
std::filesystem::create_directories(path_);
|
||||
}
|
||||
~TempDir()
|
||||
{
|
||||
std::error_code ignored;
|
||||
std::filesystem::remove_all(path_, ignored);
|
||||
}
|
||||
TempDir(const TempDir&) = delete;
|
||||
TempDir& operator=(const TempDir&) = delete;
|
||||
|
||||
[[nodiscard]] const std::filesystem::path& path() const noexcept { return path_; }
|
||||
|
||||
private:
|
||||
inline static int counter_ = 0;
|
||||
std::filesystem::path path_;
|
||||
};
|
||||
|
||||
// Écrit un fichier repère vide.
|
||||
inline void touch(const std::filesystem::path& file)
|
||||
{
|
||||
std::ofstream out(file);
|
||||
REQUIRE(out.good());
|
||||
}
|
||||
|
||||
// Fonction dont l'adresse sert à module_path.
|
||||
inline void function_in_test_executable() {}
|
||||
|
||||
} // namespace sequa::platform::testing
|
||||
|
||||
TEST_SUITE("sequa::platform::layout") {
|
||||
|
||||
TEST_CASE("find_root finds marker in start directory itself") {
|
||||
using namespace sequa::platform;
|
||||
using namespace sequa::platform::testing;
|
||||
|
||||
TempDir tmp;
|
||||
const auto root = tmp.path();
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(root / "core", ec);
|
||||
REQUIRE(!ec);
|
||||
std::filesystem::create_directories(root / "render", ec);
|
||||
REQUIRE(!ec);
|
||||
|
||||
// Créer le fichier repère
|
||||
const auto marker = root / root_marker;
|
||||
std::filesystem::create_directories(marker.parent_path(), ec);
|
||||
REQUIRE(!ec);
|
||||
touch(marker);
|
||||
|
||||
const auto found = find_root(root);
|
||||
REQUIRE(found.has_value());
|
||||
CHECK(std::filesystem::equivalent(found.value(), root));
|
||||
}
|
||||
|
||||
TEST_CASE("find_root finds marker two levels up from nested directory") {
|
||||
using namespace sequa::platform;
|
||||
using namespace sequa::platform::testing;
|
||||
|
||||
TempDir tmp;
|
||||
const auto root = tmp.path();
|
||||
const auto nested = root / "a" / "b" / "c";
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(nested, ec);
|
||||
REQUIRE(!ec);
|
||||
|
||||
// Créer le fichier repère à la racine
|
||||
const auto marker = root / root_marker;
|
||||
touch(marker);
|
||||
|
||||
const auto found = find_root(nested);
|
||||
REQUIRE(found.has_value());
|
||||
CHECK(std::filesystem::equivalent(found.value(), root));
|
||||
}
|
||||
|
||||
TEST_CASE("find_root returns nullopt when marker absent") {
|
||||
using namespace sequa::platform;
|
||||
using namespace sequa::platform::testing;
|
||||
|
||||
TempDir tmp;
|
||||
const auto root = tmp.path();
|
||||
const auto nested = root / "a" / "b";
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(nested, ec);
|
||||
REQUIRE(!ec);
|
||||
|
||||
const auto found = find_root(nested);
|
||||
CHECK_FALSE(found.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Layout paths match expected structure") {
|
||||
using namespace sequa::platform;
|
||||
using namespace sequa::platform::testing;
|
||||
|
||||
TempDir tmp;
|
||||
const auto root = tmp.path();
|
||||
|
||||
Layout layout(root);
|
||||
|
||||
CHECK(layout.root() == root);
|
||||
CHECK(layout.core_executable() == root / "core" / executable_name("sequa-core"));
|
||||
CHECK(layout.render_executable() == root / "render" / executable_name("sequa-render"));
|
||||
CHECK(layout.studio_executable() == root / "studio" / executable_name("sequa-studio"));
|
||||
CHECK(layout.ui_dir() == root / "studio" / "ui");
|
||||
CHECK(layout.engine_dir("godot") == root / "engines" / "godot");
|
||||
}
|
||||
|
||||
TEST_CASE("adapter_library returns correct filename and rejects invalid names") {
|
||||
using namespace sequa::platform;
|
||||
using namespace sequa::platform::testing;
|
||||
|
||||
TempDir tmp;
|
||||
const auto root = tmp.path();
|
||||
Layout layout(root);
|
||||
|
||||
// Nom valide
|
||||
const auto godot_adapter = layout.adapter_library("godot");
|
||||
REQUIRE(godot_adapter.has_value());
|
||||
CHECK(godot_adapter.value() == root / "engines" / "godot" / module_name("sequa_godot_adapter"));
|
||||
|
||||
// Nom invalide : contient des caractères non autorisés
|
||||
CHECK_FALSE(layout.adapter_library("../x").has_value());
|
||||
CHECK_FALSE(layout.adapter_library("").has_value());
|
||||
CHECK_FALSE(layout.adapter_library("Godot").has_value()); // majuscule
|
||||
CHECK_FALSE(layout.adapter_library("godot-1").has_value()); // tiret
|
||||
CHECK_FALSE(layout.adapter_library("godot ").has_value()); // espace
|
||||
}
|
||||
|
||||
TEST_CASE("executable_name and module_name per platform") {
|
||||
using namespace sequa::platform;
|
||||
|
||||
#if defined(_WIN32)
|
||||
CHECK(executable_name("sequa-core") == "sequa-core.exe");
|
||||
CHECK(module_name("sequa_godot_adapter") == "sequa_godot_adapter.dll");
|
||||
#else
|
||||
CHECK(executable_name("sequa-core") == "sequa-core");
|
||||
CHECK(module_name("sequa_godot_adapter") == "sequa_godot_adapter.so");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("current_executable returns non-empty existing path") {
|
||||
using namespace sequa::platform;
|
||||
|
||||
const auto exe = current_executable();
|
||||
CHECK_FALSE(exe.empty());
|
||||
std::error_code ec;
|
||||
CHECK(std::filesystem::exists(exe, ec));
|
||||
CHECK(!ec);
|
||||
}
|
||||
|
||||
TEST_CASE("module_path of a function in this test equals current_executable") {
|
||||
using namespace sequa::platform;
|
||||
|
||||
const auto exe = current_executable();
|
||||
REQUIRE_FALSE(exe.empty());
|
||||
|
||||
// Adresse d'une fonction locale pour tester module_path
|
||||
const auto mod = module_path(reinterpret_cast<const void*>(&testing::function_in_test_executable));
|
||||
REQUIRE_FALSE(mod.empty());
|
||||
|
||||
std::error_code ec;
|
||||
CHECK(std::filesystem::equivalent(exe, mod, ec));
|
||||
CHECK(!ec);
|
||||
}
|
||||
|
||||
TEST_CASE("is_valid_engine_name accepts valid names and rejects invalid ones") {
|
||||
using namespace sequa::platform;
|
||||
|
||||
CHECK(is_valid_engine_name("godot"));
|
||||
CHECK(is_valid_engine_name("debug"));
|
||||
CHECK(is_valid_engine_name("engine_123"));
|
||||
CHECK(is_valid_engine_name("a"));
|
||||
|
||||
CHECK_FALSE(is_valid_engine_name(""));
|
||||
CHECK_FALSE(is_valid_engine_name("Godot"));
|
||||
CHECK_FALSE(is_valid_engine_name("godot-1"));
|
||||
CHECK_FALSE(is_valid_engine_name("godot "));
|
||||
CHECK_FALSE(is_valid_engine_name("godot.engine"));
|
||||
CHECK_FALSE(is_valid_engine_name("../godot"));
|
||||
CHECK_FALSE(is_valid_engine_name("godot/evil"));
|
||||
}
|
||||
|
||||
TEST_CASE("path_from_utf8 and path_to_utf8 keep non-ASCII characters") {
|
||||
using namespace sequa::platform;
|
||||
|
||||
const std::string utf8 = "C:/Users/Am\xC3\xA9lie/SEQUA"; // « é » en UTF-8
|
||||
const auto path = path_from_utf8(utf8);
|
||||
CHECK(path.filename() == path_from_utf8("SEQUA"));
|
||||
CHECK(path_to_utf8(path) == utf8);
|
||||
CHECK(path.u8string() == u8"C:/Users/Amélie/SEQUA");
|
||||
}
|
||||
|
||||
TEST_CASE("utf8_arguments drops the program name") {
|
||||
using namespace sequa::platform;
|
||||
|
||||
char program[] = "sequa-layout-tests";
|
||||
char* argv[] = {program, nullptr};
|
||||
const auto args = utf8_arguments(1, argv);
|
||||
#if !defined(_WIN32)
|
||||
CHECK(args.empty());
|
||||
#else
|
||||
// Sous Windows, la vraie ligne de commande du test (arguments de doctest éventuels).
|
||||
CHECK(args.size() < 64);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // TEST_SUITE
|
||||
@@ -8,8 +8,9 @@ add_executable(sequa_render
|
||||
src/adapter_library.cpp
|
||||
src/core_link.cpp)
|
||||
set_target_properties(sequa_render PROPERTIES OUTPUT_NAME sequa-render)
|
||||
target_link_libraries(sequa_render PRIVATE sequa::3d sequa::3d_wire ixwebsocket::ixwebsocket)
|
||||
target_link_libraries(sequa_render PRIVATE sequa::3d sequa::3d_wire sequa::layout ixwebsocket::ixwebsocket)
|
||||
if(UNIX)
|
||||
target_link_libraries(sequa_render PRIVATE ${CMAKE_DL_LIBS})
|
||||
endif()
|
||||
sequa_target_defaults(sequa_render)
|
||||
sequa_install_executable(sequa_render render)
|
||||
+3
-3
@@ -13,10 +13,10 @@ Spécification : [docs/specs/protocole-core-rendu-0.1.md](../docs/specs/protocol
|
||||
## Lancer (par le Core)
|
||||
|
||||
```powershell
|
||||
# Godot, fenêtre de jeu (preset avec SEQUA_BUILD_GODOT_RUNTIME)
|
||||
.\build\windows-msvc-prototypes\core\sequa-core.exe --render godot
|
||||
# Godot, fenêtre de jeu (type debug ou release avec Godot hébergé)
|
||||
.\bin\windows-x64\debug\core\sequa-core.exe --render godot
|
||||
# Sans fenêtre : vue de côté en vidéo à 10 images/s (ffmpeg facultatif, sinon images PPM)
|
||||
.\build\windows-msvc-debug\core\sequa-core.exe --render debug --render-headless --render-fps 10 `
|
||||
.\bin\windows-x64\debug\core\sequa-core.exe --render debug --render-headless --render-fps 10 `
|
||||
--render-options '{\"output\": \"build/render-debug.mp4\"}'
|
||||
```
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "adapter_library.hpp"
|
||||
|
||||
#include "sequa/3d/adapter_library.hpp"
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
@@ -53,13 +54,13 @@ AdapterLibrary::LoadOutcome AdapterLibrary::load(const std::filesystem::path& pa
|
||||
{
|
||||
void* library = open_library(path);
|
||||
if (library == nullptr) {
|
||||
return "impossible de charger " + path.string();
|
||||
return "impossible de charger " + sequa::platform::path_to_utf8(path);
|
||||
}
|
||||
auto* create = reinterpret_cast<three_d::CreateAdapterFn>(find_symbol(library, three_d::create_adapter_symbol));
|
||||
auto* destroy = reinterpret_cast<three_d::DestroyAdapterFn>(find_symbol(library, three_d::destroy_adapter_symbol));
|
||||
if (create == nullptr || destroy == nullptr) {
|
||||
close_library(library);
|
||||
return path.string() + " n'est pas une bibliothèque d'adaptateur SEQUA";
|
||||
return sequa::platform::path_to_utf8(path) + " n'est pas une bibliothèque d'adaptateur SEQUA";
|
||||
}
|
||||
|
||||
std::array<char, 512> error{};
|
||||
|
||||
+11
-9
@@ -8,6 +8,7 @@
|
||||
#include "core_link.hpp"
|
||||
|
||||
#include "sequa/3d/wire.hpp"
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
@@ -47,12 +48,12 @@ std::optional<std::uint32_t> parse_uint(const char* text)
|
||||
return static_cast<std::uint32_t>(value);
|
||||
}
|
||||
|
||||
std::optional<Options> parse(int argc, char** argv)
|
||||
std::optional<Options> parse(const std::vector<std::string>& args)
|
||||
{
|
||||
Options o;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
const char* value = i + 1 < argc ? argv[i + 1] : nullptr;
|
||||
for (std::size_t i = 0; i < args.size(); ++i) {
|
||||
const std::string_view arg = args[i];
|
||||
const char* value = i + 1 < args.size() ? args[i + 1].c_str() : nullptr;
|
||||
auto take_uint = [&](std::uint32_t& out) {
|
||||
const auto parsed = value != nullptr ? parse_uint(value) : std::nullopt;
|
||||
if (parsed) {
|
||||
@@ -62,11 +63,11 @@ std::optional<Options> parse(int argc, char** argv)
|
||||
return parsed.has_value();
|
||||
};
|
||||
if (arg == "--core" && value != nullptr) {
|
||||
o.core_url = argv[++i];
|
||||
o.core_url = args[++i];
|
||||
} else if (arg == "--adapter" && value != nullptr) {
|
||||
o.adapter = argv[++i];
|
||||
o.adapter = args[++i];
|
||||
} else if (arg == "--adapter-options" && value != nullptr) {
|
||||
o.adapter_options = argv[++i];
|
||||
o.adapter_options = args[++i];
|
||||
} else if (arg == "--width") {
|
||||
if (!take_uint(o.width)) return std::nullopt;
|
||||
} else if (arg == "--height") {
|
||||
@@ -136,7 +137,7 @@ int run(const Options& options)
|
||||
return exit_startup;
|
||||
}
|
||||
|
||||
render::AdapterLibrary::LoadOutcome loaded = render::AdapterLibrary::load(options.adapter, options.adapter_options);
|
||||
render::AdapterLibrary::LoadOutcome loaded = render::AdapterLibrary::load(sequa::platform::path_from_utf8(options.adapter), options.adapter_options);
|
||||
if (auto* error = std::get_if<std::string>(&loaded)) {
|
||||
report_failure(link, "adapter-load", *error);
|
||||
return exit_startup;
|
||||
@@ -245,7 +246,8 @@ int run(const Options& options)
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const std::optional<Options> options = parse(argc, argv);
|
||||
const std::vector<std::string> args = sequa::platform::utf8_arguments(argc, argv);
|
||||
const std::optional<Options> options = parse(args);
|
||||
if (!options) {
|
||||
std::fprintf(stderr,
|
||||
"usage : sequa-render --core <url> --adapter <bibliothèque> [--adapter-options <json>]\n"
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
# Messages en ASCII : compatible Windows PowerShell 5.1 sans BOM.
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Build unifie (ADR 0010) : Godot, C++, interface, puis bin\<plateforme>\<type>\.
|
||||
|
||||
.DESCRIPTION
|
||||
Pour chaque type demande :
|
||||
1. Godot (LibGodot) via build-godot.ps1, seulement si sa bibliotheque manque
|
||||
(template_debug pour debug, template_release pour release, rien pour asan) ;
|
||||
2. le preset CMake via build.ps1 (compilation et tests) ;
|
||||
3. arret des processus SEQUA lances depuis bin\<plateforme>\<type>\ (Studio, Core,
|
||||
rendu) : Windows refuse de remplacer un fichier en cours d'utilisation ;
|
||||
4. cmake --install vers bin\<plateforme>\<type>\.
|
||||
L'interface (studio\ui) est construite une fois, avant le premier type.
|
||||
|
||||
Lancer ensuite : bin\windows-x64\<type>\studio\sequa-studio.exe
|
||||
|
||||
.PARAMETER Type
|
||||
debug (defaut), release, asan, ou all (les trois).
|
||||
|
||||
.PARAMETER NoTests
|
||||
Compile sans lancer les tests.
|
||||
|
||||
.PARAMETER Godot
|
||||
Reconstruit Godot meme si sa bibliotheque existe (apres une modification du fork).
|
||||
|
||||
.PARAMETER SkipGodot
|
||||
Ne construit jamais Godot (echoue a la configuration s'il manque).
|
||||
|
||||
.PARAMETER SkipUi
|
||||
Ne construit pas l'interface (installe studio\ui\dist s'il existe).
|
||||
|
||||
.PARAMETER NoStop
|
||||
Echoue au lieu d'arreter les processus SEQUA qui tournent depuis le dossier vise.
|
||||
|
||||
.PARAMETER Clean
|
||||
Supprime build\<preset> et bin\<plateforme>\<type> avant de construire.
|
||||
|
||||
.PARAMETER Help
|
||||
Affiche cette aide.
|
||||
|
||||
.EXAMPLE
|
||||
.\scripts\build-all.ps1
|
||||
.EXAMPLE
|
||||
.\scripts\build-all.ps1 -Type all -NoTests
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet("debug", "release", "asan", "all")]
|
||||
[string]$Type = "debug",
|
||||
[switch]$NoTests,
|
||||
[switch]$Godot,
|
||||
[switch]$SkipGodot,
|
||||
[switch]$SkipUi,
|
||||
[switch]$NoStop,
|
||||
[switch]$Clean,
|
||||
[Alias("h", "?")]
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
if ($Help) {
|
||||
Get-Help $PSCommandPath -Detailed
|
||||
exit 0
|
||||
}
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$platform = "windows-x64"
|
||||
$types = if ($Type -eq "all") { @("debug", "release", "asan") } else { @($Type) }
|
||||
|
||||
$presets = @{ debug = "windows-msvc-debug"; release = "windows-msvc-release"; asan = "windows-msvc-asan" }
|
||||
$godotTargets = @{ debug = "template_debug"; release = "template_release"; asan = $null }
|
||||
|
||||
function Invoke-Step([string]$label, [scriptblock]$cmd) {
|
||||
Write-Host "==> $label" -ForegroundColor Cyan
|
||||
& $cmd
|
||||
if ($LASTEXITCODE -ne 0) { throw "Echec : $label (code $LASTEXITCODE)" }
|
||||
}
|
||||
|
||||
function Build-Ui {
|
||||
$ui = Join-Path $root "studio\ui"
|
||||
Push-Location $ui
|
||||
try {
|
||||
if (-not (Test-Path (Join-Path $ui "node_modules"))) {
|
||||
Invoke-Step "studio\ui : npm ci" { npm ci }
|
||||
}
|
||||
Invoke-Step "studio\ui : npm run build" { npm run build }
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function Build-Godot([string]$target) {
|
||||
$library = Join-Path $root "engines\godot\vendor\godot-src\bin\godot.windows.$target.x86_64.dll"
|
||||
if ((Test-Path $library) -and -not $Godot) { return }
|
||||
if ($SkipGodot) { return }
|
||||
Write-Host "==> Godot $target (10 a 20 min la premiere fois)" -ForegroundColor Cyan
|
||||
& (Join-Path $PSScriptRoot "build-godot.ps1") -Target $target
|
||||
}
|
||||
|
||||
# Processus SEQUA lances depuis $dir : executable dans $dir, ou Core (copie fantome)
|
||||
# dont la ligne de commande designe $dir par --sequa-root.
|
||||
function Get-SequaProcesses([string]$dir) {
|
||||
$prefix = [IO.Path]::GetFullPath($dir).TrimEnd('\') + '\'
|
||||
Get-CimInstance Win32_Process -Filter "Name LIKE 'sequa-%'" | Where-Object {
|
||||
($_.ExecutablePath -and $_.ExecutablePath.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) -or
|
||||
($_.CommandLine -and $_.CommandLine.IndexOf($prefix.TrimEnd('\'), [StringComparison]::OrdinalIgnoreCase) -ge 0)
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-SequaProcesses([string]$dir) {
|
||||
$running = @(Get-SequaProcesses $dir)
|
||||
if ($running.Count -eq 0) { return }
|
||||
$names = ($running | ForEach-Object { "$($_.Name) (pid $($_.ProcessId))" }) -join ", "
|
||||
if ($NoStop) { throw "Processus SEQUA en cours depuis $dir : $names (-NoStop)" }
|
||||
Write-Host "==> arret : $names" -ForegroundColor Yellow
|
||||
foreach ($process in $running) {
|
||||
Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
foreach ($process in $running) {
|
||||
Wait-Process -Id $process.ProcessId -Timeout 10 -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$start = Get-Date
|
||||
if (-not $SkipUi) { Build-Ui }
|
||||
|
||||
foreach ($t in $types) {
|
||||
$preset = $presets[$t]
|
||||
$bin = Join-Path $root "bin\$platform\$t"
|
||||
$buildDir = Join-Path $root "build\$preset"
|
||||
|
||||
if ($godotTargets[$t]) { Build-Godot $godotTargets[$t] }
|
||||
|
||||
$buildArgs = @{ Preset = $preset }
|
||||
if ($NoTests) { $buildArgs.NoTests = $true }
|
||||
if ($Clean) { $buildArgs.Clean = $true }
|
||||
& (Join-Path $PSScriptRoot "build.ps1") @buildArgs
|
||||
|
||||
Stop-SequaProcesses $bin
|
||||
if ($Clean -and (Test-Path $bin)) { Remove-Item -Recurse -Force $bin }
|
||||
# L'interface change de noms de fichiers a chaque build : pas de restes.
|
||||
$uiBin = Join-Path $bin "studio\ui"
|
||||
if (Test-Path $uiBin) { Remove-Item -Recurse -Force $uiBin }
|
||||
|
||||
# cmake est dans le PATH depuis build.ps1 (CMake de Visual Studio).
|
||||
Invoke-Step "install $preset -> bin\$platform\$t" { cmake --install $buildDir --prefix $bin }
|
||||
}
|
||||
|
||||
$elapsed = (Get-Date) - $start
|
||||
Write-Host ("==> termine en {0:N1} min" -f $elapsed.TotalMinutes) -ForegroundColor Green
|
||||
foreach ($t in $types) {
|
||||
Write-Host " bin\$platform\$t\studio\sequa-studio.exe"
|
||||
}
|
||||
+9
-7
@@ -1,7 +1,7 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# Verification locale complete avant commit (remplace la CI) :
|
||||
# tous les presets Windows, puis Studio s'il existe.
|
||||
# Studio, puis build unifie de tous les types Windows.
|
||||
# Messages en ASCII : compatible Windows PowerShell 5.1 sans BOM.
|
||||
#
|
||||
# .\scripts\check.ps1
|
||||
@@ -12,8 +12,9 @@
|
||||
Verification locale complete avant commit (remplace la CI).
|
||||
|
||||
.DESCRIPTION
|
||||
Construit et teste les presets windows-msvc-debug, -release et -asan, puis lance
|
||||
npm ci et npm run check dans studio/.
|
||||
Lance npm ci et npm run check dans studio\ui, puis build-all.ps1 -Type all :
|
||||
Godot, presets windows-msvc-debug, -release et -asan (compilation et tests),
|
||||
remplissage de bin\windows-x64\<type>\.
|
||||
|
||||
.PARAMETER SkipStudio
|
||||
Ne verifie pas Studio.
|
||||
@@ -37,10 +38,7 @@ if ($Help) {
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
foreach ($preset in "windows-msvc-debug", "windows-msvc-release", "windows-msvc-asan") {
|
||||
& (Join-Path $PSScriptRoot "build.ps1") -Preset $preset
|
||||
}
|
||||
|
||||
# Interface d'abord : npm run check construit aussi studio\ui\dist, que build-all installe.
|
||||
$studio = Join-Path $root "studio\ui"
|
||||
if (-not $SkipStudio -and (Test-Path (Join-Path $studio "package.json"))) {
|
||||
Push-Location $studio
|
||||
@@ -57,4 +55,8 @@ if (-not $SkipStudio -and (Test-Path (Join-Path $studio "package.json"))) {
|
||||
}
|
||||
}
|
||||
|
||||
# Build unifie (ADR 0010, modification de l'ADR 0004) : debug, release et asan, Godot
|
||||
# compris, puis bin\windows-x64\<type>\.
|
||||
& (Join-Path $PSScriptRoot "build-all.ps1") -Type all -SkipUi
|
||||
|
||||
Write-Host "Verification locale OK" -ForegroundColor Green
|
||||
@@ -12,11 +12,10 @@ Face C++ de SEQUA Studio ([README de Studio](../README.md)) : l'exécutable que
|
||||
## Utilisation
|
||||
|
||||
```powershell
|
||||
.\scripts\build.ps1 # construit Core, host et tests
|
||||
cd studio\ui; npm run build; cd ..\.. # construit l'interface servie
|
||||
.\build\windows-msvc-debug\studio\host\app\sequa-studio.exe # Core + interface + navigateur
|
||||
.\build\windows-msvc-debug\studio\host\app\sequa-studio.exe --dev # avec Vite (scripts\studio.ps1 dans un autre terminal)
|
||||
.\build\windows-msvc-debug\studio\host\app\sequa-studio.exe --watch # recharge le Core à chaque recompilation
|
||||
.\scripts\build-all.ps1 # construit tout : Core, host, interface, arborescence unifiée
|
||||
.\bin\windows-x64\debug\studio\sequa-studio.exe # Core + interface + navigateur
|
||||
.\bin\windows-x64\debug\studio\sequa-studio.exe --dev # avec Vite (scripts\studio.ps1 dans un autre terminal)
|
||||
.\bin\windows-x64\debug\studio\sequa-studio.exe --watch # recharge le Core à chaque recompilation
|
||||
```
|
||||
|
||||
Commandes dans la console : `status`, `reload` (nouvel exécutable du Core, session rejouée et comparée tick par tick), `restart`, `detach` / `attach` (le Core survit ou non à Studio, réversible), `log`, `quit`.
|
||||
|
||||
@@ -6,13 +6,14 @@ add_library(sequa_host_app STATIC
|
||||
src/options.cpp
|
||||
src/studio_host.cpp)
|
||||
target_include_directories(sequa_host_app PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(sequa_host_app PUBLIC sequa::host_supervisor sequa::host_client sequa::host_web)
|
||||
target_link_libraries(sequa_host_app PUBLIC sequa::host_supervisor sequa::host_client sequa::host_web sequa::layout)
|
||||
sequa_target_defaults(sequa_host_app)
|
||||
|
||||
add_executable(sequa_studio_app src/main.cpp)
|
||||
set_target_properties(sequa_studio_app PROPERTIES OUTPUT_NAME sequa-studio)
|
||||
target_link_libraries(sequa_studio_app PRIVATE sequa_host_app)
|
||||
sequa_target_defaults(sequa_studio_app)
|
||||
sequa_install_executable(sequa_studio_app studio)
|
||||
|
||||
if(SEQUA_BUILD_TESTS)
|
||||
add_executable(sequa_host_app_tests
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "sequa/studio/host/ui_server.hpp"
|
||||
#include "sequa/studio/host/urls.hpp"
|
||||
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
@@ -127,7 +129,8 @@ int main(int argc, char** argv)
|
||||
#if defined(_WIN32)
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
#endif
|
||||
std::vector<std::string_view> args(argv + 1, argv + argc);
|
||||
const auto utf8_args = sequa::platform::utf8_arguments(argc, argv);
|
||||
std::vector<std::string_view> args(utf8_args.begin(), utf8_args.end());
|
||||
const ParsedOptions parsed = parse_options(args);
|
||||
if (!parsed.options) {
|
||||
std::fprintf(stderr, "%s\n%s", parsed.error.c_str(), usage().c_str());
|
||||
@@ -145,6 +148,7 @@ int main(int argc, char** argv)
|
||||
std::fprintf(stderr, "Exécutable du Core introuvable : préciser --core-exe.\n");
|
||||
return 2;
|
||||
}
|
||||
const auto sequa_root = find_sequa_root(exe_dir);
|
||||
const auto run_dir = default_run_dir();
|
||||
if (!run_dir) {
|
||||
std::fprintf(stderr, "Dossier d'exécution introuvable (variable d'environnement manquante).\n");
|
||||
@@ -165,6 +169,9 @@ int main(int argc, char** argv)
|
||||
config.allowed_origins.push_back(*origin);
|
||||
}
|
||||
}
|
||||
if (sequa_root) {
|
||||
config.sequa_root = *sequa_root;
|
||||
}
|
||||
|
||||
StudioHost host(config);
|
||||
if (const std::string error = host.start(); !error.empty()) {
|
||||
@@ -190,7 +197,7 @@ int main(int argc, char** argv)
|
||||
} else {
|
||||
const auto ui_dir = options.ui_dir.empty() ? find_ui_dir(exe_dir) : std::optional(options.ui_dir);
|
||||
if (!ui_dir) {
|
||||
say("Interface introuvable (studio/ui/dist) : lancer « npm run build » dans studio/ui, ou utiliser --dev.");
|
||||
say("Interface introuvable (studio/ui de l'arborescence) : construire avec scripts/build-all.ps1, ou utiliser --dev.");
|
||||
} else {
|
||||
UiServerOptions ui_options;
|
||||
ui_options.port = options.ui_port;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
@@ -47,10 +49,10 @@ bool is_file(const std::filesystem::path& path)
|
||||
std::string usage()
|
||||
{
|
||||
return "Usage : sequa-studio [options]\n"
|
||||
" --core-exe <chemin> exécutable du Core (défaut : trouvé à côté ou dans build/<preset>/core)\n"
|
||||
" --core-exe <chemin> exécutable du Core (défaut : core/ de l'arborescence (sequa-root.json))\n"
|
||||
" --core-port <n> port du Core (défaut 9100)\n"
|
||||
" --ui-port <n> port de l'interface servie (défaut 9180)\n"
|
||||
" --ui-dir <chemin> interface construite (défaut : studio/ui/dist)\n"
|
||||
" --ui-dir <chemin> interface construite (défaut : studio/ui de l'arborescence)\n"
|
||||
" --dev [url] n'ouvre que le serveur Vite (défaut http://localhost:5173)\n"
|
||||
" --detach-core le Core survivra à Studio (bail levé)\n"
|
||||
" --no-browser n'ouvre pas le navigateur\n"
|
||||
@@ -85,10 +87,10 @@ ParsedOptions parse_options(std::span<const std::string_view> args)
|
||||
options.dev_url = "http://localhost:5173";
|
||||
}
|
||||
} else if (arg == "--core-exe" && has_value) {
|
||||
options.core_exe = std::filesystem::path(std::u8string(args[i + 1].begin(), args[i + 1].end()));
|
||||
options.core_exe = sequa::platform::path_from_utf8(args[i + 1]);
|
||||
++i;
|
||||
} else if (arg == "--ui-dir" && has_value) {
|
||||
options.ui_dir = std::filesystem::path(std::u8string(args[i + 1].begin(), args[i + 1].end()));
|
||||
options.ui_dir = sequa::platform::path_from_utf8(args[i + 1]);
|
||||
++i;
|
||||
} else if ((arg == "--core-port" || arg == "--ui-port") && has_value) {
|
||||
const auto port = parse_port(args[++i]);
|
||||
@@ -106,29 +108,35 @@ ParsedOptions parse_options(std::span<const std::string_view> args)
|
||||
return {.options=options, .error={}};
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> find_sequa_root(const std::filesystem::path& exe_dir)
|
||||
{
|
||||
return sequa::platform::find_root(exe_dir);
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> find_core_exe(const std::filesystem::path& exe_dir)
|
||||
{
|
||||
if (is_file(exe_dir / core_name)) {
|
||||
return exe_dir / core_name;
|
||||
const auto root = find_sequa_root(exe_dir);
|
||||
if (!root) {
|
||||
return std::nullopt;
|
||||
}
|
||||
for (std::filesystem::path dir = exe_dir; dir.has_parent_path() && dir != dir.parent_path();
|
||||
dir = dir.parent_path()) {
|
||||
if (is_file(dir / "core" / core_name)) {
|
||||
return dir / "core" / core_name;
|
||||
}
|
||||
const auto layout = sequa::platform::Layout(*root);
|
||||
const auto exe = layout.core_executable();
|
||||
if (is_file(exe)) {
|
||||
return exe;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> find_ui_dir(const std::filesystem::path& exe_dir)
|
||||
{
|
||||
for (std::filesystem::path dir = exe_dir; dir.has_parent_path() && dir != dir.parent_path();
|
||||
dir = dir.parent_path()) {
|
||||
// const auto candidate = dir / "studio" / "ui" / "dist";
|
||||
auto candidate = dir / "studio" / "ui" / "dist";
|
||||
if (is_file(candidate / "index.html")) {
|
||||
return candidate;
|
||||
}
|
||||
const auto root = find_sequa_root(exe_dir);
|
||||
if (!root) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto layout = sequa::platform::Layout(*root);
|
||||
const auto ui = layout.ui_dir();
|
||||
if (is_file(ui / "index.html")) {
|
||||
return ui;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -32,13 +32,15 @@ struct ParsedOptions {
|
||||
[[nodiscard]] ParsedOptions parse_options(std::span<const std::string_view> args);
|
||||
[[nodiscard]] std::string usage();
|
||||
|
||||
// Exécutable du Core : à côté de sequa-studio, sinon <ancêtre>/core/sequa-core[.exe]
|
||||
// en remontant depuis le dossier de sequa-studio (arborescence de build CMake).
|
||||
// Exécutable du Core : root/core/sequa-core[.exe] où root contient sequa-root.json (ADR 0010).
|
||||
[[nodiscard]] std::optional<std::filesystem::path> find_core_exe(const std::filesystem::path& exe_dir);
|
||||
|
||||
// Interface construite : <ancêtre>/studio/ui/dist contenant index.html.
|
||||
// Interface construite : root/studio/ui contenant index.html où root contient sequa-root.json (ADR 0010).
|
||||
[[nodiscard]] std::optional<std::filesystem::path> find_ui_dir(const std::filesystem::path& exe_dir);
|
||||
|
||||
// Racine de l'arborescence SEQUA : premier dossier contenant sequa-root.json en remontant depuis exe_dir.
|
||||
[[nodiscard]] std::optional<std::filesystem::path> find_sequa_root(const std::filesystem::path& exe_dir);
|
||||
|
||||
// Origine (schéma://hôte:port) d'une URL http(s), pour --allowed-origin du Core.
|
||||
[[nodiscard]] std::optional<std::string> origin_of(std::string_view url);
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "sequa/studio/host/urls.hpp"
|
||||
#include "sequa/studio/host/wire.hpp"
|
||||
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
@@ -134,6 +136,9 @@ std::string StudioHost::launch_core()
|
||||
|
||||
std::vector<std::string> args = {"--host", config_.host, "--port", std::to_string(config_.core_port),
|
||||
"--token", run_.token, "--linked"};
|
||||
if (!config_.sequa_root.empty()) {
|
||||
args.insert(args.end(), {"--sequa-root", sequa::platform::path_to_utf8(config_.sequa_root)});
|
||||
}
|
||||
for (const std::string& origin : config_.allowed_origins) {
|
||||
args.insert(args.end(), {"--allowed-origin", origin});
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ using sequa::platform::LaunchOptions;
|
||||
struct HostConfig {
|
||||
std::filesystem::path core_exe; // exécutable SOURCE du Core (jamais lancé directement)
|
||||
std::filesystem::path run_dir; // dossier d'exécution (§7.4)
|
||||
std::filesystem::path sequa_root; // racine de l'arborescence (sequa-root.json), passée au Core (--sequa-root), vide : non passée
|
||||
std::string host = "127.0.0.1";
|
||||
std::uint16_t core_port = 9100;
|
||||
std::vector<std::string> allowed_origins; // transmis au Core (--allowed-origin)
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "sequa/platform/layout.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string_view>
|
||||
@@ -74,26 +76,40 @@ TEST_CASE("Options : origine d'une URL")
|
||||
CHECK_FALSE(origin_of("http://").has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Options : recherche du Core et de l'interface dans une arborescence de build")
|
||||
TEST_CASE("Options : recherche du Core et de l'interface dans une arborescence avec sequa-root.json")
|
||||
{
|
||||
const auto root = std::filesystem::temp_directory_path() / "sequa-app-options-test";
|
||||
std::filesystem::remove_all(root);
|
||||
#if defined(_WIN32)
|
||||
const char* core_name = "sequa-core.exe";
|
||||
#else
|
||||
const char* core_name = "sequa-core";
|
||||
#endif
|
||||
const auto exe_dir = root / "build" / "preset" / "studio" / "host" / "app";
|
||||
const auto core_name = sequa::platform::executable_name("sequa-core");
|
||||
const auto exe_dir = root / "studio" / "host" / "app";
|
||||
std::filesystem::create_directories(exe_dir);
|
||||
std::filesystem::create_directories(root / "build" / "preset" / "core");
|
||||
std::filesystem::create_directories(root / "studio" / "ui" / "dist");
|
||||
std::ofstream(root / "build" / "preset" / "core" / core_name) << "x";
|
||||
std::ofstream(root / "studio" / "ui" / "dist" / "index.html") << "<html>";
|
||||
std::filesystem::create_directories(root / "core");
|
||||
std::filesystem::create_directories(root / "studio" / "ui");
|
||||
std::ofstream(root / "sequa-root.json") << "{}";
|
||||
std::ofstream(root / "core" / core_name) << "x";
|
||||
std::ofstream(root / "studio" / "ui" / "index.html") << "<html>";
|
||||
|
||||
CHECK(find_core_exe(exe_dir) == root / "build" / "preset" / "core" / core_name);
|
||||
CHECK(find_ui_dir(exe_dir) == root / "studio" / "ui" / "dist");
|
||||
CHECK(find_sequa_root(exe_dir) == root);
|
||||
CHECK(find_core_exe(exe_dir) == root / "core" / core_name);
|
||||
CHECK(find_ui_dir(exe_dir) == root / "studio" / "ui");
|
||||
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
|
||||
TEST_CASE("Options : sans sequa-root.json retourne nullopt")
|
||||
{
|
||||
const auto root = std::filesystem::temp_directory_path() / "sequa-app-options-test-no-root";
|
||||
std::filesystem::remove_all(root);
|
||||
const auto exe_dir = root / "studio" / "host" / "app";
|
||||
std::filesystem::create_directories(exe_dir);
|
||||
std::filesystem::create_directories(root / "core");
|
||||
std::filesystem::create_directories(root / "studio" / "ui");
|
||||
std::ofstream(root / "core" / sequa::platform::executable_name("sequa-core")) << "x";
|
||||
std::ofstream(root / "studio" / "ui" / "index.html") << "<html>";
|
||||
|
||||
CHECK_FALSE(find_sequa_root(exe_dir).has_value());
|
||||
CHECK_FALSE(find_core_exe(exe_dir).has_value());
|
||||
CHECK_FALSE(find_ui_dir(exe_dir).has_value());
|
||||
|
||||
std::ofstream(exe_dir / core_name) << "x"; // à côté : prioritaire (distribution)
|
||||
CHECK(find_core_exe(exe_dir) == exe_dir / core_name);
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
Reference in new issue
Block a user