5dc50327cf
Quote char appears to be ignored by ProcessStartInfo on non-Windows operating systems, so it needs to be escaped.
632 lines
22 KiB
C++
632 lines
22 KiB
C++
/*************************************************************************/
|
|
/* godotsharp_builds.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
|
|
/* */
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
/* a copy of this software and associated documentation files (the */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/*************************************************************************/
|
|
|
|
#include "godotsharp_builds.h"
|
|
|
|
#include "core/os/os.h"
|
|
#include "core/vector.h"
|
|
#include "main/main.h"
|
|
|
|
#include "../glue/cs_glue_version.gen.h"
|
|
#include "../godotsharp_dirs.h"
|
|
#include "../mono_gd/gd_mono_class.h"
|
|
#include "../mono_gd/gd_mono_marshal.h"
|
|
#include "../utils/path_utils.h"
|
|
#include "bindings_generator.h"
|
|
#include "csharp_project.h"
|
|
#include "godotsharp_editor.h"
|
|
|
|
#define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)"
|
|
#define PROP_NAME_MSBUILD_VS "MSBuild (VS Build Tools)"
|
|
#define PROP_NAME_XBUILD "xbuild (Deprecated)"
|
|
|
|
void godot_icall_BuildInstance_ExitCallback(MonoString *p_solution, MonoString *p_config, int p_exit_code) {
|
|
|
|
String solution = GDMonoMarshal::mono_string_to_godot(p_solution);
|
|
String config = GDMonoMarshal::mono_string_to_godot(p_config);
|
|
GodotSharpBuilds::get_singleton()->build_exit_callback(MonoBuildInfo(solution, config), p_exit_code);
|
|
}
|
|
|
|
static Vector<const char *> _get_msbuild_hint_dirs() {
|
|
Vector<const char *> ret;
|
|
#ifdef OSX_ENABLED
|
|
ret.push_back("/Library/Frameworks/Mono.framework/Versions/Current/bin/");
|
|
ret.push_back("/usr/local/var/homebrew/linked/mono/bin/");
|
|
#endif
|
|
ret.push_back("/opt/novell/mono/bin/");
|
|
return ret;
|
|
}
|
|
|
|
#ifdef UNIX_ENABLED
|
|
String _find_build_engine_on_unix(const String &p_name) {
|
|
String ret = path_which(p_name);
|
|
|
|
if (ret.length())
|
|
return ret;
|
|
|
|
String ret_fallback = path_which(p_name + ".exe");
|
|
if (ret_fallback.length())
|
|
return ret_fallback;
|
|
|
|
static Vector<const char *> locations = _get_msbuild_hint_dirs();
|
|
|
|
for (int i = 0; i < locations.size(); i++) {
|
|
String hint_path = locations[i] + p_name;
|
|
|
|
if (FileAccess::exists(hint_path)) {
|
|
return hint_path;
|
|
}
|
|
}
|
|
|
|
return String();
|
|
}
|
|
#endif
|
|
|
|
MonoString *godot_icall_BuildInstance_get_MSBuildPath() {
|
|
|
|
GodotSharpBuilds::BuildTool build_tool = GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool")));
|
|
|
|
#if defined(WINDOWS_ENABLED)
|
|
switch (build_tool) {
|
|
case GodotSharpBuilds::MSBUILD_VS: {
|
|
static String msbuild_tools_path;
|
|
|
|
if (msbuild_tools_path.empty() || !FileAccess::exists(msbuild_tools_path)) {
|
|
// Try to search it again if it wasn't found last time or if it was removed from its location
|
|
msbuild_tools_path = MonoRegUtils::find_msbuild_tools_path();
|
|
|
|
if (msbuild_tools_path.empty()) {
|
|
ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_VS "'. Tried with path: " + msbuild_tools_path);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (!msbuild_tools_path.ends_with("\\"))
|
|
msbuild_tools_path += "\\";
|
|
|
|
return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe");
|
|
} break;
|
|
case GodotSharpBuilds::MSBUILD_MONO: {
|
|
String msbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("msbuild.bat");
|
|
|
|
if (!FileAccess::exists(msbuild_path)) {
|
|
ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_MONO "'. Tried with path: " + msbuild_path);
|
|
return NULL;
|
|
}
|
|
|
|
return GDMonoMarshal::mono_string_from_godot(msbuild_path);
|
|
} break;
|
|
case GodotSharpBuilds::XBUILD: {
|
|
String xbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("xbuild.bat");
|
|
|
|
if (!FileAccess::exists(xbuild_path)) {
|
|
ERR_PRINTS("Cannot find executable for '" PROP_NAME_XBUILD "'. Tried with path: " + xbuild_path);
|
|
return NULL;
|
|
}
|
|
|
|
return GDMonoMarshal::mono_string_from_godot(xbuild_path);
|
|
} break;
|
|
default:
|
|
ERR_EXPLAIN("You don't deserve to live");
|
|
CRASH_NOW();
|
|
}
|
|
#elif defined(UNIX_ENABLED)
|
|
static String msbuild_path;
|
|
static String xbuild_path;
|
|
|
|
if (build_tool == GodotSharpBuilds::XBUILD) {
|
|
if (xbuild_path.empty() || !FileAccess::exists(xbuild_path)) {
|
|
// Try to search it again if it wasn't found last time or if it was removed from its location
|
|
xbuild_path = _find_build_engine_on_unix("msbuild");
|
|
}
|
|
|
|
if (xbuild_path.empty()) {
|
|
ERR_PRINT("Cannot find binary for '" PROP_NAME_XBUILD "'");
|
|
return NULL;
|
|
}
|
|
} else {
|
|
if (msbuild_path.empty() || !FileAccess::exists(msbuild_path)) {
|
|
// Try to search it again if it wasn't found last time or if it was removed from its location
|
|
msbuild_path = _find_build_engine_on_unix("msbuild");
|
|
}
|
|
|
|
if (msbuild_path.empty()) {
|
|
ERR_PRINT("Cannot find binary for '" PROP_NAME_MSBUILD_MONO "'");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? msbuild_path : xbuild_path);
|
|
#else
|
|
(void)build_tool; // UNUSED
|
|
|
|
ERR_EXPLAIN("Not implemented on this platform");
|
|
ERR_FAIL_V(NULL);
|
|
#endif
|
|
}
|
|
|
|
MonoString *godot_icall_BuildInstance_get_MonoWindowsBinDir() {
|
|
|
|
#if defined(WINDOWS_ENABLED)
|
|
const MonoRegInfo &mono_reg_info = GDMono::get_singleton()->get_mono_reg_info();
|
|
if (mono_reg_info.bin_dir.length()) {
|
|
return GDMonoMarshal::mono_string_from_godot(mono_reg_info.bin_dir);
|
|
}
|
|
|
|
ERR_EXPLAIN("Cannot find Mono's binaries directory in the registry");
|
|
ERR_FAIL_V(NULL);
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
MonoBoolean godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows() {
|
|
|
|
#if defined(WINDOWS_ENABLED)
|
|
return GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))) == GodotSharpBuilds::MSBUILD_MONO;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
MonoBoolean godot_icall_BuildInstance_get_PrintBuildOutput() {
|
|
|
|
return (bool)EDITOR_GET("mono/builds/print_build_output");
|
|
}
|
|
|
|
void GodotSharpBuilds::register_internal_calls() {
|
|
|
|
static bool registered = false;
|
|
ERR_FAIL_COND(registered);
|
|
registered = true;
|
|
|
|
mono_add_internal_call("GodotSharpTools.Build.BuildSystem::godot_icall_BuildInstance_ExitCallback", (void *)godot_icall_BuildInstance_ExitCallback);
|
|
mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MSBuildPath", (void *)godot_icall_BuildInstance_get_MSBuildPath);
|
|
mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MonoWindowsBinDir", (void *)godot_icall_BuildInstance_get_MonoWindowsBinDir);
|
|
mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows", (void *)godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows);
|
|
mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_PrintBuildOutput", (void *)godot_icall_BuildInstance_get_PrintBuildOutput);
|
|
}
|
|
|
|
void GodotSharpBuilds::show_build_error_dialog(const String &p_message) {
|
|
|
|
GodotSharpEditor::get_singleton()->show_error_dialog(p_message, "Build error");
|
|
MonoBottomPanel::get_singleton()->show_build_tab();
|
|
}
|
|
|
|
bool GodotSharpBuilds::build_api_sln(const String &p_api_sln_dir, const String &p_config) {
|
|
|
|
String api_sln_file = p_api_sln_dir.plus_file(API_SOLUTION_NAME ".sln");
|
|
|
|
String core_api_assembly_dir = p_api_sln_dir.plus_file(CORE_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config);
|
|
String core_api_assembly_file = core_api_assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
|
|
|
|
String editor_api_assembly_dir = p_api_sln_dir.plus_file(EDITOR_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config);
|
|
String editor_api_assembly_file = editor_api_assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
|
|
|
|
if (!FileAccess::exists(core_api_assembly_file) || !FileAccess::exists(editor_api_assembly_file)) {
|
|
MonoBuildInfo api_build_info(api_sln_file, p_config);
|
|
// TODO Replace this global NoWarn with '#pragma warning' directives on generated files,
|
|
// once we start to actively document manually maintained C# classes
|
|
api_build_info.custom_props.push_back("NoWarn=1591"); // Ignore missing documentation warnings
|
|
|
|
if (!GodotSharpBuilds::get_singleton()->build(api_build_info)) {
|
|
show_build_error_dialog("Failed to build " API_SOLUTION_NAME " solution.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) {
|
|
|
|
// Create destination directory if needed
|
|
if (!DirAccess::exists(p_dst_dir)) {
|
|
DirAccess *da = DirAccess::create_for_path(p_dst_dir);
|
|
Error err = da->make_dir_recursive(p_dst_dir);
|
|
memdelete(da);
|
|
|
|
if (err != OK) {
|
|
show_build_error_dialog("Failed to create destination directory for the API assemblies. Error: " + itos(err));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
String assembly_file = p_assembly_name + ".dll";
|
|
String assembly_src = p_src_dir.plus_file(assembly_file);
|
|
String assembly_dst = p_dst_dir.plus_file(assembly_file);
|
|
|
|
if (!FileAccess::exists(assembly_dst) ||
|
|
FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) ||
|
|
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
|
|
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
|
|
|
String xml_file = p_assembly_name + ".xml";
|
|
if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK)
|
|
WARN_PRINTS("Failed to copy " + xml_file);
|
|
|
|
String pdb_file = p_assembly_name + ".pdb";
|
|
if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK)
|
|
WARN_PRINTS("Failed to copy " + pdb_file);
|
|
|
|
Error err = da->copy(assembly_src, assembly_dst);
|
|
|
|
if (err != OK) {
|
|
show_build_error_dialog("Failed to copy " + assembly_file);
|
|
return false;
|
|
}
|
|
|
|
GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
String GodotSharpBuilds::_api_folder_name(APIAssembly::Type p_api_type) {
|
|
|
|
uint64_t api_hash = p_api_type == APIAssembly::API_CORE ?
|
|
GDMono::get_singleton()->get_api_core_hash() :
|
|
GDMono::get_singleton()->get_api_editor_hash();
|
|
return String::num_uint64(api_hash) +
|
|
"_" + String::num_uint64(BindingsGenerator::get_version()) +
|
|
"_" + String::num_uint64(CS_GLUE_VERSION);
|
|
}
|
|
|
|
bool GodotSharpBuilds::make_api_assembly(APIAssembly::Type p_api_type) {
|
|
|
|
String api_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
|
|
|
|
String editor_prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir();
|
|
String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
|
|
|
|
if (FileAccess::exists(editor_prebuilt_api_dir.plus_file(api_name + ".dll"))) {
|
|
EditorProgress pr("mono_copy_prebuilt_api_assembly", "Copying prebuilt " + api_name + " assembly...", 1);
|
|
pr.step("Copying " + api_name + " assembly", 0);
|
|
return GodotSharpBuilds::copy_api_assembly(editor_prebuilt_api_dir, res_assemblies_dir, api_name, p_api_type);
|
|
}
|
|
|
|
String api_build_config = "Release";
|
|
|
|
EditorProgress pr("mono_build_release_" API_SOLUTION_NAME, "Building " API_SOLUTION_NAME " solution...", 3);
|
|
|
|
pr.step("Generating " API_SOLUTION_NAME " solution", 0);
|
|
|
|
String api_sln_dir = GodotSharpDirs::get_mono_solutions_dir()
|
|
.plus_file(_api_folder_name(APIAssembly::API_CORE));
|
|
|
|
String api_sln_file = api_sln_dir.plus_file(API_SOLUTION_NAME ".sln");
|
|
|
|
if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) {
|
|
BindingsGenerator bindings_generator;
|
|
|
|
if (!OS::get_singleton()->is_stdout_verbose()) {
|
|
bindings_generator.set_log_print_enabled(false);
|
|
}
|
|
|
|
Error err = bindings_generator.generate_cs_api(api_sln_dir);
|
|
if (err != OK) {
|
|
show_build_error_dialog("Failed to generate " API_SOLUTION_NAME " solution. Error: " + itos(err));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
pr.step("Building " API_SOLUTION_NAME " solution", 1);
|
|
|
|
if (!GodotSharpBuilds::build_api_sln(api_sln_dir, api_build_config))
|
|
return false;
|
|
|
|
pr.step("Copying " + api_name + " assembly", 2);
|
|
|
|
// Copy the built assembly to the assemblies directory
|
|
String api_assembly_dir = api_sln_dir.plus_file(api_name).plus_file("bin").plus_file(api_build_config);
|
|
if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name, p_api_type))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GodotSharpBuilds::build_project_blocking(const String &p_config, const Vector<String> &p_godot_defines) {
|
|
|
|
if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
|
|
return true; // No solution to build
|
|
|
|
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE))
|
|
return false;
|
|
|
|
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR))
|
|
return false;
|
|
|
|
EditorProgress pr("mono_project_debug_build", "Building project solution...", 1);
|
|
pr.step("Building project solution", 0);
|
|
|
|
MonoBuildInfo build_info(GodotSharpDirs::get_project_sln_path(), p_config);
|
|
|
|
// Add Godot defines
|
|
#ifdef WINDOWS_ENABLED
|
|
String constants = "GodotDefineConstants=\"";
|
|
#else
|
|
String constants = "GodotDefineConstants=\\\"";
|
|
#endif
|
|
|
|
for (int i = 0; i < p_godot_defines.size(); i++) {
|
|
constants += "GODOT_" + p_godot_defines[i].to_upper().replace("-", "_").replace(" ", "_").replace(";", "_") + ";";
|
|
}
|
|
|
|
#ifdef REAL_T_IS_DOUBLE
|
|
constants += "GODOT_REAL_T_IS_DOUBLE;";
|
|
#endif
|
|
|
|
#ifdef WINDOWS_ENABLED
|
|
constants += "\"";
|
|
#else
|
|
constants += "\\\"";
|
|
#endif
|
|
build_info.custom_props.push_back(constants);
|
|
|
|
if (!GodotSharpBuilds::get_singleton()->build(build_info)) {
|
|
GodotSharpBuilds::show_build_error_dialog("Failed to build project solution");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GodotSharpBuilds::editor_build_callback() {
|
|
|
|
if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
|
|
return true; // No solution to build
|
|
|
|
String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor");
|
|
String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player");
|
|
|
|
Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor);
|
|
ERR_FAIL_COND_V(metadata_err != OK, false);
|
|
|
|
if (FileAccess::exists(scripts_metadata_path_editor)) {
|
|
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
|
Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player);
|
|
|
|
ERR_EXPLAIN("Failed to copy scripts metadata file");
|
|
ERR_FAIL_COND_V(copy_err != OK, false);
|
|
}
|
|
|
|
Vector<String> godot_defines;
|
|
godot_defines.push_back(OS::get_singleton()->get_name());
|
|
godot_defines.push_back(sizeof(void *) == 4 ? "32" : "64");
|
|
return build_project_blocking("Tools", godot_defines);
|
|
}
|
|
|
|
GodotSharpBuilds *GodotSharpBuilds::singleton = NULL;
|
|
|
|
void GodotSharpBuilds::build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code) {
|
|
|
|
BuildProcess *match = builds.getptr(p_build_info);
|
|
ERR_FAIL_NULL(match);
|
|
|
|
BuildProcess &bp = *match;
|
|
bp.on_exit(p_exit_code);
|
|
}
|
|
|
|
void GodotSharpBuilds::restart_build(MonoBuildTab *p_build_tab) {
|
|
}
|
|
|
|
void GodotSharpBuilds::stop_build(MonoBuildTab *p_build_tab) {
|
|
}
|
|
|
|
bool GodotSharpBuilds::build(const MonoBuildInfo &p_build_info) {
|
|
|
|
BuildProcess *match = builds.getptr(p_build_info);
|
|
|
|
if (match) {
|
|
BuildProcess &bp = *match;
|
|
bp.start(true);
|
|
return bp.exit_code == 0;
|
|
} else {
|
|
BuildProcess bp = BuildProcess(p_build_info);
|
|
bp.start(true);
|
|
builds.set(p_build_info, bp);
|
|
return bp.exit_code == 0;
|
|
}
|
|
}
|
|
|
|
bool GodotSharpBuilds::build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) {
|
|
|
|
BuildProcess *match = builds.getptr(p_build_info);
|
|
|
|
if (match) {
|
|
BuildProcess &bp = *match;
|
|
bp.start();
|
|
return !bp.exited; // failed to start
|
|
} else {
|
|
BuildProcess bp = BuildProcess(p_build_info, p_callback);
|
|
bp.start();
|
|
builds.set(p_build_info, bp);
|
|
return !bp.exited; // failed to start
|
|
}
|
|
}
|
|
|
|
GodotSharpBuilds::GodotSharpBuilds() {
|
|
|
|
singleton = this;
|
|
|
|
EditorNode::get_singleton()->add_build_callback(&GodotSharpBuilds::editor_build_callback);
|
|
|
|
// Build tool settings
|
|
EditorSettings *ed_settings = EditorSettings::get_singleton();
|
|
|
|
#ifdef WINDOWS_ENABLED
|
|
EDITOR_DEF("mono/builds/build_tool", MSBUILD_VS);
|
|
#else
|
|
EDITOR_DEF("mono/builds/build_tool", MSBUILD_MONO);
|
|
#endif
|
|
|
|
ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/builds/build_tool", PROPERTY_HINT_ENUM,
|
|
PROP_NAME_MSBUILD_MONO
|
|
#ifdef WINDOWS_ENABLED
|
|
"," PROP_NAME_MSBUILD_VS
|
|
#endif
|
|
"," PROP_NAME_XBUILD));
|
|
|
|
EDITOR_DEF("mono/builds/print_build_output", false);
|
|
}
|
|
|
|
GodotSharpBuilds::~GodotSharpBuilds() {
|
|
|
|
singleton = NULL;
|
|
}
|
|
|
|
void GodotSharpBuilds::BuildProcess::on_exit(int p_exit_code) {
|
|
|
|
exited = true;
|
|
exit_code = p_exit_code;
|
|
build_tab->on_build_exit(p_exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR);
|
|
build_instance.unref();
|
|
|
|
if (exit_callback)
|
|
exit_callback(exit_code);
|
|
}
|
|
|
|
void GodotSharpBuilds::BuildProcess::start(bool p_blocking) {
|
|
|
|
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
|
|
|
|
exit_code = -1;
|
|
|
|
String log_dirpath = build_info.get_log_dirpath();
|
|
|
|
if (build_tab) {
|
|
build_tab->on_build_start();
|
|
} else {
|
|
build_tab = memnew(MonoBuildTab(build_info, log_dirpath));
|
|
MonoBottomPanel::get_singleton()->add_build_tab(build_tab);
|
|
}
|
|
|
|
if (p_blocking) {
|
|
// Required in order to update the build tasks list
|
|
Main::iteration();
|
|
}
|
|
|
|
if (!exited) {
|
|
exited = true;
|
|
String message = "Tried to start build process, but it is already running";
|
|
build_tab->on_build_exec_failed(message);
|
|
ERR_EXPLAIN(message);
|
|
ERR_FAIL();
|
|
}
|
|
|
|
exited = false;
|
|
|
|
// Remove old issues file
|
|
|
|
String issues_file = get_msbuild_issues_filename();
|
|
DirAccessRef d = DirAccess::create_for_path(log_dirpath);
|
|
if (d->file_exists(issues_file)) {
|
|
Error err = d->remove(issues_file);
|
|
if (err != OK) {
|
|
exited = true;
|
|
String file_path = ProjectSettings::get_singleton()->localize_path(log_dirpath).plus_file(issues_file);
|
|
String message = "Cannot remove issues file: " + file_path;
|
|
build_tab->on_build_exec_failed(message);
|
|
ERR_EXPLAIN(message);
|
|
ERR_FAIL();
|
|
}
|
|
}
|
|
|
|
GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Build", "BuildInstance");
|
|
|
|
MonoObject *mono_object = mono_object_new(mono_domain_get(), klass->get_mono_ptr());
|
|
|
|
// Construct
|
|
|
|
Variant solution = build_info.solution;
|
|
Variant config = build_info.configuration;
|
|
|
|
const Variant *ctor_args[2] = { &solution, &config };
|
|
|
|
MonoException *exc = NULL;
|
|
GDMonoMethod *ctor = klass->get_method(".ctor", 2);
|
|
ctor->invoke(mono_object, ctor_args, &exc);
|
|
|
|
if (exc) {
|
|
exited = true;
|
|
GDMonoUtils::debug_unhandled_exception(exc);
|
|
String message = "The build constructor threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc);
|
|
build_tab->on_build_exec_failed(message);
|
|
ERR_EXPLAIN(message);
|
|
ERR_FAIL();
|
|
}
|
|
|
|
// Call Build
|
|
|
|
String logger_assembly_path = GDMono::get_singleton()->get_editor_tools_assembly()->get_path();
|
|
Variant logger_assembly = ProjectSettings::get_singleton()->globalize_path(logger_assembly_path);
|
|
Variant logger_output_dir = log_dirpath;
|
|
Variant custom_props = build_info.custom_props;
|
|
|
|
const Variant *args[3] = { &logger_assembly, &logger_output_dir, &custom_props };
|
|
|
|
exc = NULL;
|
|
GDMonoMethod *build_method = klass->get_method(p_blocking ? "Build" : "BuildAsync", 3);
|
|
build_method->invoke(mono_object, args, &exc);
|
|
|
|
if (exc) {
|
|
exited = true;
|
|
GDMonoUtils::debug_unhandled_exception(exc);
|
|
String message = "The build method threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc);
|
|
build_tab->on_build_exec_failed(message);
|
|
ERR_EXPLAIN(message);
|
|
ERR_FAIL();
|
|
}
|
|
|
|
// Build returned
|
|
|
|
if (p_blocking) {
|
|
exited = true;
|
|
exit_code = klass->get_field("exitCode")->get_int_value(mono_object);
|
|
|
|
if (exit_code != 0) {
|
|
String log_filepath = build_info.get_log_dirpath().plus_file(get_msbuild_log_filename());
|
|
print_verbose("MSBuild exited with code: " + itos(exit_code) + ". Log file: " + log_filepath);
|
|
}
|
|
|
|
build_tab->on_build_exit(exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR);
|
|
} else {
|
|
build_instance = MonoGCHandle::create_strong(mono_object);
|
|
exited = false;
|
|
}
|
|
}
|
|
|
|
GodotSharpBuilds::BuildProcess::BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) :
|
|
build_info(p_build_info),
|
|
build_tab(NULL),
|
|
exit_callback(p_callback),
|
|
exited(true),
|
|
exit_code(-1) {
|
|
}
|