feat(prototypes): libgodot_host, Godot hébergé par LibGodot et script de construction
scripts/build-godot.ps1 construit Godot en bibliothèque partagée depuis le fork (SCons dans un venv, Vulkan seul, --path autorisé). Le prototype charge la DLL, démarre Godot par l'interface C de GDExtension et pilote iteration() : démarrage 1,5 s, arrêt propre à condition de décharger la DLL juste après la destruction de l'instance.
This commit is contained in:
1 parent
4ffcd11734
commit
e7ddd690ce
9 files changed
+453
No files matched your search
@@ -5,6 +5,7 @@
|
||||
|
||||
if(WIN32)
|
||||
add_subdirectory(gpu_interop)
|
||||
add_subdirectory(libgodot_host)
|
||||
else()
|
||||
message(STATUS "prototypes : gpu_interop ignore (Windows uniquement pour l'instant)")
|
||||
endif()
|
||||
@@ -0,0 +1,25 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# Prototype jetable : héberger Godot par LibGodot (ADR 0007, ADR 0008).
|
||||
# Exige Godot construit par scripts/build-godot.ps1 (en-tête GDExtension généré + DLL).
|
||||
|
||||
set(_godot_src "${PROJECT_SOURCE_DIR}/engines/godot/vendor/godot-src")
|
||||
set(_godot_header "${_godot_src}/core/extension/gdextension_interface.gen.h")
|
||||
set(_godot_library "${_godot_src}/bin/godot.windows.template_debug.x86_64.dll")
|
||||
|
||||
if(NOT EXISTS "${_godot_header}")
|
||||
message(STATUS "prototypes : libgodot_host ignore (construire Godot : scripts/build-godot.ps1)")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(libgodot_host main.cpp)
|
||||
target_include_directories(libgodot_host SYSTEM PRIVATE "${_godot_src}/core/extension")
|
||||
target_compile_definitions(libgodot_host PRIVATE
|
||||
SEQUA_LIBGODOT_DEFAULT_LIBRARY="${_godot_library}"
|
||||
SEQUA_LIBGODOT_DEFAULT_PROJECT="${CMAKE_CURRENT_SOURCE_DIR}/project")
|
||||
sequa_target_defaults(libgodot_host)
|
||||
|
||||
if(SEQUA_BUILD_TESTS AND EXISTS "${_godot_library}")
|
||||
add_test(NAME libgodot_host COMMAND libgodot_host --frames 300)
|
||||
set_tests_properties(libgodot_host PROPERTIES LABELS "gpu;prototype;godot" TIMEOUT 120)
|
||||
endif()
|
||||
@@ -0,0 +1,59 @@
|
||||
> **PROTOTYPE JETABLE — pas du code produit.**
|
||||
> Ce dossier sert uniquement à valider une hypothèse technique. Il sera supprimé ou réécrit.
|
||||
|
||||
# libgodot_host — héberger Godot par LibGodot
|
||||
|
||||
## Rôle
|
||||
|
||||
Vérifier ce que supposent l'[ADR 0007](../../docs/adr/0007-adaptateur-godot-dans-le-depot.md) et l'[ADR 0008](../../docs/adr/0008-processus-de-rendu-et-bibliotheques-chargees.md) : un programme SEQUA peut charger Godot comme bibliothèque, le démarrer et piloter sa boucle image par image avec `GodotInstance::iteration()`.
|
||||
|
||||
Le programme n'utilise que l'interface C de GDExtension (`gdextension_interface.gen.h`, généré par la construction de Godot), sans godot-cpp :
|
||||
|
||||
1. charge `godot.windows.template_debug.x86_64.dll` (`LoadLibrary`, `dlopen` ailleurs) ;
|
||||
2. appelle `libgodot_create_godot_instance` avec sa fonction d'initialisation GDExtension, par laquelle il reçoit l'accès à l'API de Godot ;
|
||||
3. retrouve `GodotInstance::start` et `iteration` (`classdb_get_method_bind`, empreinte tirée de `extension_api-4-7.json`) et les appelle (`object_method_bind_ptrcall`) ;
|
||||
4. boucle sur `iteration()` en mesurant chaque appel, jusqu'à `--frames N` ou la fermeture de la fenêtre ;
|
||||
5. détruit l'instance, puis décharge la bibliothèque.
|
||||
|
||||
Le projet Godot chargé (`project/`) contient un cube qui tourne, une caméra et une lumière.
|
||||
|
||||
## Construire et lancer
|
||||
|
||||
```powershell
|
||||
.\scripts\build-godot.ps1 # Godot en bibliothèque (~6 min la première fois)
|
||||
.\scripts\build.ps1 -Preset windows-msvc-prototypes -BuildDir build\windows-msvc-prototypes -Target libgodot_host -TestRegex libgodot_host
|
||||
.\build\windows-msvc-prototypes\prototypes\libgodot_host\libgodot_host.exe [--frames N] [--headless]
|
||||
```
|
||||
|
||||
Sans Godot construit, la cible est ignorée (message `STATUS` à la configuration).
|
||||
|
||||
## Résultats (2026-11-07)
|
||||
|
||||
Windows 11, RTX 4090 Laptop, Vulkan 1.4.357, Godot 4.7.2-stable (fork `sequa/4.7.2`), `template_debug`.
|
||||
|
||||
| Mesure | Fenêtre | `--headless` |
|
||||
|---|---|---|
|
||||
| Démarrage (création + `start()`) | 1,5 – 1,8 s | 0,14 s |
|
||||
| Débit | ≈179 images/s | ≈150 images/s |
|
||||
| `iteration()` médiane / p95 | 5,6 / 6,1 ms | 6,9 / 6,9 ms |
|
||||
| Pilote de rendu | Vulkan, Forward+ | factice (aucune image) |
|
||||
| Arrêt | propre (code 0) | propre (code 0) |
|
||||
|
||||
Lecture : ≈5,6 ms correspond à la synchronisation verticale de l'écran (≈180 Hz), pas au coût du rendu de cette scène minuscule. Le mode `--headless` confirme qu'aucun rendu n'a lieu.
|
||||
|
||||
## Constats
|
||||
|
||||
- **`--path` est interdit par défaut** dans les modèles d'export de Godot 4.7 : il faut compiler avec `disable_path_overrides=no` (fait dans `scripts/build-godot.ps1`).
|
||||
- **Déchargement explicite obligatoire.** Laisser Windows décharger la DLL en fin de processus provoque un accès mémoire invalide (`0xC0000005`) : elle passe alors après d'autres DLL dont Godot dépend encore. Détruire l'instance puis appeler `FreeLibrary` tout de suite donne un arrêt propre. Ne pas détruire l'instance « marche » aussi, mais Godot signale alors de nombreuses fuites.
|
||||
- Une seule instance de Godot par processus, et pas de recréation après destruction (commentaire de `platform/windows/libgodot_windows.cpp`) : relancer Godot revient à relancer le processus de rendu.
|
||||
- `ERROR: Could not load global script cache` : le projet n'a jamais été importé par l'éditeur. Sans effet ici.
|
||||
|
||||
## Limites
|
||||
|
||||
- Windows seulement pour l'instant ; le code prévoit `dlopen`, mais rien n'a été essayé sous Linux.
|
||||
- La scène vient d'un fichier `.tscn` : rien n'est encore créé depuis SEQUA (étape suivante, avec godot-cpp).
|
||||
- Le temps d'`iteration()` inclut l'attente de la synchronisation verticale ; mesurer le coût réel demandera de la couper ou d'utiliser les compteurs GPU.
|
||||
|
||||
## Licences
|
||||
|
||||
MPL 2.0 (en-têtes SPDX). Godot : MIT.
|
||||
@@ -0,0 +1,224 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
//
|
||||
// PROTOTYPE JETABLE : héberger Godot par LibGodot (ADR 0007, ADR 0008).
|
||||
// Charge la bibliothèque Godot à l'exécution, crée l'instance, puis pilote la boucle
|
||||
// image par image avec GodotInstance::iteration(). Mesure le temps de chaque itération.
|
||||
//
|
||||
// Aucune dépendance à godot-cpp : uniquement l'interface C de GDExtension
|
||||
// (gdextension_interface.gen.h, généré par la construction de Godot).
|
||||
|
||||
#include <gdextension_interface.gen.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using CreateInstanceFn = GDExtensionObjectPtr (*)(int, char*[], GDExtensionInitializationFunction);
|
||||
using DestroyInstanceFn = void (*)(GDExtensionObjectPtr);
|
||||
|
||||
GDExtensionInterfaceGetProcAddress g_get_proc_address = nullptr;
|
||||
int g_init_calls = 0;
|
||||
|
||||
// Fonction d'initialisation GDExtension exigée par LibGodot : c'est par elle que l'hôte
|
||||
// reçoit l'accès à l'API de Godot.
|
||||
GDExtensionBool host_init(GDExtensionInterfaceGetProcAddress get_proc_address,
|
||||
GDExtensionClassLibraryPtr /*library*/, GDExtensionInitialization* init)
|
||||
{
|
||||
g_get_proc_address = get_proc_address;
|
||||
init->minimum_initialization_level = GDEXTENSION_INITIALIZATION_SCENE;
|
||||
init->userdata = nullptr;
|
||||
init->initialize = [](void*, GDExtensionInitializationLevel) { ++g_init_calls; };
|
||||
init->deinitialize = [](void*, GDExtensionInitializationLevel) {};
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <class Fn>
|
||||
Fn proc(const char* name)
|
||||
{
|
||||
auto* fn = reinterpret_cast<Fn>(reinterpret_cast<void*>(g_get_proc_address(name)));
|
||||
if (fn == nullptr) {
|
||||
std::fprintf(stderr, "fonction GDExtension introuvable : %s\n", name);
|
||||
std::exit(1);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
void* load_library(const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return LoadLibraryA(path.c_str());
|
||||
#else
|
||||
return dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void* load_symbol(void* library, const char* name)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(library), name));
|
||||
#else
|
||||
return dlsym(library, name);
|
||||
#endif
|
||||
}
|
||||
|
||||
struct Options {
|
||||
std::string library = SEQUA_LIBGODOT_DEFAULT_LIBRARY;
|
||||
std::string project = SEQUA_LIBGODOT_DEFAULT_PROJECT;
|
||||
long frames = 0; // 0 : jusqu'à la fermeture de la fenêtre
|
||||
bool headless = false;
|
||||
};
|
||||
|
||||
Options parse(int argc, char** argv)
|
||||
{
|
||||
Options o;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string a = argv[i];
|
||||
const bool has_value = i + 1 < argc;
|
||||
if (a == "--library" && has_value) {
|
||||
o.library = argv[++i];
|
||||
} else if (a == "--project" && has_value) {
|
||||
o.project = argv[++i];
|
||||
} else if (a == "--frames" && has_value) {
|
||||
o.frames = std::strtol(argv[++i], nullptr, 10);
|
||||
} else if (a == "--headless") {
|
||||
o.headless = true;
|
||||
} else {
|
||||
std::printf("usage : libgodot_host [--library <godot.dll>] [--project <dossier>] [--frames N] [--headless]\n");
|
||||
std::exit(a == "--help" ? 0 : 2);
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0); // messages visibles même en cas de plantage
|
||||
const Options options = parse(argc, argv);
|
||||
|
||||
void* library = load_library(options.library);
|
||||
if (library == nullptr) {
|
||||
std::fprintf(stderr, "[1] impossible de charger %s\n", options.library.c_str());
|
||||
return 1;
|
||||
}
|
||||
auto* create = reinterpret_cast<CreateInstanceFn>(load_symbol(library, "libgodot_create_godot_instance"));
|
||||
auto* destroy = reinterpret_cast<DestroyInstanceFn>(load_symbol(library, "libgodot_destroy_godot_instance"));
|
||||
if (create == nullptr || destroy == nullptr) {
|
||||
std::fprintf(stderr, "[1] symboles LibGodot absents : bibliothèque construite sans library_type=shared_library ?\n");
|
||||
return 1;
|
||||
}
|
||||
std::printf("[1] bibliothèque chargée : %s\n", options.library.c_str());
|
||||
|
||||
std::vector<std::string> args = {"sequa-libgodot-host", "--path", options.project, "--rendering-driver", "vulkan"};
|
||||
if (options.headless) {
|
||||
args.emplace_back("--headless");
|
||||
}
|
||||
std::vector<char*> godot_argv;
|
||||
for (std::string& s : args) {
|
||||
godot_argv.push_back(s.data());
|
||||
}
|
||||
godot_argv.push_back(nullptr);
|
||||
|
||||
const auto t_create = std::chrono::steady_clock::now();
|
||||
GDExtensionObjectPtr instance = create(static_cast<int>(args.size()), godot_argv.data(), &host_init);
|
||||
if (instance == nullptr || g_get_proc_address == nullptr) {
|
||||
std::fprintf(stderr, "[2] échec de libgodot_create_godot_instance\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
GDExtensionGodotVersion2 version{};
|
||||
proc<GDExtensionInterfaceGetGodotVersion2>("get_godot_version2")(&version);
|
||||
std::printf("[2] instance créée : %s (%s)\n", version.string, version.hash);
|
||||
|
||||
auto string_name = proc<GDExtensionInterfaceStringNameNewWithLatin1Chars>("string_name_new_with_latin1_chars");
|
||||
auto method_bind = proc<GDExtensionInterfaceClassdbGetMethodBind>("classdb_get_method_bind");
|
||||
auto ptrcall = proc<GDExtensionInterfaceObjectMethodBindPtrcall>("object_method_bind_ptrcall");
|
||||
|
||||
// StringName : type opaque de la taille d'un pointeur (configuration 64 bits).
|
||||
alignas(8) std::uint8_t class_name[8]{};
|
||||
alignas(8) std::uint8_t start_name[8]{};
|
||||
alignas(8) std::uint8_t iteration_name[8]{};
|
||||
string_name(class_name, "GodotInstance", 1);
|
||||
string_name(start_name, "start", 1);
|
||||
string_name(iteration_name, "iteration", 1);
|
||||
|
||||
// Empreintes des méthodes (extension_api-4-7.json de godot-cpp) : bool f() const-less.
|
||||
constexpr GDExtensionInt hash_bool_noargs = 2240911060;
|
||||
GDExtensionMethodBindPtr start = method_bind(class_name, start_name, hash_bool_noargs);
|
||||
GDExtensionMethodBindPtr iteration = method_bind(class_name, iteration_name, hash_bool_noargs);
|
||||
if (start == nullptr || iteration == nullptr) {
|
||||
std::fprintf(stderr, "[3] méthodes GodotInstance introuvables (empreinte différente ?)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
GDExtensionBool ok = 0;
|
||||
ptrcall(start, instance, nullptr, &ok);
|
||||
const double startup_ms =
|
||||
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - t_create).count();
|
||||
if (ok == 0) {
|
||||
std::fprintf(stderr, "[3] GodotInstance::start() a échoué\n");
|
||||
destroy(instance);
|
||||
return 1;
|
||||
}
|
||||
std::printf("[3] démarré en %.0f ms (création + start), %d niveaux d'initialisation reçus\n", startup_ms,
|
||||
g_init_calls);
|
||||
|
||||
std::vector<double> frame_ms;
|
||||
frame_ms.reserve(options.frames > 0 ? static_cast<std::size_t>(options.frames) : 4096);
|
||||
const auto t_loop = std::chrono::steady_clock::now();
|
||||
for (long frame = 0; options.frames == 0 || frame < options.frames; ++frame) {
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
GDExtensionBool quit = 0;
|
||||
ptrcall(iteration, instance, nullptr, &quit);
|
||||
frame_ms.push_back(std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - t0).count());
|
||||
if (quit != 0) {
|
||||
std::printf("[4] Godot demande l'arrêt (fenêtre fermée) après %ld images\n", frame + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const double loop_s = std::chrono::duration<double>(std::chrono::steady_clock::now() - t_loop).count();
|
||||
|
||||
if (!frame_ms.empty()) {
|
||||
std::vector<double> sorted = frame_ms;
|
||||
std::sort(sorted.begin(), sorted.end());
|
||||
double sum = 0;
|
||||
for (const double v : frame_ms) {
|
||||
sum += v;
|
||||
}
|
||||
const auto pct = [&](double p) {
|
||||
return sorted[std::min(sorted.size() - 1, static_cast<std::size_t>(p * static_cast<double>(sorted.size())))];
|
||||
};
|
||||
std::printf("[4] %zu itérations en %.2f s (%.1f images/s)\n", frame_ms.size(), loop_s,
|
||||
static_cast<double>(frame_ms.size()) / loop_s);
|
||||
std::printf(" iteration() : moyenne %.2f ms, médiane %.2f ms, p95 %.2f ms, max %.2f ms\n",
|
||||
sum / static_cast<double>(frame_ms.size()), pct(0.5), pct(0.95), sorted.back());
|
||||
}
|
||||
|
||||
destroy(instance);
|
||||
// Déchargement explicite, juste après la destruction : laissé à la fin du processus,
|
||||
// il intervient après d'autres DLL dont Godot dépend encore et provoque un accès
|
||||
// mémoire invalide (constaté sous Windows, Godot 4.7.2).
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(static_cast<HMODULE>(library));
|
||||
#else
|
||||
dlclose(library);
|
||||
#endif
|
||||
std::printf("[5] instance détruite, bibliothèque déchargée\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# Cache créé par Godot à l'exécution
|
||||
.godot/
|
||||
@@ -0,0 +1,21 @@
|
||||
[gd_scene load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://spin.gd" id="1"]
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_1"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_1"]
|
||||
albedo_color = Color(0.9, 0.4, 0.15, 1)
|
||||
|
||||
[node name="Main" type="Node3D"]
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
|
||||
|
||||
[node name="Sun" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 2, 0)
|
||||
|
||||
[node name="Box" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("BoxMesh_1")
|
||||
surface_material_override/0 = SubResource("Material_1")
|
||||
script = ExtResource("1")
|
||||
@@ -0,0 +1,18 @@
|
||||
; Projet Godot minimal du prototype libgodot_host (PROTOTYPE JETABLE).
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="SEQUA - essai LibGodot"
|
||||
run/main_scene="res://main.tscn"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1280
|
||||
window/size/viewport_height=720
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="forward_plus"
|
||||
rendering_device/driver.windows="vulkan"
|
||||
@@ -0,0 +1,6 @@
|
||||
extends MeshInstance3D
|
||||
|
||||
# Rotation continue : preuve visuelle que les images avancent.
|
||||
func _process(delta: float) -> void:
|
||||
rotate_y(delta)
|
||||
rotate_x(delta * 0.5)
|
||||
@@ -0,0 +1,97 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
# Messages en ASCII : compatible Windows PowerShell 5.1 sans BOM.
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Construit Godot en bibliotheque partagee (LibGodot) depuis le fork engines/godot/vendor/godot-src.
|
||||
|
||||
.DESCRIPTION
|
||||
Cree au besoin un environnement Python isole (engines/godot/.venv) avec SCons, puis lance
|
||||
SCons avec library_type=shared_library (ADR 0007). Pilote graphique Vulkan uniquement :
|
||||
D3D12, AccessKit et ANGLE sont desactives (ADR 0007 : Vulkan sur Windows et Linux).
|
||||
--path reste autorise : c'est l'hote qui indique a Godot le projet a charger.
|
||||
SCons detecte MSVC lui-meme. Duree indicative : 10 a 20 min pour une premiere construction.
|
||||
|
||||
Sortie : engines/godot/vendor/godot-src/bin/godot.windows.<target>.x86_64.dll
|
||||
|
||||
.PARAMETER Target
|
||||
template_debug (defaut) ou template_release.
|
||||
|
||||
.PARAMETER Jobs
|
||||
Nombre de compilations en parallele. Defaut : nombre de processeurs logiques.
|
||||
|
||||
.PARAMETER Log
|
||||
Fichier ou copier la sortie de SCons (en plus de la console).
|
||||
|
||||
.PARAMETER Help
|
||||
Affiche cette aide.
|
||||
|
||||
.EXAMPLE
|
||||
.\scripts\build-godot.ps1
|
||||
.EXAMPLE
|
||||
Start-Process pwsh -ArgumentList '-NoExit','-File','scripts\build-godot.ps1','-Log','build\godot-build.log'
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet("template_debug", "template_release")]
|
||||
[string]$Target = "template_debug",
|
||||
[int]$Jobs = [Environment]::ProcessorCount,
|
||||
[string]$Log,
|
||||
[Alias("h", "?")]
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
if ($Help) {
|
||||
Get-Help $PSCommandPath -Detailed
|
||||
exit 0
|
||||
}
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$godot = Join-Path $root "engines\godot\vendor\godot-src"
|
||||
$venv = Join-Path $root "engines\godot\.venv"
|
||||
$python = Join-Path $venv "Scripts\python.exe"
|
||||
$sconsVersion = "4.9.1"
|
||||
|
||||
if (-not (Test-Path (Join-Path $godot "SConstruct"))) {
|
||||
throw "Sources de Godot absentes : git submodule update --init engines/godot/vendor/godot-src"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $python)) {
|
||||
Write-Host "==> venv $venv" -ForegroundColor Cyan
|
||||
& python -m venv $venv
|
||||
if ($LASTEXITCODE -ne 0) { throw "Echec : creation du venv (Python 3 requis dans le PATH)" }
|
||||
}
|
||||
|
||||
& $python -c "import SCons, sys; sys.exit(0 if SCons.__version__ == '$sconsVersion' else 1)" 2>$null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "==> pip install scons==$sconsVersion" -ForegroundColor Cyan
|
||||
& $python -m pip install --disable-pip-version-check --quiet "scons==$sconsVersion"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Echec : installation de SCons" }
|
||||
}
|
||||
|
||||
$sconsArgs = @(
|
||||
"-m", "SCons", "-C", $godot, "-j", $Jobs,
|
||||
"platform=windows", "arch=x86_64", "target=$Target",
|
||||
"library_type=shared_library",
|
||||
"d3d12=no", "accesskit=no", "angle=no",
|
||||
# L'hote indique le projet a charger (--path), interdit par defaut dans les modeles 4.7.
|
||||
"disable_path_overrides=no"
|
||||
)
|
||||
|
||||
Write-Host "==> scons $($sconsArgs[4..($sconsArgs.Length - 1)] -join ' ')" -ForegroundColor Cyan
|
||||
$start = Get-Date
|
||||
if ($Log) {
|
||||
New-Item -ItemType Directory -Force (Split-Path -Parent $Log) | Out-Null
|
||||
& $python @sconsArgs 2>&1 | Tee-Object -FilePath $Log
|
||||
}
|
||||
else {
|
||||
& $python @sconsArgs
|
||||
}
|
||||
$code = $LASTEXITCODE
|
||||
$elapsed = (Get-Date) - $start
|
||||
if ($code -ne 0) { throw "Echec : scons (code $code) apres $([int]$elapsed.TotalMinutes) min" }
|
||||
|
||||
Write-Host ("==> termine en {0:N1} min" -f $elapsed.TotalMinutes) -ForegroundColor Green
|
||||
Get-ChildItem (Join-Path $godot "bin") -Filter "godot.windows.$Target.x86_64*" | Select-Object Name, Length
|
||||
Reference in new issue
Block a user