From edc5c42dd2cafd3ef253b3489720a4d0ad9a4e7f Mon Sep 17 00:00:00 2001 From: Michael Alexsander Date: Sun, 16 Feb 2025 13:32:48 -0300 Subject: [PATCH] Make possible to generate a build profile from the command line --- editor/editor_node.cpp | 162 ++++++++++-------- editor/editor_node.h | 4 + editor/settings/editor_build_profile.cpp | 6 + editor/settings/editor_build_profile.h | 1 + main/main.cpp | 31 +++- misc/dist/shell/_godot.zsh-completion | 1 + misc/dist/shell/godot.bash-completion | 1 + misc/dist/shell/godot.fish | 1 + .../render_forward_clustered.cpp | 5 +- 9 files changed, 129 insertions(+), 83 deletions(-) diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 11a604730778..cb7533b2a28f 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1181,94 +1181,101 @@ void EditorNode::_fs_changed() { String export_error; Error err = OK; // It's important to wait for the first scan to finish; otherwise, scripts or resources might not be imported. - if (!export_defer.preset.is_empty() && !EditorFileSystem::get_singleton()->is_scanning()) { - String preset_name = export_defer.preset; - // Ensures export_project does not loop infinitely, because notifications may - // come during the export. - export_defer.preset = ""; - Ref export_preset; - for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); ++i) { - export_preset = EditorExport::get_singleton()->get_export_preset(i); - if (export_preset->get_name() == preset_name) { - break; - } - export_preset.unref(); - } - - if (export_preset.is_null()) { - Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - if (da->file_exists("res://export_presets.cfg")) { - err = FAILED; - export_error = vformat( - "Invalid export preset name: %s.\nThe following presets were detected in this project's `export_presets.cfg`:\n\n", - preset_name); - for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); ++i) { - // Write the preset name between double quotes since it needs to be written between quotes on the command line if it contains spaces. - export_error += vformat(" \"%s\"\n", EditorExport::get_singleton()->get_export_preset(i)->get_name()); + if (!EditorFileSystem::get_singleton()->is_scanning()) { + if (!export_defer.preset.is_empty()) { + String preset_name = export_defer.preset; + // Ensures export_project does not loop infinitely, because notifications may + // come during the export. + export_defer.preset = ""; + Ref export_preset; + for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); ++i) { + export_preset = EditorExport::get_singleton()->get_export_preset(i); + if (export_preset->get_name() == preset_name) { + break; + } + export_preset.unref(); + } + + if (export_preset.is_null()) { + Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (da->file_exists("res://export_presets.cfg")) { + err = FAILED; + export_error = vformat( + "Invalid export preset name: %s.\nThe following presets were detected in this project's `export_presets.cfg`:\n\n", + preset_name); + for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); ++i) { + // Write the preset name between double quotes since it needs to be written between quotes on the command line if it contains spaces. + export_error += vformat(" \"%s\"\n", EditorExport::get_singleton()->get_export_preset(i)->get_name()); + } + } else { + err = FAILED; + export_error = "This project doesn't have an `export_presets.cfg` file at its root.\nCreate an export preset from the \"Project > Export\" dialog and try again."; } } else { - err = FAILED; - export_error = "This project doesn't have an `export_presets.cfg` file at its root.\nCreate an export preset from the \"Project > Export\" dialog and try again."; - } - } else { - Ref platform = export_preset->get_platform(); - const String export_path = export_defer.path.is_empty() ? export_preset->get_export_path() : export_defer.path; - if (export_path.is_empty()) { - err = FAILED; - export_error = vformat("Export preset \"%s\" doesn't have a default export path, and none was specified.", preset_name); - } else if (platform.is_null()) { - err = FAILED; - export_error = vformat("Export preset \"%s\" doesn't have a matching platform.", preset_name); - } else { - export_preset->update_value_overrides(); - if (export_defer.pack_only) { // Only export .pck or .zip data pack. - if (export_path.ends_with(".zip")) { - if (export_defer.patch) { - err = platform->export_zip_patch(export_preset, export_defer.debug, export_path, export_defer.patches); + Ref platform = export_preset->get_platform(); + const String export_path = export_defer.path.is_empty() ? export_preset->get_export_path() : export_defer.path; + if (export_path.is_empty()) { + err = FAILED; + export_error = vformat("Export preset \"%s\" doesn't have a default export path, and none was specified.", preset_name); + } else if (platform.is_null()) { + err = FAILED; + export_error = vformat("Export preset \"%s\" doesn't have a matching platform.", preset_name); + } else { + export_preset->update_value_overrides(); + if (export_defer.pack_only) { // Only export .pck or .zip data pack. + if (export_path.ends_with(".zip")) { + if (export_defer.patch) { + err = platform->export_zip_patch(export_preset, export_defer.debug, export_path, export_defer.patches); + } else { + err = platform->export_zip(export_preset, export_defer.debug, export_path); + } + } else if (export_path.ends_with(".pck")) { + if (export_defer.patch) { + err = platform->export_pack_patch(export_preset, export_defer.debug, export_path, export_defer.patches); + } else { + err = platform->export_pack(export_preset, export_defer.debug, export_path); + } } else { - err = platform->export_zip(export_preset, export_defer.debug, export_path); + ERR_PRINT(vformat("Export path \"%s\" doesn't end with a supported extension.", export_path)); + err = FAILED; + } + } else { // Normal project export. + String config_error; + bool missing_templates; + if (export_defer.android_build_template) { + export_template_manager->install_android_template(export_preset); } - } else if (export_path.ends_with(".pck")) { - if (export_defer.patch) { - err = platform->export_pack_patch(export_preset, export_defer.debug, export_path, export_defer.patches); + if (!platform->can_export(export_preset, config_error, missing_templates, export_defer.debug)) { + ERR_PRINT(vformat("Cannot export project with preset \"%s\" due to configuration errors:\n%s", preset_name, config_error)); + err = missing_templates ? ERR_FILE_NOT_FOUND : ERR_UNCONFIGURED; } else { - err = platform->export_pack(export_preset, export_defer.debug, export_path); + platform->clear_messages(); + err = platform->export_project(export_preset, export_defer.debug, export_path); } - } else { - ERR_PRINT(vformat("Export path \"%s\" doesn't end with a supported extension.", export_path)); - err = FAILED; - } - } else { // Normal project export. - String config_error; - bool missing_templates; - if (export_defer.android_build_template) { - export_template_manager->install_android_template(export_preset); } - if (!platform->can_export(export_preset, config_error, missing_templates, export_defer.debug)) { - ERR_PRINT(vformat("Cannot export project with preset \"%s\" due to configuration errors:\n%s", preset_name, config_error)); - err = missing_templates ? ERR_FILE_NOT_FOUND : ERR_UNCONFIGURED; - } else { - platform->clear_messages(); - err = platform->export_project(export_preset, export_defer.debug, export_path); + if (err != OK) { + export_error = vformat("Project export for preset \"%s\" failed.", preset_name); + } else if (platform->get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) { + export_error = vformat("Project export for preset \"%s\" completed with warnings.", preset_name); } } - if (err != OK) { - export_error = vformat("Project export for preset \"%s\" failed.", preset_name); - } else if (platform->get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) { - export_error = vformat("Project export for preset \"%s\" completed with warnings.", preset_name); - } } - } - if (err != OK) { - ERR_PRINT(export_error); - _exit_editor(EXIT_FAILURE); - return; + if (err != OK) { + ERR_PRINT(export_error); + _exit_editor(EXIT_FAILURE); + return; + } + if (!export_error.is_empty()) { + WARN_PRINT(export_error); + } + _exit_editor(EXIT_SUCCESS); } - if (!export_error.is_empty()) { - WARN_PRINT(export_error); + + if (!build_profile_path.is_empty()) { + err = build_profile_manager->generate_build_profile_from_project(build_profile_path); + _exit_editor(err == OK ? EXIT_SUCCESS : EXIT_FAILURE); } - _exit_editor(EXIT_SUCCESS); } } @@ -5632,6 +5639,11 @@ bool EditorNode::is_project_exporting() const { return project_export && project_export->is_exporting(); } +void EditorNode::generate_build_profile(const String &p_path) { + build_profile_path = p_path; + cmdline_mode = true; +} + void EditorNode::show_accept(const String &p_text, const String &p_title) { current_menu_option = -1; if (accept) { diff --git a/editor/editor_node.h b/editor/editor_node.h index f0a6dc270db8..1ab4b0dd5456 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -498,6 +498,8 @@ class EditorNode : public Node { bool unfocused_low_processor_usage_mode_enabled = true; + String build_profile_path; + static EditorBuildCallback build_callbacks[MAX_BUILD_CALLBACKS]; static EditorPluginInitializeCallback plugin_init_callbacks[MAX_INIT_CALLBACKS]; static int build_callback_count; @@ -942,6 +944,8 @@ class EditorNode : public Node { Error export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only, bool p_android_build_template, bool p_patch, const Vector &p_patches); bool is_project_exporting() const; + void generate_build_profile(const String &p_path); + Control *get_gui_base() { return gui_base; } void save_scene_to_path(String p_file, bool p_with_preview = true) { diff --git a/editor/settings/editor_build_profile.cpp b/editor/settings/editor_build_profile.cpp index d8579eaf3753..9750bb1284b2 100644 --- a/editor/settings/editor_build_profile.cpp +++ b/editor/settings/editor_build_profile.cpp @@ -1271,6 +1271,12 @@ void EditorBuildProfileManager::_export_profile(const String &p_path) { } } +Error EditorBuildProfileManager::generate_build_profile_from_project(const String &p_path) { + edited.instantiate(); + _detect_from_project(); + return edited->save_to_file(p_path); +} + Ref EditorBuildProfileManager::get_current_profile() { return edited; } diff --git a/editor/settings/editor_build_profile.h b/editor/settings/editor_build_profile.h index 4d2c3f212712..b4c1ed2729b3 100644 --- a/editor/settings/editor_build_profile.h +++ b/editor/settings/editor_build_profile.h @@ -207,6 +207,7 @@ class EditorBuildProfileManager : public AcceptDialog { void _notification(int p_what); public: + Error generate_build_profile_from_project(const String &p_path); Ref get_current_profile(); static EditorBuildProfileManager *get_singleton() { return singleton; } diff --git a/main/main.cpp b/main/main.cpp index 36d3c44b764b..484ef1c10e29 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -690,6 +690,7 @@ void Main::print_help(const char *p_binary) { print_help_option("--gdscript-docs ", "Rather than dumping the engine API, generate API reference from the inline documentation in the GDScript files found in (used with --doctool).\n", CLI_OPTION_AVAILABILITY_EDITOR); #endif print_help_option("--build-solutions", "Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--generate-build-profile ", "Generate an engine compilation profile by detecting the configuration from the project and save it to a given file. The path should be absolute.\n", CLI_OPTION_AVAILABILITY_EDITOR); print_help_option("--dump-gdextension-interface", "Generate a GDExtension header file \"gdextension_interface.h\" in the current folder. This file is the base file required to implement a GDExtension.\n", CLI_OPTION_AVAILABILITY_EDITOR); print_help_option("--dump-extension-api", "Generate a JSON dump of the Godot API for GDExtension bindings named \"extension_api.json\" in the current folder.\n", CLI_OPTION_AVAILABILITY_EDITOR); print_help_option("--dump-extension-api-with-docs", "Generate JSON dump of the Godot API like the previous option, but including documentation.\n", CLI_OPTION_AVAILABILITY_EDITOR); @@ -1559,14 +1560,14 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph quit_after = 1; } else if (arg == "--export-release" || arg == "--export-debug" || arg == "--export-pack" || arg == "--export-patch") { // Export project - // Actually handling is done in start(). + // Actual handling is done in start(). editor = true; cmdline_tool = true; wait_for_import = true; main_args.push_back(arg); } else if (arg == "--patches") { if (N) { - // Actually handling is done in start(). + // Actual handling is done in start(). main_args.push_back(arg); main_args.push_back(N->get()); @@ -1575,12 +1576,18 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->print("Missing comma-separated list of patches after --patches, aborting.\n"); goto error; } + } else if (arg == "--generate-build-profile") { + // Actual handling is done in start(). + editor = true; + cmdline_tool = true; + wait_for_import = true; + main_args.push_back(arg); #ifndef DISABLE_DEPRECATED } else if (arg == "--export") { // For users used to 3.x syntax. OS::get_singleton()->print("The Godot 3 --export option was changed to more explicit --export-release / --export-debug / --export-pack options.\nSee the --help output for details.\n"); goto error; } else if (arg == "--convert-3to4") { - // Actually handling is done in start(). + // Actual handling is done in start(). cmdline_tool = true; main_args.push_back(arg); @@ -1595,7 +1602,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } } } else if (arg == "--validate-conversion-3to4") { - // Actually handling is done in start(). + // Actual handling is done in start(). cmdline_tool = true; main_args.push_back(arg); @@ -1611,7 +1618,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } #endif // DISABLE_DEPRECATED } else if (arg == "--doctool") { - // Actually handling is done in start(). + // Actual handling is done in start(). cmdline_tool = true; // `--doctool` implies `--headless` to avoid spawning an unnecessary window @@ -3839,6 +3846,7 @@ int Main::start() { bool export_pack_only = false; bool install_android_build_template = false; bool export_patch = false; + String build_profile_path; #ifdef MODULE_GDSCRIPT_ENABLED String gdscript_docs_path; #endif @@ -3926,11 +3934,11 @@ int Main::start() { #endif } else if (E->get() == "--export-release") { ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting."); - editor = true; //needs editor + editor = true; _export_preset = E->next()->get(); } else if (E->get() == "--export-debug") { ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting."); - editor = true; //needs editor + editor = true; _export_preset = E->next()->get(); export_debug = true; } else if (E->get() == "--export-pack") { @@ -3946,6 +3954,10 @@ int Main::start() { export_patch = true; } else if (E->get() == "--patches") { patches = E->next()->get().split(",", false); + } else if (E->get() == "--generate-build-profile") { + ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when generating a build configuration, aborting."); + editor = true; + build_profile_path = E->next()->get(); #endif } else { // The parameter does not match anything known, don't skip the next argument @@ -4380,6 +4392,11 @@ int Main::start() { game_path = ""; // Do not load anything. } + if (!build_profile_path.is_empty()) { + editor_node->generate_build_profile(build_profile_path); + game_path = ""; // Do not load anything. + } + OS::get_singleton()->benchmark_end_measure("Startup", "Editor"); } #endif diff --git a/misc/dist/shell/_godot.zsh-completion b/misc/dist/shell/_godot.zsh-completion index f65cf3787050..0914665526a6 100644 --- a/misc/dist/shell/_godot.zsh-completion +++ b/misc/dist/shell/_godot.zsh-completion @@ -89,6 +89,7 @@ _arguments \ '--doctool[dump the engine API reference to the given path in XML format, merging if existing files are found]:path to base Godot build directory (optional):_dirs' \ '--no-docbase[disallow dumping the base types (used with --doctool)]' \ '--build-solutions[build the scripting solutions (e.g. for C# projects)]' \ + '--generate-build-profile[Generate an engine compilation profile by detecting the configuration from the project and save it to a given file. The path should be absolute]:path of profile' \ '--dump-gdextension-interface[generate GDExtension header file 'gdextension_interface.h' in the current folder. This file is the base file required to implement a GDExtension.]' \ '--dump-extension-api[generate JSON dump of the Godot API for GDExtension bindings named "extension_api.json" in the current folder]' \ '--benchmark[benchmark the run time and print it to console]' \ diff --git a/misc/dist/shell/godot.bash-completion b/misc/dist/shell/godot.bash-completion index 63efa95c10d3..6261e8780216 100644 --- a/misc/dist/shell/godot.bash-completion +++ b/misc/dist/shell/godot.bash-completion @@ -92,6 +92,7 @@ _complete_godot_options() { --doctool --no-docbase --build-solutions +--generate-build-profile --dump-gdextension-interface --dump-extension-api --benchmark diff --git a/misc/dist/shell/godot.fish b/misc/dist/shell/godot.fish index 3f0675fcb2bc..f1557a2744c0 100644 --- a/misc/dist/shell/godot.fish +++ b/misc/dist/shell/godot.fish @@ -110,6 +110,7 @@ complete -c godot -l validate-conversion-3to4 -d "Shows what elements will be re complete -c godot -l doctool -d "Dump the engine API reference to the given path in XML format, merging if existing files are found" -r complete -c godot -l no-docbase -d "Disallow dumping the base types (used with --doctool)" complete -c godot -l build-solutions -d "Build the scripting solutions (e.g. for C# projects)" +complete -c godot -l generate-build-profile -d "Generate an engine compilation profile by detecting the configuration from the project and save it to a given file. The path should be absolute" -x complete -c godot -l dump-gdextension-interface -d "Generate GDExtension header file 'gdextension_interface.h' in the current folder. This file is the base file required to implement a GDExtension" complete -c godot -l dump-extension-api -d "Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder" complete -c godot -l benchmark -d "Benchmark the run time and print it to console" diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 456661d71267..48d5b587d836 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -4669,7 +4669,10 @@ void RenderForwardClustered::_update_dirty_geometry_pipelines() { while (geometry_surface_compilation_dirty_list.first() != nullptr) { GeometryInstanceSurfaceDataCache *surface_cache = geometry_surface_compilation_dirty_list.first()->self(); _mesh_generate_all_pipelines_for_surface_cache(surface_cache, global_pipeline_data_compiled); - surface_cache->compilation_dirty_element.remove_from_list(); + + if (surface_cache->compilation_dirty_element.in_list()) { + surface_cache->compilation_dirty_element.remove_from_list(); + } } } }